102 lines
5.2 KiB
TypeScript
102 lines
5.2 KiB
TypeScript
import { readConfig } from "./src/config";
|
|
import { runCiPublishUserServiceDryRunPreflight, 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 infraBlockedTransport: PublishPreflightTransport = {
|
|
coreFetch: async (path) => ({
|
|
ok: false,
|
|
status: 503,
|
|
body: {
|
|
ok: false,
|
|
failureKind: "target-stack-not-running",
|
|
runnerDisposition: "infra-blocked",
|
|
degradedReason: "backend-core-container-missing",
|
|
message: `backend-core unavailable for ${path}`,
|
|
},
|
|
}),
|
|
dispatchHostSsh: async (command, waitMs, remoteTimeoutMs) => ({
|
|
ok: false,
|
|
taskId: null,
|
|
status: "infra-blocked",
|
|
stdout: "",
|
|
stderr: `backend-core bridge unavailable while dispatching readonly SSH task (${waitMs}/${remoteTimeoutMs})`,
|
|
exitCode: null,
|
|
raw: {
|
|
ok: false,
|
|
failureKind: "target-stack-not-running",
|
|
runnerDisposition: "infra-blocked",
|
|
command,
|
|
},
|
|
}),
|
|
commandCwd: "/workspace/unidesk",
|
|
artifactRegistryCommand: (probe) => [process.execPath, "scripts/cli.ts", "ssh", probe.providerId, "argv", "bash", "-lc", probe.script],
|
|
};
|
|
|
|
async function main(): Promise<void> {
|
|
const result = await runCiPublishUserServiceDryRunPreflight(config, [
|
|
"publish-user-service",
|
|
"--service",
|
|
"frontend",
|
|
"--commit",
|
|
commit,
|
|
"--dry-run",
|
|
], infraBlockedTransport);
|
|
|
|
const record = asRecord(result, "preflight");
|
|
const source = asRecord(record.source, "source");
|
|
const channels = Array.isArray(record.channels) ? record.channels.map((item) => asRecord(item, "channel")) : [];
|
|
const registry = asRecord(record.registry, "registry");
|
|
const backendCore = asRecord(channels.find((item) => item.channel === "backend-core-api")?.detail, "backendCore detail");
|
|
const backendCoreTransport = asRecord(backendCore.detail, "backendCore transport payload");
|
|
const backendCoreBody = asRecord(backendCoreTransport.body, "backendCore body payload");
|
|
const providerDispatch = asRecord(channels.find((item) => item.channel === "provider-dispatch")?.detail, "providerDispatch detail");
|
|
const missingChannels = Array.isArray(record.missingChannels) ? record.missingChannels as string[] : [];
|
|
|
|
assertCondition(record.ok === false, "infra-blocked preflight should fail", record);
|
|
assertCondition(record.mode === "dry-run-preflight", "dry-run preflight mode should be reported", record);
|
|
assertCondition(record.runnerDisposition === "infra-blocked", "runnerDisposition should be infra-blocked", record);
|
|
assertCondition(Array.isArray(record.missingChannels), "missingChannels should be an array", record);
|
|
assertCondition(missingChannels.includes("backend-core-api"), "backend-core-api should be missing", record);
|
|
assertCondition(missingChannels.includes("database"), "database should be missing", record);
|
|
assertCondition(missingChannels.includes("provider-dispatch"), "provider-dispatch should be missing", record);
|
|
assertCondition(missingChannels.includes("provider-host-ssh"), "provider-host-ssh should be missing", record);
|
|
assertCondition(missingChannels.includes("artifact-registry"), "artifact-registry should be missing", record);
|
|
assertCondition(!JSON.stringify(record).includes("No such container: unidesk-database"), "raw container error should not leak", record);
|
|
assertCondition(backendCoreBody.failureKind === "target-stack-not-running", "backend-core detail should classify target-stack-not-running", backendCoreBody);
|
|
assertCondition(providerDispatch.status === "infra-blocked", "provider dispatch should be infra-blocked", providerDispatch);
|
|
assertCondition(registry.ok === false, "registry channel should fail without backend-core bridge", registry);
|
|
assertCondition(Array.isArray(channels) && channels.length >= 5, "expected five channel probes", channels);
|
|
assertCondition(source.mode === "planned-only", "source should remain planned-only", source);
|
|
assertCondition(source.repoFetchUrl === "git@github.com:pikasTech/unidesk.git", "source repo should use CI catalog ssh form", source);
|
|
assertCondition(asRecord(record.artifactSummary, "artifactSummary").imageRef === `127.0.0.1:5000/unidesk/frontend:${commit}`, "artifact ref should remain commit-pinned", record.artifactSummary);
|
|
assertCondition(String(record.boundary ?? "").includes("read-only"), "boundary should state preflight is read-only", record);
|
|
|
|
process.stdout.write(`${JSON.stringify({
|
|
ok: true,
|
|
checks: [
|
|
"dry-run preflight returns infra-blocked when backend-core/database/provider channels are absent",
|
|
"missing channel list names the absent channels",
|
|
"artifact summary remains commit-pinned and read-only",
|
|
],
|
|
missingChannels: record.missingChannels,
|
|
registry,
|
|
}, null, 2)}\n`);
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
await main();
|
|
}
|