1a88ab3199
The v0.8 milestone: installable on any Debian-based server via docker (Gitea registry image, honest sandbox degrade), deb (amd64+arm64, advisory postinst), and arch-aware binary — with release consistency enforced by pre-commit hooks + Gitea Actions (3-runner fleet on CT 113, machine-made releases with the full 8-asset matrix + registry image), the CLI carrying the full suite (guide, channel-aware doctor), and the admin runbook (deploy/README.md, evidence-backed recipes). ---ci--- phase: 6 milestone: v0.8 status: complete requirements: covered: [REQ-8-001, REQ-8-002, REQ-8-003, REQ-8-004, REQ-8-005] partial: [] ---/ci---
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdtempSync, existsSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const cliDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const repoRoot = join(cliDir, "..", "..");
|
|
|
|
// P1 harness wiring (D-055, Task 1-2-02): the real 600s fresh-clone bootstrap
|
|
// is opt-in — it never runs inside the default `cli:test` suite. Setting
|
|
// CLI_E2E=1 (or `pnpm cli:test:e2e`) runs it; without the gate the test
|
|
// reports a VISIBLE skip with the reason, never a silent green.
|
|
test(
|
|
"fresh-clone E2E: clone -> doctor -> bootstrap -> verify (happy path, transcript)",
|
|
{
|
|
skip: process.env.CLI_E2E
|
|
? false
|
|
: "env-gated: set CLI_E2E=1 (pnpm cli:test:e2e) to run the real 600s bootstrap",
|
|
},
|
|
async () => {
|
|
const work = mkdtempSync(join(tmpdir(), "nc-e2e-"));
|
|
const cloneDir = join(work, "nextcraft");
|
|
|
|
const clone = spawnSync(
|
|
"git",
|
|
["clone", "--quiet", "--no-hardlinks", repoRoot, cloneDir],
|
|
{ encoding: "utf8", timeout: 120000 },
|
|
);
|
|
assert.equal(clone.status, 0, `clone failed: ${clone.stderr}`);
|
|
|
|
const binary = join(repoRoot, "apps", "cli", "dist", "nextcraft-linux-x64");
|
|
const cli = existsSync(binary)
|
|
? binary
|
|
: join(cliDir, "node_modules", ".bin", "tsx");
|
|
|
|
const transcript: string[] = [];
|
|
const step = (name: string, args: string[], expect: number, timeoutMs: number) => {
|
|
// Strip the test-runner's env marker: the child CLI must see a clean
|
|
// env (its isDirectRun() probe keys on NODE_TEST_CONTEXT).
|
|
const { NODE_TEST_CONTEXT: _stripped, ...restEnv } = process.env as Record<string, string | undefined>;
|
|
void _stripped;
|
|
const env = {
|
|
...restEnv,
|
|
PATH: [join(process.env.HOME ?? "/home", ".local/bin"), process.env.PATH].filter(Boolean).join(":"),
|
|
NEXTCRAFT_VERSION: "v-e2e",
|
|
} as Record<string, string | undefined>;
|
|
const run = spawnSync(cli, existsSync(binary) ? args : [join(cliDir, "src", "index.ts"), ...args], {
|
|
cwd: cloneDir,
|
|
encoding: "utf8",
|
|
timeout: timeoutMs,
|
|
env,
|
|
});
|
|
transcript.push(`$ nextcraft ${args.join(" ")} -> exit ${run.status}`);
|
|
if (run.status !== expect) {
|
|
transcript.push(run.stdout, run.stderr);
|
|
assert.fail(
|
|
`${name} exited ${run.status} (expected ${expect})\nTRANSCRIPT:\n${transcript.join("\n")}\nstderr: ${run.stderr}`,
|
|
);
|
|
}
|
|
return run.stdout + run.stderr;
|
|
};
|
|
|
|
const doctorOut = step("doctor", ["doctor"], 0, 60000);
|
|
assert.ok(doctorOut.includes("node"), "doctor mentions node");
|
|
assert.ok(doctorOut.includes("pnpm"), "doctor mentions pnpm");
|
|
|
|
step("bootstrap", ["bootstrap"], 0, 600000);
|
|
|
|
const verifyOut = step("verify", ["verify"], 0, 120000);
|
|
assert.ok(verifyOut.includes("venv"), "verify covers venv");
|
|
assert.ok(existsSync(join(cloneDir, "apps", "ai-service", ".env")), ".env created in clone");
|
|
assert.ok(existsSync(join(cloneDir, "apps", "ai-service", ".venv")), "venv created in clone");
|
|
assert.ok(
|
|
existsSync(join(cloneDir, "node_modules", ".pnpm")),
|
|
"workspace node_modules present",
|
|
);
|
|
|
|
const envExample = readFileSync(join(repoRoot, "apps", "ai-service", ".env.example"), "utf8");
|
|
const envClone = readFileSync(join(cloneDir, "apps", "ai-service", ".env"), "utf8");
|
|
assert.equal(envClone, envExample, ".env content matches template");
|
|
|
|
console.log("E2E TRANSCRIPT:\n" + transcript.join("\n"));
|
|
},
|
|
);
|