153 lines
6.7 KiB
TypeScript
153 lines
6.7 KiB
TypeScript
import { readConfig } from "./src/config";
|
|
import { runCiPublishBackendCoreDryRunPreflight, type PublishPreflightTransport } from "./src/ci";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function asRecord(value: unknown, label: string): JsonRecord {
|
|
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, { value });
|
|
return value as JsonRecord;
|
|
}
|
|
|
|
const commit = "0123456789abcdef0123456789abcdef01234567";
|
|
const config = readConfig();
|
|
|
|
const readyTransport: PublishPreflightTransport = {
|
|
kind: "remote-frontend",
|
|
remoteHost: "https://example.invalid",
|
|
coreFetch: async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
ok: true,
|
|
dbReady: true,
|
|
},
|
|
}),
|
|
dispatchHostSsh: async (command) => {
|
|
if (command.includes("kubectl get namespace unidesk-ci")) {
|
|
return {
|
|
ok: true,
|
|
taskId: "task-ci-runner",
|
|
status: "succeeded",
|
|
stdout: [
|
|
"kubectl=ok",
|
|
"docker=ok",
|
|
"docker_socket=true",
|
|
"namespace=true",
|
|
"tekton_pipeline=true",
|
|
"tekton_task=true",
|
|
"service_account=true",
|
|
"pvc=true",
|
|
"source_parent_directory=true",
|
|
].join("\n"),
|
|
stderr: "",
|
|
exitCode: 0,
|
|
raw: {},
|
|
};
|
|
}
|
|
if (command.includes("v2/")) {
|
|
return {
|
|
ok: true,
|
|
taskId: "task-registry",
|
|
status: "succeeded",
|
|
stdout: [
|
|
"systemctl_available=true",
|
|
"docker_available=true",
|
|
"curl_available=true",
|
|
"unit_exists=true",
|
|
"unit_active=active",
|
|
"unit_enabled=enabled",
|
|
"compose_exists=true",
|
|
"config_exists=true",
|
|
"storage_exists=true",
|
|
"container_running=true",
|
|
"container_status=running",
|
|
"container_image=registry:2.8.3",
|
|
"container_restart_policy=always",
|
|
"listener_count=1",
|
|
"bad_listener_count=0",
|
|
"loopback_only=true",
|
|
"v2_http_code=200",
|
|
"image_matches=true",
|
|
"config_hash_matches=true",
|
|
"compose_hash_matches=true",
|
|
"unit_hash_matches=true",
|
|
].join("\n"),
|
|
stderr: "",
|
|
exitCode: 0,
|
|
raw: {},
|
|
};
|
|
}
|
|
return {
|
|
ok: true,
|
|
taskId: "task-host-ssh",
|
|
status: "succeeded",
|
|
stdout: "provider_host_ssh=ok\n",
|
|
stderr: "",
|
|
exitCode: 0,
|
|
raw: {},
|
|
};
|
|
},
|
|
commandCwd: "/workspace/unidesk",
|
|
artifactRegistryCommand: (probe) => ["mock", probe.action, probe.remoteCommandShape],
|
|
};
|
|
|
|
async function main(): Promise<void> {
|
|
const result = await runCiPublishBackendCoreDryRunPreflight(config, [
|
|
"publish-backend-core",
|
|
"--commit",
|
|
commit,
|
|
"--dry-run",
|
|
], readyTransport);
|
|
|
|
const record = asRecord(result, "backend-core preflight");
|
|
const source = asRecord(record.source, "source");
|
|
const sourceAuth = asRecord(record.sourceAuth, "sourceAuth");
|
|
const d601Ci = asRecord(record.d601Ci, "d601Ci");
|
|
const artifactSummary = asRecord(record.artifactSummary, "artifactSummary");
|
|
const artifactRequirements = asRecord(record.artifactRequirements, "artifactRequirements");
|
|
const requiredLabels = asRecord(artifactRequirements.requiredLabels, "requiredLabels");
|
|
const devApplyPath = asRecord(record.devApplyPath, "devApplyPath");
|
|
const controlledPublish = asRecord(record.controlledPublish, "controlledPublish");
|
|
const blockedScopes = Array.isArray(record.blockedScopes) ? record.blockedScopes as string[] : [];
|
|
|
|
assertCondition(record.ok === true, "ready backend-core preflight should pass", record);
|
|
assertCondition(record.mode === "dry-run-preflight", "backend-core dry-run must be preflight mode", record);
|
|
assertCondition(record.targetCommit === commit, "target commit should be surfaced", record);
|
|
assertCondition(record.sourceRepo === "https://github.com/pikasTech/unidesk", "source repo should come from CI.json", record);
|
|
assertCondition(record.registryTarget === "127.0.0.1:5000/unidesk/backend-core", "registry target should be D601 backend-core repository", record);
|
|
assertCondition(record.wouldBuildOnD601 === true, "preflight should state real publish would build on D601", record);
|
|
assertCondition(record.dryRunBuildStarted === false, "dry-run must not start a build", record);
|
|
assertCondition(blockedScopes.length === 0, "ready preflight should have no blocked scopes", record);
|
|
assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source auth should use GitHub SSH fetch URL", source);
|
|
assertCondition(sourceAuth.egressProxy === "http://127.0.0.1:18789", "source auth should name provider egress proxy", sourceAuth);
|
|
assertCondition(d601Ci.wouldBuildOnD601 === true, "D601 CI runner preflight should expose D601 build boundary", d601Ci);
|
|
assertCondition(artifactSummary.imageRef === `127.0.0.1:5000/unidesk/backend-core:${commit}`, "artifact should be commit-pinned", artifactSummary);
|
|
assertCondition(artifactSummary.digest === null, "dry-run must not fake digest", artifactSummary);
|
|
assertCondition(requiredLabels["unidesk.ai/service-id"] === "backend-core", "service id label should be required", requiredLabels);
|
|
assertCondition(requiredLabels["unidesk.ai/source-commit"] === commit, "source commit label should be required", requiredLabels);
|
|
assertCondition(devApplyPath.pullOnly === true, "dev apply path should be pull-only", devApplyPath);
|
|
assertCondition(String(devApplyPath.apply ?? "").includes("deploy apply --env dev --service backend-core"), "dev apply command should be surfaced", devApplyPath);
|
|
assertCondition(!String(devApplyPath.apply ?? "").includes("--env prod"), "dev apply path must not point to prod", devApplyPath);
|
|
assertCondition(controlledPublish.environment === "D601", "controlled publish should name D601", controlledPublish);
|
|
assertCondition(String(record.boundary ?? "").includes("no Rust compile"), "boundary should explicitly forbid Rust compile during dry-run", record);
|
|
assertCondition(String(record.recommendedAction ?? "").includes("ci publish-backend-core"), "recommended action should name real publish command", record);
|
|
|
|
process.stdout.write(`${JSON.stringify({
|
|
ok: true,
|
|
checks: [
|
|
"backend-core dry-run exposes target commit, source repo, D601 runner and registry target",
|
|
"backend-core dry-run reports wouldBuildOnD601 without starting a build",
|
|
"artifact labels and digest requirements are visible without faking a digest",
|
|
"standard path is publish artifact, verify labels/digest, then dev pull-only apply",
|
|
],
|
|
}, null, 2)}\n`);
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
await main();
|
|
}
|