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---
150 lines
6.0 KiB
TypeScript
150 lines
6.0 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { discoverToolchain, type Toolchain } from "../src/lib/toolchain.ts";
|
|
import { testCtx } from "./helpers.ts";
|
|
import type { Ctx } from "../src/ctx.ts";
|
|
|
|
// v0.4.6 toolchain discovery: node/pnpm behind nvm/PNPM_HOME init files are
|
|
// invisible to non-interactive shells (the deployed host's real failure).
|
|
// These tests fake the filesystem layout via overridden exists().
|
|
|
|
function ctxWith(
|
|
env: Record<string, string | undefined>,
|
|
existsPaths: string[],
|
|
): Ctx & { out(): string } {
|
|
const base = testCtx({ env: { ...env } as Record<string, string | undefined> });
|
|
const set = new Set(existsPaths.map((p) => join(p)));
|
|
return {
|
|
...base,
|
|
exists: (p: string) => set.has(p),
|
|
} as unknown as Ctx & { out(): string };
|
|
}
|
|
|
|
function touch(root: string, rel: string): string {
|
|
const p = join(root, rel);
|
|
mkdirSync(join(p, ".."), { recursive: true });
|
|
writeFileSync(p, "x");
|
|
return p;
|
|
}
|
|
|
|
test("toolchain: pnpm on PATH is used as-is", () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "nc-tc-"));
|
|
touch(dir, "bin/pnpm");
|
|
const tc = discoverToolchain(
|
|
ctxWith({ PATH: `${join(dir, "bin")}:/usr/bin` }, [join(dir, "bin/pnpm")]),
|
|
);
|
|
assert.equal(tc.pnpmSource, "PATH");
|
|
assert.equal(tc.pnpm, join(dir, "bin", "pnpm"));
|
|
assert.equal(tc.envPathPrefix, undefined);
|
|
rmSync(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("toolchain: nvm layout discovered when off PATH (the host failure)", () => {
|
|
const home = mkdtempSync(join(tmpdir(), "nc-tc-nvm-"));
|
|
const nvmBin = join(home, ".nvm/versions/node/v24.21.0/bin");
|
|
mkdirSync(nvmBin, { recursive: true });
|
|
writeFileSync(join(nvmBin, "pnpm"), "x");
|
|
const nvmRoot = join(home, ".nvm", "versions", "node");
|
|
const existsPaths = [nvmRoot, nvmBin, join(nvmBin, "pnpm")];
|
|
// PATH has no pnpm anywhere
|
|
const tc = discoverToolchain(
|
|
ctxWith({ PATH: "/usr/bin:/bin", HOME: home }, existsPaths),
|
|
);
|
|
assert.equal(tc.pnpmSource, "nvm");
|
|
assert.ok(tc.pnpm?.includes("v24.21.0"), tc.pnpm);
|
|
assert.equal(tc.envPathPrefix, nvmBin);
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
test("toolchain: PNPM_HOME bin discovered as fallback", () => {
|
|
const home = mkdtempSync(join(tmpdir(), "nc-tc-ph-"));
|
|
const bin = join(home, ".local/share/pnpm/bin");
|
|
mkdirSync(bin, { recursive: true });
|
|
writeFileSync(join(bin, "pnpm"), "x");
|
|
const tc = discoverToolchain(
|
|
ctxWith({ PATH: "/usr/bin:/bin", HOME: home }, [bin, join(bin, "pnpm"), home]),
|
|
);
|
|
assert.equal(tc.pnpmSource, "PNPM_HOME");
|
|
assert.equal(tc.envPathPrefix, bin);
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
test("toolchain: split install (nvm node + PNPM_HOME pnpm) — prefix carries BOTH (host case)", () => {
|
|
const home = mkdtempSync(join(tmpdir(), "nc-tc-split-"));
|
|
const nvmBin = join(home, ".nvm/versions/node/v24.21.0/bin");
|
|
const pnpmBin = join(home, ".local/share/pnpm/bin");
|
|
mkdirSync(nvmBin, { recursive: true });
|
|
mkdirSync(pnpmBin, { recursive: true });
|
|
writeFileSync(join(nvmBin, "node"), "x"); // node lives in nvm
|
|
writeFileSync(join(pnpmBin, "pnpm"), "x"); // pnpm lives in PNPM_HOME
|
|
const nvmRoot = join(home, ".nvm", "versions", "node");
|
|
const tc = discoverToolchain(
|
|
ctxWith(
|
|
{ PATH: "/usr/bin:/bin", HOME: home },
|
|
[nvmRoot, nvmBin, join(nvmBin, "node"), pnpmBin, join(pnpmBin, "pnpm")],
|
|
),
|
|
);
|
|
assert.equal(tc.pnpmSource, "PNPM_HOME");
|
|
assert.equal(tc.pnpm, join(pnpmBin, "pnpm"));
|
|
// the prefix must contain pnpm's dir AND node's dir — pnpm children
|
|
// exec bare `node`, which must resolve to the nvm node (the exact
|
|
// production-host layout: pnpm via PNPM_HOME, node via nvm).
|
|
const prefix = (tc.envPathPrefix ?? "").split(":");
|
|
assert.ok(prefix.includes(pnpmBin), prefix.join(":"));
|
|
assert.ok(prefix.includes(nvmBin), prefix.join(":"));
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
test("toolchain: newest nvm version wins when several installed", () => {
|
|
const home = mkdtempSync(join(tmpdir(), "nc-tc-nvm2-"));
|
|
const nvmRoot = join(home, ".nvm", "versions", "node");
|
|
const existsPaths: string[] = [nvmRoot];
|
|
for (const v of ["v18.20.1", "v24.21.0", "v20.10.0"]) {
|
|
const bin = join(home, `.nvm/versions/node/${v}/bin`);
|
|
mkdirSync(bin, { recursive: true });
|
|
writeFileSync(join(bin, "pnpm"), "x");
|
|
existsPaths.push(bin, join(bin, "pnpm"));
|
|
}
|
|
const tc = discoverToolchain(ctxWith({ PATH: "/usr/bin", HOME: home }, existsPaths));
|
|
assert.ok(tc.pnpm?.includes("v24.21.0"), `expected newest, got ${tc.pnpm}`);
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
test("toolchain: none found → pnpm undefined, never throws", () => {
|
|
const tc = discoverToolchain(ctxWith({ PATH: "/usr/bin" }, []));
|
|
assert.equal(tc.pnpm, undefined);
|
|
assert.equal(tc.pnpmSource, "none");
|
|
});
|
|
|
|
test("toolchain: runPnpm prepends the discovered dir to child PATH", async () => {
|
|
const seen: Array<{ env?: Record<string, string | undefined> }> = [];
|
|
const ctx = ctxWith({ PATH: "/usr/bin" }, []);
|
|
const tc: Toolchain = {
|
|
pnpm: "/opt/found/pnpm",
|
|
pnpmSource: "nvm",
|
|
envPathPrefix: "/opt/found",
|
|
};
|
|
const spawn = async (
|
|
_cmd: string,
|
|
_args: string[],
|
|
opts: { env?: Record<string, string | undefined> },
|
|
) => {
|
|
seen.push({ env: opts.env });
|
|
return { code: 0, signal: null, timedOut: false, stdout: "ok", stderr: "" };
|
|
};
|
|
const { runPnpm } = await import("../src/lib/toolchain.ts");
|
|
await runPnpm({ ...ctx, spawn } as unknown as Ctx, tc, ["--version"], { capture: true });
|
|
assert.equal(seen[0]?.env?.PATH, "/opt/found:/usr/bin");
|
|
});
|
|
|
|
test("toolchain: runPnpm with no pnpm returns 127 without spawning", async () => {
|
|
const ctx = ctxWith({ PATH: "/usr/bin" }, []);
|
|
const tc: Toolchain = { pnpm: undefined, pnpmSource: "none", envPathPrefix: undefined };
|
|
const { runPnpm } = await import("../src/lib/toolchain.ts");
|
|
const r = await runPnpm(ctx, tc, ["install"]);
|
|
assert.equal(r.code, 127);
|
|
});
|