diff --git a/src/cli.ts b/src/cli.ts index ae37e4d..82d9d69 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1129,13 +1129,25 @@ function nativeProofConfigFromModule(loaded: unknown): NativeProofConfig | undef return isNativeProofConfig(current) ? current : undefined; } +/** + * The selection the preflight resolves the project with: CLI flags first, then the same + * env vars the runner itself reads (PLATFORM / NATIVEPROOF_PROJECT). Without the env + * fallback, `PLATFORM=ios nativeproof` preflighted the android project (wrong Appium + * driver ensured, macOS guard skipped) and then ran the ios one. + */ +export function runSelection(args: CliArgs, env: NodeJS.ProcessEnv = process.env): RunnerEnv { + const selection: RunnerEnv = {}; + const platform = args.platform ?? env.PLATFORM; + const project = args.project ?? env.NATIVEPROOF_PROJECT; + if (platform) selection.platform = platform; + if (project) selection.project = project; + return selection; +} + async function runTests(args: CliArgs): Promise { const { wdioConfig, configPath, extraEnv } = resolveRunner(args); const userConfig = await loadNativeProofConfig(configPath); - const selection: RunnerEnv = {}; - if (args.platform) selection.platform = args.platform; - if (args.project) selection.project = args.project; - const project = resolveProject(userConfig, selection); + const project = resolveProject(userConfig, runSelection(args)); const appium = await ensureAppium(userConfig.appium, args.startAppium, project.platform); try { return await new Promise((resolve, reject) => { diff --git a/src/config.ts b/src/config.ts index a08a582..06001b7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -181,7 +181,7 @@ export interface RunnerEnv { spec?: string; } -/** Pick the project by explicit name, else by platform, else the first one. */ +/** Pick the project by explicit name, else by platform (loudly failing on no match), else the first one. */ export function resolveProject(config: RunnerConfig, env: RunnerEnv = {}): DeviceProject { if (env.project) { const named = config.projects.find((project) => project.name === env.project); @@ -191,6 +191,12 @@ export function resolveProject(config: RunnerConfig, env: RunnerEnv = {}): Devic if (env.platform) { const byPlatform = config.projects.find((project) => project.platform === env.platform); if (byPlatform) return byPlatform; + // Falling back to projects[0] here silently ran the WRONG platform: `--ios` with an + // android-only config did an android run with android evidence and zero warning. + const available = config.projects.map((project) => `${project.name} (${project.platform})`).join(", "); + throw new Error( + `nativeproof: no ${env.platform} project in nativeproof.config.ts — available: ${available}`, + ); } const first = config.projects[0]; if (!first) throw new Error("nativeproof: config has no `projects`"); diff --git a/test/cli.test.ts b/test/cli.test.ts index 09dd724..9430c36 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -17,6 +17,7 @@ import { onboardCommand, parseArgs, resolveRunner, + runSelection, type ScaffoldIo, scaffold, scaffoldFiles, @@ -649,3 +650,12 @@ test("updateConfigAppPath survives comments containing apostrophes and braces", assert.match(updated.slice(androidAt), /"appium:app": "\.\/old\.apk"/); // android untouched assert.doesNotMatch(updated.slice(androidAt), /New\.app/); }); + +test("runSelection falls back to the env vars the runner itself reads", () => { + const args = parseArgs([]); + // Flags win; without them, PLATFORM/NATIVEPROOF_PROJECT steer the preflight exactly + // like they steer the runner — previously the CLI preflighted projects[0] instead. + assert.deepEqual(runSelection(args, { PLATFORM: "ios" }), { platform: "ios" }); + assert.deepEqual(runSelection(args, { NATIVEPROOF_PROJECT: "beta" }), { project: "beta" }); + assert.deepEqual(runSelection(parseArgs(["--android"]), { PLATFORM: "ios" }), { platform: "android" }); +}); diff --git a/test/config.test.ts b/test/config.test.ts index 99c90d9..e09a68b 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -214,3 +214,11 @@ test("findConfigFile locates nativeproof.config.* via the injected exists check" null, ); }); + +test("resolveProject errors when an explicit platform has no matching project", () => { + // Falling back to projects[0] silently ran the wrong platform for `--ios`. + assert.throws( + () => resolveProject({ projects }, { platform: "ios2" }), + /no ios2 project in nativeproof\.config\.ts — available: .*android/, + ); +});