import { expect, test } from "bun:test"; import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { needsVersionPreflight, preflightVersion, resolveInstalledVersion, } from "../../lib/version-update"; const updaterUrl = new URL("../../lib/version-update.ts", import.meta.url).href; const entryUrl = new URL("../../index.ts", import.meta.url).href; const mainUrl = new URL("../../command/main.ts", import.meta.url).href; const contextUrl = new URL("../../lib/context.ts", import.meta.url).href; const errorUrl = new URL("../../lib/error.ts", import.meta.url).href; test("replayed command receives parent signals and preserves its resulting status", async () => { const cwd = await mkdtemp(join(tmpdir(), "kuber-version-signal-")); try { const executable = join(cwd, "kuber"); await writeFile( executable, `#!${process.execPath}\nprocess.on('SIGTERM', () => process.exit(23)); console.log('ready'); setInterval(() => {}, 1000);`, ); await chmod(executable, 0o755); const script = `const {rerunCommand,propagateCommandExit} = await import(${JSON.stringify(updaterUrl)}); propagateCommandExit(await rerunCommand(${JSON.stringify(executable)},[]));`; const child = Bun.spawn([process.execPath, "-e", script], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe", }); const timer = setTimeout(() => child.kill("SIGKILL"), 5_000); try { const reader = child.stdout.getReader(); const first = await reader.read(); expect(new TextDecoder().decode(first.value)).toContain("ready"); child.kill("SIGTERM"); expect(await child.exited).toBe(23); expect(await new Response(child.stderr).text()).toBe(""); reader.releaseLock(); } finally { clearTimeout(timer); } } finally { await rm(cwd, { recursive: true, force: true }); } }, 10_000); test("packaged CLI preflights, installs and replays before importing a user config", async () => { const cwd = await mkdtemp(join(tmpdir(), "kuber-packaged-version-")); try { const bundle = join(cwd, "index.js"); const built = await Bun.build({ entrypoints: [new URL("../../index.ts", import.meta.url).pathname], target: "bun", outdir: cwd, }); expect(built.success).toBe(true); const executable = join(cwd, "kuber"); await writeFile( executable, `#!${process.execPath}\nif (process.argv[2] === '--version') {console.log('1.0.0');} else {console.log(JSON.stringify({args:process.argv.slice(2),marker:process.env.KUBER_VERSION_REEXEC}));process.exitCode=19;}`, ); await chmod(executable, 0o755); const preload = join(cwd, "preload.ts"); await writeFile( preload, ` import {mock} from 'bun:test'; import {spawn as realSpawn} from 'node:child_process'; const spawn = realSpawn; mock.module('node:child_process', () => ({spawn: (exe,args,options) => { if (args.join(' ') === 'pm bin -g') return spawn(process.execPath,['-e',${JSON.stringify(`console.log(${JSON.stringify(cwd)})`)}],options); return spawn(exe,args,options); }})); const originalSpawn = Bun.spawn; Bun.spawn = (args,options) => { if (args[1] === 'i' && args[2] === '-g') {console.error('INSTALL:' + args.at(-1)); return {exited:Promise.resolve(0),kill(){}};} return originalSpawn(args,options); }; globalThis.fetch = async (url,options) => { console.error('HEALTH:' + url + ':' + options.method + ':' + !!options.headers); return new Response('',{headers:{'X-Kuber-Version':'1.0.0'}}); }; `, ); await writeFile( join(cwd, "custom.ts"), "throw Error('user config ran before replay');", ); const child = Bun.spawn( [ process.execPath, "--preload", preload, bundle, "--config", "custom.ts", "fuck", ], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe", env: { ...process.env, NODE_ENV: "production", KUBER_VERSION_REEXEC: "", }, }, ); const [code, out, err] = await Promise.all([ child.exited, new Response(child.stdout).text(), new Response(child.stderr).text(), ]); expect({ code, out, err }).toMatchObject({ code: 19 }); expect(JSON.parse(out)).toEqual({ args: ["--config", "custom.ts", "fuck"], marker: "1", }); expect(err).toBe( "HEALTH:https://kuber.astrxl.dev/api/v2/health:GET:false\nINSTALL:@dmgnr/kuber@1.0.0\n\x1b[90m+ Updated to 1.0.0\x1b[0m\n", ); } finally { await rm(cwd, { recursive: true, force: true }); } }, 20_000); test("offline and informational commands skip the health check", () => { for (const args of [ [], ["complete"], ["--version"], ["up", "-h"], ["users", "--help"], ["exec", "-v"], ["export"], ["add"], ["db", "ls"], ["s3", "ls"], ["ui"], ]) expect(needsVersionPreflight(args)).toBe(false); for (const args of [ ["login"], ["init"], ["exec"], ["ci"], ["up"], ["down"], ["trust"], ["users"], ["--config", "custom.ts", "fuck"], ]) expect(needsVersionPreflight(args)).toBe(true); }); test("preflight has a bounded deadline even if a fetch seam ignores abort", async () => { let signal: AbortSignal | null | undefined; expect( await preflightVersion(["up"], { runner: async () => { throw Error("unexpected install"); }, fetch: ((_url: unknown, options: RequestInit) => { signal = options.signal; return new Promise(() => {}); }) as typeof fetch, timeoutMs: 5, }), ).toBeUndefined(); expect(signal?.aborted).toBe(true); }); test("global resolver uses Bun's configured bin and verifies the exact version", async () => { for (const reported of [undefined, "1.0.0", "2.7.0+other", "2.7.0"]) { const calls: string[][] = []; const result = await resolveInstalledVersion("2.7.0", async (args) => { calls.push(args); return calls.length === 1 ? "/custom/global/bin" : reported; }); expect(calls).toEqual([ [process.execPath, "pm", "bin", "-g"], ["/custom/global/bin/kuber", "--version"], ]); expect(result).toBe( reported === "2.7.0" ? "/custom/global/bin/kuber" : undefined, ); } }); test.each([ "success", "older", "metadata", "failure", "rejection", "wrong executable", "spawn failure", "equal", "invalid", "absent", "unavailable", "marker", "signal", ])( "startup synchronization: %s", async (scenario) => { const cwd = await mkdtemp(join(tmpdir(), "kuber-version-process-")); const executable = join(cwd, "kuber"); const version = scenario === "older" ? "1.0.0" : scenario === "metadata" ? "2.6.1+server" : scenario === "equal" ? "2.6.1" : scenario === "invalid" ? "v2.7.0" : "2.7.0"; const success = ["success", "older", "metadata", "signal"].includes( scenario, ); try { await writeFile( executable, `#!${process.execPath}\n const { preflightVersion } = await import(${JSON.stringify(updaterUrl)}); await preflightVersion(process.argv.slice(2), { runner: async () => { throw Error('recursive install'); } }); console.log('child:' + JSON.stringify({args:process.argv.slice(2),cwd:process.cwd(),marker:process.env.KUBER_VERSION_REEXEC,path:process.env.PATH,custom:process.env.KUBER_TEST_CUSTOM})); ${scenario === "signal" ? "process.kill(process.pid, 'SIGTERM')" : "process.exitCode = 17"}; `, ); await chmod(executable, 0o755); const script = ` import { mock } from 'bun:test'; const original = await import(${JSON.stringify(updaterUrl)}); const preflight = original.preflightVersion; mock.module(${JSON.stringify(updaterUrl)}, () => ({ ...original, preflightVersion: args => preflight(args, { currentVersion: '2.6.1', fetch: async (url, options) => { console.log('health:' + url + ':' + options.method + ':' + !!options.headers); if (${JSON.stringify(scenario)} === 'unavailable') throw Error('offline'); return new Response('', {headers:${scenario === "absent" ? "{}" : `{'X-Kuber-Version':${JSON.stringify(version)}}`}}); }, runner: async version => { console.log('install:' + version); ${scenario === "rejection" ? "throw Error('failed')" : `return ${scenario !== "failure"}`}; }, resolveExecutable: async version => {console.log('verify:' + version); return ${scenario === "wrong executable" ? "undefined" : JSON.stringify(scenario === "spawn failure" ? join(cwd, "missing") : executable)};}, }) })); mock.module(${JSON.stringify(mainUrl)}, () => ({main:{run:({rawArgs}) => {console.log('mutate:' + JSON.stringify(rawArgs)); process.exitCode = 7;}},initializeCompletion:async () => {}})); mock.module(${JSON.stringify(contextUrl)}, () => ({provideContext:async (run, config) => {console.log('context:' + config); await run();}})); mock.module(${JSON.stringify(errorUrl)}, () => ({formatUnknownError:String,wrapCommandErrors:() => {}})); process.argv = [process.execPath, 'test-cli.js', '--config', 'custom.ts', 'fuck']; await import(${JSON.stringify(entryUrl)}); `; const child = Bun.spawn([process.execPath, "-e", script], { cwd, stdin: "ignore", stdout: "pipe", stderr: "pipe", env: { ...process.env, KUBER_TEST_CUSTOM: "preserved", KUBER_VERSION_REEXEC: scenario === "marker" ? "1" : "", }, }); const [code, out, err] = await Promise.all([ child.exited, new Response(child.stdout).text(), new Response(child.stderr).text(), ]); if (success) { expect(code).toBe(scenario === "signal" ? 143 : 17); expect(out).not.toContain("context:"); expect(out).not.toContain("mutate:"); expect(out.split("child:")).toHaveLength(2); const payload = JSON.parse(out.split("child:")[1]!); expect(payload).toEqual({ args: ["--config", "custom.ts", "fuck"], cwd, marker: "1", path: process.env.PATH, custom: "preserved", }); expect(err).toBe(`\x1b[90m+ Updated to ${version}\x1b[0m\n`); } else { expect({ code, out, err }).toMatchObject({ code: 7 }); expect(out.split("mutate:")).toHaveLength(2); expect(out).toContain('context:custom.ts\nmutate:["rollback"]'); expect(out).not.toContain("child:"); expect(err).toBe( [ "failure", "rejection", "wrong executable", "spawn failure", ].includes(scenario) ? `\x1b[90m+ New version available: ${version}\x1b[0m\n` : "", ); } expect(out.split("install:")).toHaveLength( success || [ "failure", "rejection", "wrong executable", "spawn failure", ].includes(scenario) ? 2 : 1, ); if (scenario !== "marker") expect(out).toStartWith( "health:https://kuber.astrxl.dev/api/v2/health:GET:false\n", ); } finally { await rm(cwd, { recursive: true, force: true }); } }, 10_000, );