import { test } from "node:test"; import assert from "node:assert/strict"; import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { installPlan, installStep, NPM_REPOSITORIES, REPOSITORIES, script } from "../tools/runs.ts"; // Defends novox/hq issue 624: lab_run ran `npm ci` at the root of every checkout it installs, and // mesh-tools keeps its package in node-tools/ with no lock file at its root, so every run failed at its // setup, in 9 seconds, before anything was built. function aCheckout(files: Record): string { const root = mkdtempSync(join(tmpdir(), "lab-checkout-")); for (const [path, content] of Object.entries(files)) { mkdirSync(join(root, path, ".."), { recursive: true }); writeFileSync(join(root, path), content); } return root; } test("a checkout with a lock file at its root is installed there", () => { const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}", "node-tools/package.json": "{}" }); assert.deepEqual(installPlan(root), { install: [root], unlocked: [] }); rmSync(root, { recursive: true, force: true }); }); test("a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools", () => { const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}", "Dockerfile": "" }); assert.deepEqual(installPlan(root), { install: [join(root, "node-tools")], unlocked: [] }); rmSync(root, { recursive: true, force: true }); }); test("a package.json with no lock file is named, never installed", () => { const root = aCheckout({ "package.json": "{}" }); assert.deepEqual(installPlan(root), { install: [], unlocked: [root] }); rmSync(root, { recursive: true, force: true }); }); // The shell a run executes follows the same rule: npm is asked where installPlan says, and a checkout // with nothing installable fails the run loudly, naming it. function runStep(root: string, build: boolean): { ok: boolean; said: string; asked: string[] } { const bin = mkdtempSync(join(tmpdir(), "lab-npm-")); const record = join(bin, "asked"); writeFileSync(join(bin, "npm"), `#!/bin/sh\necho "$(pwd) $*" >> ${JSON.stringify(record)}\n`); chmodSync(join(bin, "npm"), 0o755); const ran = spawnSync("bash", ["-c", `set -euo pipefail\n${installStep(root, build)}`], { encoding: "utf8", env: { ...process.env, PATH: `${bin}:${process.env.PATH}` }, }); let asked: string[] = []; try { asked = readFileSync(record, "utf8").trim().split("\n").filter(Boolean); } catch { /* nothing asked */ } rmSync(bin, { recursive: true, force: true }); return { ok: ran.status === 0, said: `${ran.stdout}${ran.stderr}`, asked }; } test("a run installs and builds mesh-tools in node-tools, where its lock file is", () => { const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}" }); const r = runStep(root, true); assert.ok(r.ok, r.said); assert.deepEqual(r.asked, [ `${join(root, "node-tools")} ci --no-audit --no-fund --loglevel=error`, `${join(root, "node-tools")} run build --if-present`, ]); rmSync(root, { recursive: true, force: true }); }); test("a run installs a checkout with its lock file at the root there, and builds only when asked", () => { const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}" }); const r = runStep(root, false); assert.ok(r.ok, r.said); assert.deepEqual(r.asked, [`${root} ci --no-audit --no-fund --loglevel=error`]); rmSync(root, { recursive: true, force: true }); }); test("a run fails at its setup, naming the checkout, when nothing of it can be installed", () => { const layouts: Record[] = [{ "package.json": "{}" }, { "Dockerfile": "" }, { "sub/package.json": "{}" }]; for (const files of layouts) { const root = aCheckout(files); const r = runStep(root, false); assert.equal(r.ok, false, `installed ${JSON.stringify(files)}`); assert.match(r.said, /issue 624/); assert.deepEqual(r.asked, []); rmSync(root, { recursive: true, force: true }); } }); // A run checks out mesh-tools-go too (novox/hq issue 616): genesis builds the Go toolchain from the // repository beside mesh-tools, and a bed that raises genesis serves it. And it tells the beds where the // checkouts are. test("a run checks out mesh-tools-go and says where its checkouts are", () => { assert.ok(REPOSITORIES.includes("mesh-tools-go")); const s = script("/work", "run-x", "http://forge", ["test/integration/genesis-keeps-its-bus.test.ts"], {}); assert.match(s, /novox\/mesh-tools-go\.git' '\/work\/run-x\/mesh-tools-go'/); assert.match(s, /export MESH_LAB_REPOS='\/work\/run-x'/); assert.doesNotMatch(s, /cd '\/work\/run-x'\/\$repo && npm ci/); for (const repo of NPM_REPOSITORIES) assert.ok(s.includes(`'/work/run-x/${repo}'/package-lock.json`), repo); });