Publish curated Knowledge Hub source from 2ed833f290b439a7bc91e5fd19b042c269e3bee9
This commit is contained in:
1 parent
10f2edcd29
commit
59f15b2d08
218 files changed
+23995
-1849
No files matched your search
+102
-33
@@ -3,7 +3,8 @@
|
||||
import fs from "node:fs";
|
||||
import process from "node:process";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { commandExists, run } from "./lib/exec.mjs";
|
||||
import { capture, commandExists, run } from "./lib/exec.mjs";
|
||||
import { requireReleaseTool } from "./lib/release-gates.mjs";
|
||||
|
||||
const flags = new Set(process.argv.slice(2));
|
||||
const structureOnly = flags.has("--structure-only");
|
||||
@@ -11,6 +12,24 @@ const fast = flags.has("--fast");
|
||||
const ci = flags.has("--ci");
|
||||
const release = flags.has("--release");
|
||||
const publicSourceExport = fs.existsSync("PUBLIC_SOURCE_EXPORT.md");
|
||||
const releaseImageReference = process.env.RELEASE_IMAGE_REFERENCE ?? "itworx/knowledge-hub:local";
|
||||
let releaseImageId = null;
|
||||
// Test hosts use fixed configuration and do not need appsettings hot reload.
|
||||
// Avoid consuming the shared Linux host's inotify quota for hundreds of factories.
|
||||
const backendTestEnvironment = { DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE: "false" };
|
||||
|
||||
function splitImageReference(reference) {
|
||||
const tagSeparator = reference.lastIndexOf(":");
|
||||
if (tagSeparator <= reference.lastIndexOf("/") || tagSeparator === 0 || /\s/.test(reference)) {
|
||||
throw new Error(`Release image reference must include a repository and tag: '${reference}'.`);
|
||||
}
|
||||
const repository = reference.slice(0, tagSeparator);
|
||||
const tag = reference.slice(tagSeparator + 1);
|
||||
if (!repository || !/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/.test(tag)) {
|
||||
throw new Error(`Release image reference has an invalid repository or tag: '${reference}'.`);
|
||||
}
|
||||
return { repository, tag };
|
||||
}
|
||||
|
||||
function findWorkingPython() {
|
||||
// On some Windows setups `python3` resolves via PATH to the Microsoft Store
|
||||
@@ -39,12 +58,22 @@ async function section(name, action) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Fail before starting a long release matrix when a mandatory scanner is
|
||||
// unavailable. CI provides these through the pinned release workflow setup;
|
||||
// local release verification must install the same tools explicitly.
|
||||
const releaseGitleaks = release ? requireReleaseTool("gitleaks") : null;
|
||||
const releaseTrivy = release ? requireReleaseTool("trivy") : null;
|
||||
|
||||
await section("Bundle structure", async () => {
|
||||
if (publicSourceExport) {
|
||||
await run(process.execPath, ["scripts/validate-public-source.mjs"]);
|
||||
} else {
|
||||
await run(process.execPath, ["scripts/validate-bundle.mjs"]);
|
||||
await run(process.execPath, ["scripts/test-safety-hook.mjs"]);
|
||||
await run(process.execPath, ["scripts/validate-product-state.mjs"]);
|
||||
await run(process.execPath, ["scripts/validate-route-map.mjs"]);
|
||||
await run(process.execPath, ["--test", "scripts/tests/route-map.test.mjs", "scripts/tests/product-backlog.test.mjs", "scripts/tests/validate-public-source.test.mjs", "scripts/tests/validate-bundle.test.mjs", "scripts/tests/v2-r4-real-backup-restore-drill.test.mjs", "scripts/tests/cleanup-temporary-resources.test.mjs"]);
|
||||
await run(python, ["-m", "unittest", "scripts.tests.test_public_source_pipeline"]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,7 +137,7 @@ try {
|
||||
"trx",
|
||||
"--results-directory",
|
||||
"artifacts/test-results"
|
||||
]);
|
||||
], { env: backendTestEnvironment });
|
||||
});
|
||||
|
||||
await section("Frontend install and verification", async () => {
|
||||
@@ -149,16 +178,60 @@ try {
|
||||
|
||||
if (ci || release) {
|
||||
await section("Production image build", async () => {
|
||||
await run("docker", ["compose", "build", "app"], {
|
||||
env: {
|
||||
POSTGRES_PASSWORD: "verification-only-not-for-production",
|
||||
BUILD_COMMIT: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? "verification"
|
||||
const buildCommit = process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? "verification";
|
||||
if (release) {
|
||||
const { repository, tag } = splitImageReference(releaseImageReference);
|
||||
await run("docker", [
|
||||
"build",
|
||||
"--file",
|
||||
"deploy/docker/Dockerfile",
|
||||
"--tag",
|
||||
releaseImageReference,
|
||||
"--build-arg",
|
||||
`BUILD_COMMIT=${buildCommit}`,
|
||||
"--build-arg",
|
||||
`RELEASE_TAG=${process.env.RELEASE_TAG ?? ""}`,
|
||||
"--build-arg",
|
||||
`IMAGE_REPOSITORY=${repository}`,
|
||||
"--build-arg",
|
||||
`IMAGE_TAG=${tag}`,
|
||||
"."
|
||||
]);
|
||||
releaseImageId = await capture("docker", [
|
||||
"image",
|
||||
"inspect",
|
||||
"--format",
|
||||
"{{.Id}}",
|
||||
releaseImageReference
|
||||
]);
|
||||
if (!/^sha256:[0-9a-f]{64}$/i.test(releaseImageId)) {
|
||||
throw new Error(`Docker returned an invalid image ID for '${releaseImageReference}'.`);
|
||||
}
|
||||
});
|
||||
const imageIdPath = "artifacts/release-verification/image-id.txt";
|
||||
fs.mkdirSync("artifacts/release-verification", { recursive: true });
|
||||
fs.writeFileSync(imageIdPath, `${releaseImageId}\n`, "utf8");
|
||||
console.log(`Captured immutable release image ID in ${imageIdPath}.`);
|
||||
} else {
|
||||
await run("docker", ["compose", "build", "app"], {
|
||||
env: {
|
||||
POSTGRES_PASSWORD: "verification-only-not-for-production",
|
||||
BUILD_COMMIT: buildCommit
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (release) {
|
||||
await section("Release evidence integrity tests", async () => {
|
||||
await run(process.execPath, [
|
||||
"--test",
|
||||
"scripts/tests/release-gates.test.mjs",
|
||||
"scripts/tests/generate-release-evidence.test.mjs",
|
||||
"scripts/tests/generate-release-manifest.test.mjs"
|
||||
]);
|
||||
});
|
||||
|
||||
await section("Full-scale search/tree suite (release-gate only)", async () => {
|
||||
await run("dotnet", [
|
||||
"test",
|
||||
@@ -167,8 +240,12 @@ try {
|
||||
"Release",
|
||||
"--no-build",
|
||||
"--filter",
|
||||
"Category=ReleaseScale"
|
||||
]);
|
||||
"Category=ReleaseScale",
|
||||
"--logger",
|
||||
"trx",
|
||||
"--results-directory",
|
||||
"artifacts/test-results"
|
||||
], { env: backendTestEnvironment });
|
||||
});
|
||||
|
||||
await section(".NET dependency vulnerability scan", async () => {
|
||||
@@ -195,32 +272,24 @@ try {
|
||||
});
|
||||
|
||||
await section("Secret scan", async () => {
|
||||
if (!commandExists("gitleaks")) {
|
||||
throw new Error("gitleaks is required for the release secret scan but was not found on PATH.");
|
||||
}
|
||||
|
||||
await run("gitleaks", ["detect", "--source", ".", "--redact"]);
|
||||
await run(releaseGitleaks, ["detect", "--source", ".", "--redact"]);
|
||||
});
|
||||
|
||||
if (commandExists("trivy")) {
|
||||
await section("Container vulnerability scan", async () => {
|
||||
await run("trivy", [
|
||||
"image",
|
||||
"--severity",
|
||||
"CRITICAL,HIGH",
|
||||
"--exit-code",
|
||||
"1",
|
||||
"--scanners",
|
||||
"vuln",
|
||||
"itworx/knowledge-hub:local"
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"\ntrivy not found on PATH — container vulnerability scan skipped. Install " +
|
||||
"trivy before a real release; this is not a substitute for the scan."
|
||||
);
|
||||
}
|
||||
await section("Container vulnerability scan", async () => {
|
||||
if (!releaseImageId) {
|
||||
throw new Error("The release image ID was not captured after the production image build.");
|
||||
}
|
||||
await run(releaseTrivy, [
|
||||
"image",
|
||||
"--severity",
|
||||
"CRITICAL,HIGH",
|
||||
"--exit-code",
|
||||
"1",
|
||||
"--scanners",
|
||||
"vuln",
|
||||
releaseImageId
|
||||
]);
|
||||
});
|
||||
|
||||
console.warn(
|
||||
"\nRelease verification still requires the milestone-defined E2E (desktop + " +
|
||||
|
||||
Reference in new issue
Block a user