mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
fix(install): say the CLI is installed and setup is not complete when rig setup isn't ready (#1547)
When `rig setup` (step 4 of the one-line installer) finishes without being ready, the installer now ends with `INSTALLED, SETUP NOT COMPLETE (exit N).` instead of `FAILED [4/4] … (exit 1)`, which read as "OpenRig didn't install" though the CLI was installed and the compatibility check ran. The summary gives the installed CLI path, the Node/SQLite check result (`passed`, or `SKIPPED (not checked; see above)`), a pointer to setup's own "Some steps need attention" list, with a Fix where setup gives one, or to the error that stopped it, and "Fix those, then run rig setup again; the CLI does not need reinstalling." - Setup's exit status passes through exactly, and setup's own output stays above the summary. - Steps 1-3 keep their `FAILED [n/4]` line, and a ready setup keeps today's closing line. - It names no cause that setup's result doesn't establish: a provider check fails for a missing sign-in but also for a probe that timed out or errored, and the installer sees only setup's exit status, so the summary doesn't say "sign in". - A skipped compatibility check is never reported as passed. Setup's readiness and exit behaviour, provider selection and every other exit status are unchanged, with no extra probe. The README and getting-started are unchanged on purpose: they show the pinned release's installer, which still ends with `FAILED [4/4]`. They move together with the pin, at the first release tag that contains this script. Tests: the wrapper tests cover setup exit 1, 40 and a piped run's 43, unchanged early failures, a skipped check and the ready ending; five setup-boundary cases cover sign-in only, a mixed failure, ready, an unselected provider and an uncertain probe.
This commit is contained in:
1 parent
7b490de5c0
commit
f86a80285a
3 files changed
+118
-3
No files matched your search
@@ -113,3 +113,60 @@ it.each([
|
||||
const result = JSON.parse(logs[0]!) as SetupResult;
|
||||
expect(result.steps.find(step => step.id === "ghostty_install")?.message).toContain(expected);
|
||||
});
|
||||
|
||||
// The installer's step 4 consumes only `rig setup`'s exit status and prints setup's own output. These five
|
||||
// cases pin that boundary: an incomplete setup exits 1 and lists what is left (each with its Fix) under
|
||||
// "Some steps need attention"; a ready one exits 0 with "Setup complete.". A failed provider probe is listed
|
||||
// as "not ready to launch" with the probe's own error, whatever the cause, so the installer names no sign-in.
|
||||
describe("the installer's setup boundary", () => {
|
||||
const run = async (exec: (cmd: string) => string, flags: string[] = []) => {
|
||||
process.exitCode = undefined;
|
||||
vi.stubGlobal("fetch", vi.fn(async () => new Response(null, { status: 503 })));
|
||||
const logs: string[] = [];
|
||||
vi.spyOn(console, "log").mockImplementation((...args) => { logs.push(args.map(String).join(" ")); });
|
||||
const deps: SetupDeps = { platform: "darwin", env: {}, exec: vi.fn(exec), readFile: () => null, exists: () => false, writeFile: vi.fn(), mkdirp: vi.fn() };
|
||||
await new Command().addCommand(setupCommand(deps)).parseAsync(["node", "rig", "setup", ...flags]);
|
||||
const output = logs.join("\n");
|
||||
return { exitCode: process.exitCode, output, attention: output.slice(output.indexOf("Some steps need attention:")) };
|
||||
};
|
||||
const ok = (cmd: string) => cmd === "cmux capabilities --json" ? '{"capabilities":[]}' : "available";
|
||||
|
||||
it("a sign-in only: exit 1, the provider listed with its Fix", async () => {
|
||||
const r = await run((cmd) => { if (cmd === "claude auth status") throw new Error("Not logged in"); return ok(cmd); });
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.attention).toContain("claude_auth: Claude Code is installed but not ready to launch: Not logged in");
|
||||
expect(r.attention).toContain("Fix: Run `claude auth login`");
|
||||
});
|
||||
|
||||
it("a real failure beside a sign-in: exit 1, both listed", async () => {
|
||||
const r = await run((cmd) => {
|
||||
if (cmd === "claude auth status") throw new Error("Not logged in");
|
||||
if (cmd === "codex --version" || cmd === "npm install -g @openai/codex") throw new Error("npm ERR! network");
|
||||
return ok(cmd);
|
||||
});
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.attention).toContain("claude_auth:");
|
||||
expect(r.attention).toContain("codex_install:");
|
||||
});
|
||||
|
||||
it("ready: exit 0 and Setup complete", async () => {
|
||||
const r = await run(ok);
|
||||
expect(r.exitCode).toBeUndefined();
|
||||
expect(r.output).toContain("Setup complete.");
|
||||
expect(r.output).not.toContain("Some steps need attention:");
|
||||
});
|
||||
|
||||
it("an unselected provider is not a failure: exit 0 with only the chosen provider checked", async () => {
|
||||
const r = await run((cmd) => { if (cmd.startsWith("codex")) throw new Error("codex absent"); return ok(cmd); }, ["--providers", "claude"]);
|
||||
expect(r.exitCode).toBeUndefined();
|
||||
expect(r.output).toContain("Setup complete.");
|
||||
expect(r.output).toContain("codex_auth: Not selected");
|
||||
});
|
||||
|
||||
it("an uncertain probe is listed with its own error, not as a confirmed missing login", async () => {
|
||||
const r = await run((cmd) => { if (cmd === "claude auth status") throw new Error("Command timed out after 10000ms"); return ok(cmd); });
|
||||
expect(r.exitCode).toBe(1);
|
||||
expect(r.attention).toContain("claude_auth: Claude Code is installed but not ready to launch: Command timed out after 10000ms");
|
||||
expect(r.attention).not.toMatch(/not logged in/i);
|
||||
});
|
||||
});
|
||||
+21
-1
@@ -21,6 +21,20 @@ install_step() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Step 4 is the one failure after which OpenRig itself is installed: say so, and that setup is
|
||||
# not complete, keeping setup's own status. Its output above names what is left; this summary
|
||||
# names nothing it cannot see (a failed provider check is not proof of a missing sign-in).
|
||||
install_setup_incomplete() {
|
||||
install_exit=$1
|
||||
printf '\nINSTALLED, SETUP NOT COMPLETE (exit %s).\n' "$install_exit" >&2
|
||||
printf 'The OpenRig CLI is installed: %s\n' "$install_rig" >&2
|
||||
printf 'Node/SQLite compatibility check: %s.\n' "$install_abi" >&2
|
||||
printf '%s\n' 'rig setup did not finish ready. Its output above lists what is left under' \
|
||||
'"Some steps need attention", with a Fix where setup gives one, or shows the error that stopped it.' \
|
||||
'Fix those, then run rig setup again; the CLI does not need reinstalling.' >&2
|
||||
exit "$install_exit"
|
||||
}
|
||||
|
||||
install_main() {
|
||||
case "$#:$1" in
|
||||
0:|1:--dry-run) ;;
|
||||
@@ -84,11 +98,17 @@ install_main() {
|
||||
if [ -f "$install_checker" ]; then
|
||||
install_step 2/4 node "$install_checker"
|
||||
printf '\n%s\n' 'Node/SQLite compatibility check passed.'
|
||||
install_abi=passed
|
||||
else
|
||||
printf '\nNode/SQLite compatibility check SKIPPED: installed checker not found at %s\n' "$install_checker" >&2
|
||||
install_abi='SKIPPED (not checked; see above)'
|
||||
fi
|
||||
install_step 3/4 "$install_rig" setup --dry-run
|
||||
install_step 4/4 "$install_rig" setup
|
||||
printf '\n[%s] %s\n' 4/4 "$install_rig setup"
|
||||
# Keep native output and the actual status, as install_step does.
|
||||
if "$install_rig" setup; then :; else
|
||||
install_setup_incomplete "$?"
|
||||
fi
|
||||
printf '\n%s\n' 'Follow the next steps printed by rig setup: start the daemon, open the OpenRig view, then tell the operator what you want to build.'
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@ for (const [variable, code, step, command] of [
|
||||
["ROOT_EXIT", 45, 2, "npm root -g"],
|
||||
["ABI_EXIT", 44, 2, "check-abi.mjs"],
|
||||
["PREVIEW_EXIT", 39, 3, "setup --dry-run"],
|
||||
["SETUP_EXIT", 40, 4, "setup"],
|
||||
]) test(`${command} failure preserves status and native output`, t => {
|
||||
const f = fixture(t, { [variable]: String(code) });
|
||||
const r = f.run();
|
||||
@@ -170,6 +169,45 @@ test("the actual HTTP download piped to sh preserves setup failure", async t =>
|
||||
assert.equal(status, 43, stderr);
|
||||
assert.match(stdout, /setup native output/);
|
||||
assert.match(stderr, /setup native error/);
|
||||
assert.match(stderr, /FAILED \[4\/4\].*exit 43/);
|
||||
assert.match(stderr, /INSTALLED, SETUP NOT COMPLETE \(exit 43\)/);
|
||||
assert.ok(!stderr.includes("FAILED [4/4]"), stderr);
|
||||
assert.ok(!f.calls().includes("STALE"));
|
||||
});
|
||||
|
||||
// Step 4 is the one failure after which OpenRig itself is installed. The summary says so and that
|
||||
// setup is not complete, keeps setup's own status, and names nothing setup's result doesn't establish:
|
||||
// every incomplete setup (a sign-in still needed, a real failure beside it, an uncertain provider
|
||||
// probe, a crash) reaches the wrapper only as a non-zero status, so it is never called a sign-in here.
|
||||
for (const code of [1, 40]) test(`a rig setup exit ${code} reports the CLI installed and setup not complete, with status ${code}`, t => {
|
||||
const f = fixture(t, { SETUP_EXIT: String(code) });
|
||||
const r = f.run();
|
||||
assert.equal(r.status, code, r.stderr);
|
||||
assert.ok(f.calls().includes("installed:setup"));
|
||||
assert.match(r.stdout, /setup native output/);
|
||||
assert.match(r.stderr, /setup native error/);
|
||||
assert.ok(!r.stderr.includes("FAILED"), r.stderr);
|
||||
assert.ok(r.stderr.includes(`INSTALLED, SETUP NOT COMPLETE (exit ${code})`), r.stderr);
|
||||
assert.ok(r.stderr.includes(`The OpenRig CLI is installed: ${path.join(f.prefix, "bin", "rig")}`), r.stderr);
|
||||
assert.match(r.stderr, /Node\/SQLite compatibility check: passed\./);
|
||||
assert.match(r.stderr, /"Some steps need attention", with a Fix where setup gives one, or shows the error that stopped it/);
|
||||
assert.match(r.stderr, /run rig setup again; the CLI does not need reinstalling/);
|
||||
assert.ok(!/sign[ -]?in|login/i.test(r.stderr.slice(r.stderr.indexOf("INSTALLED, SETUP NOT COMPLETE"))), "the summary claims no sign-in");
|
||||
assert.ok(!r.stdout.includes("Follow the next steps"));
|
||||
});
|
||||
|
||||
test("an incomplete setup after a skipped compatibility check says the check was skipped, not passed", t => {
|
||||
const f = fixture(t, { SETUP_EXIT: "1" });
|
||||
fs.unlinkSync(f.checker);
|
||||
const r = f.run();
|
||||
assert.equal(r.status, 1, r.stderr);
|
||||
assert.match(r.stderr, /Node\/SQLite compatibility check: SKIPPED \(not checked; see above\)\./);
|
||||
assert.ok(!r.stderr.includes("compatibility check: passed"), r.stderr);
|
||||
});
|
||||
|
||||
test("a setup that finishes ready keeps today's closing line and no summary", t => {
|
||||
const f = fixture(t);
|
||||
const r = f.run();
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
assert.match(r.stdout, /Follow the next steps printed by rig setup/);
|
||||
assert.ok(!r.stderr.includes("SETUP NOT COMPLETE"), r.stderr);
|
||||
});
|
||||
Reference in new issue
Block a user