FN-5809: add onboarding skip-flag and env bypass handling

Ensure CLI onboarding auto-launch is bypassed cleanly via explicit flag/env controls.

- Parse and strip global `--skip-onboarding` in CLI argument preprocessing while surfacing a dedicated `skipOnboarding` signal.
- Pass the surfaced skip signal into onboarding auto-launch decisions and preserve distinct reasons (`skip-flag` vs `skip-env`).
- Tighten `FUSION_SKIP_ONBOARDING` parsing to strict truthy values only (`1`, `true`, `yes`, `on`).
- Add focused tests covering bypass reasons, truthy env parsing, global flag stripping behavior, and integration with onboarding gating.
- Add a patch changeset for @runfusion/fusion documenting the onboarding bypass behavior update.

Files changed:
 .changeset/fn-5809-skip-onboarding-bypass.md       |   9 ++
 packages/cli/src/bin.ts                            |  23 +++-
 .../__tests__/onboard-autolaunch-bypass.test.ts    | 140 +++++++++++++++++++++
 packages/cli/src/commands/onboard-autolaunch.ts    |  11 +-
 4 files changed, 173 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-5809
Fusion-Task-Lineage: fc461eab-84df-42dc-924d-9d8cc59502cd
This commit is contained in:
gsxdsm
2026-06-01 14:07:46 -07:00
parent e1a35a364b
commit 38e0422d35
4 changed files with 173 additions and 10 deletions

View File

@@ -419,14 +419,19 @@ Columns: triage, todo, in-progress, in-review, done, archived
Supported file types: png, jpg, gif, webp, txt, log, json, yaml, yml, toml, csv, xml
`.trim();
function extractGlobalProjectFlag(argv: string[]): { cleanedArgs: string[]; projectName?: string } {
export function extractGlobalProjectFlag(argv: string[]): {
cleanedArgs: string[];
projectName?: string;
skipOnboarding: boolean;
} {
const command = argv[0];
if (command === "serve" || command === "daemon") {
return { cleanedArgs: [...argv] };
return { cleanedArgs: [...argv], skipOnboarding: false };
}
const cleanedArgs: string[] = [];
let projectName: string | undefined;
let skipOnboarding = false;
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
@@ -442,10 +447,14 @@ function extractGlobalProjectFlag(argv: string[]): { cleanedArgs: string[]; proj
i++;
continue;
}
if (arg === "--skip-onboarding") {
skipOnboarding = true;
continue;
}
cleanedArgs.push(arg);
}
return { cleanedArgs, projectName };
return { cleanedArgs, projectName, skipOnboarding };
}
function getFlagValue(args: string[], flag: string): string | undefined {
@@ -533,7 +542,7 @@ function readOwnCliVersion(): string | undefined {
}
async function main() {
const { cleanedArgs: args, projectName } = extractGlobalProjectFlag(process.argv.slice(2));
const { cleanedArgs: args, projectName, skipOnboarding } = extractGlobalProjectFlag(process.argv.slice(2));
// Print version and exit before any application imports. This is what the
// dashboard's CLI Binary panel probes via `<bin> --version`; without an
@@ -558,7 +567,7 @@ async function main() {
const command = args[0];
const { maybeAutoLaunchOnboarding } = await import("./commands/onboard-autolaunch.js");
await maybeAutoLaunchOnboarding({ command, args });
await maybeAutoLaunchOnboarding({ command, args, skipOnboarding });
const {
runDashboard,
@@ -1849,4 +1858,6 @@ async function main() {
}
}
await main();
if (process.env.FUSION_CLI_SKIP_MAIN !== "1") {
await main();
}

View File

@@ -0,0 +1,140 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
isTruthyEnvFlag,
maybeAutoLaunchOnboarding,
shouldAutoLaunchOnboarding,
} from "../onboard-autolaunch.js";
afterEach(() => {
vi.restoreAllMocks();
vi.resetModules();
delete process.env.FUSION_CLI_SKIP_MAIN;
});
describe("onboard bypass reasons", () => {
it("uses skip-flag when flag is present", async () => {
expect(
shouldAutoLaunchOnboarding({
command: "task",
args: ["task", "list", "--skip-onboarding"],
centralDbExists: false,
projectInitialized: false,
isTTY: true,
}),
).toEqual({ launch: false, reason: "skip-flag" });
const runOnboard = vi.fn();
await maybeAutoLaunchOnboarding({
command: "task",
args: ["task", "list", "--skip-onboarding"],
isTTY: true,
pathExists: () => false,
runOnboard,
});
expect(runOnboard).not.toHaveBeenCalled();
});
it("uses skip-env when env is truthy", async () => {
expect(
shouldAutoLaunchOnboarding({
command: "task",
args: ["task", "list"],
centralDbExists: false,
projectInitialized: false,
isTTY: true,
env: { FUSION_SKIP_ONBOARDING: "1" },
}),
).toEqual({ launch: false, reason: "skip-env" });
const runOnboard = vi.fn();
await maybeAutoLaunchOnboarding({
command: "task",
args: ["task", "list"],
env: { FUSION_SKIP_ONBOARDING: "1" },
isTTY: true,
pathExists: () => false,
runOnboard,
});
expect(runOnboard).not.toHaveBeenCalled();
});
it("parses strict truthy env values", () => {
for (const value of ["1", "true", "yes", "on", "TRUE", "On"]) {
expect(isTruthyEnvFlag(value)).toBe(true);
}
for (const value of [undefined, "", "0", "false", "no", "off", "maybe"]) {
expect(isTruthyEnvFlag(value)).toBe(false);
}
});
it("does not bypass without flag or env", () => {
expect(
shouldAutoLaunchOnboarding({
command: "task",
args: ["task", "list"],
centralDbExists: false,
projectInitialized: false,
isTTY: true,
}),
).toEqual({ launch: true, reason: "central-db-missing" });
});
});
describe("extractGlobalProjectFlag", () => {
it("strips --skip-onboarding and surfaces skipOnboarding", async () => {
process.env.FUSION_CLI_SKIP_MAIN = "1";
const { extractGlobalProjectFlag } = await import("../../bin.js");
expect(extractGlobalProjectFlag(["task", "list", "--skip-onboarding"])).toEqual({
cleanedArgs: ["task", "list"],
projectName: undefined,
skipOnboarding: true,
});
expect(extractGlobalProjectFlag(["task", "list"])).toEqual({
cleanedArgs: ["task", "list"],
projectName: undefined,
skipOnboarding: false,
});
});
it("does not consume next arg after --skip-onboarding", async () => {
process.env.FUSION_CLI_SKIP_MAIN = "1";
const { extractGlobalProjectFlag } = await import("../../bin.js");
expect(extractGlobalProjectFlag(["task", "--skip-onboarding", "list"]).cleanedArgs).toEqual([
"task",
"list",
]);
});
it("drives skip-flag via surfaced skipOnboarding", async () => {
process.env.FUSION_CLI_SKIP_MAIN = "1";
const { extractGlobalProjectFlag } = await import("../../bin.js");
const parsed = extractGlobalProjectFlag(["task", "list", "--skip-onboarding"]);
const runOnboard = vi.fn();
await maybeAutoLaunchOnboarding({
command: "task",
args: parsed.cleanedArgs,
skipOnboarding: parsed.skipOnboarding,
isTTY: true,
pathExists: () => false,
runOnboard,
});
expect(
shouldAutoLaunchOnboarding({
command: "task",
args: parsed.cleanedArgs,
skipOnboarding: parsed.skipOnboarding,
centralDbExists: false,
projectInitialized: false,
isTTY: true,
}),
).toEqual({ launch: false, reason: "skip-flag" });
expect(runOnboard).not.toHaveBeenCalled();
});
});

View File

@@ -11,6 +11,7 @@ export interface AutoLaunchInput {
projectInitialized: boolean;
isTTY: boolean;
env?: NodeJS.ProcessEnv;
skipOnboarding?: boolean;
}
export interface AutoLaunchDecision {
@@ -42,11 +43,11 @@ export function shouldAutoLaunchOnboarding(input: AutoLaunchInput): AutoLaunchDe
return { launch: false, reason: "non-tty" };
}
if (input.args.includes("--skip-onboarding")) {
if (input.skipOnboarding || input.args.includes("--skip-onboarding")) {
return { launch: false, reason: "skip-flag" };
}
if (isTruthy(env.FUSION_SKIP_ONBOARDING)) {
if (isTruthyEnvFlag(env.FUSION_SKIP_ONBOARDING)) {
return { launch: false, reason: "skip-env" };
}
@@ -61,13 +62,13 @@ export function shouldAutoLaunchOnboarding(input: AutoLaunchInput): AutoLaunchDe
return { launch: true, reason: "central-db-missing" };
}
function isTruthy(value: string | undefined): boolean {
export function isTruthyEnvFlag(value: string | undefined): boolean {
if (value === undefined) {
return false;
}
const normalized = value.trim().toLowerCase();
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
}
interface RunOnboardOptions {
@@ -79,6 +80,7 @@ type RunOnboard = (options?: RunOnboardOptions) => Promise<void> | void;
export interface MaybeAutoLaunchDeps {
command: string;
args: string[];
skipOnboarding?: boolean;
centralDbPath?: string;
projectInitialized?: boolean;
cwd?: string;
@@ -114,6 +116,7 @@ export async function maybeAutoLaunchOnboarding(deps: MaybeAutoLaunchDeps): Prom
projectInitialized,
isTTY,
env,
skipOnboarding: deps.skipOnboarding,
});
if (!decision.launch) {