(opts: InitOptions, deps: InitDeps = {})
| 157 | // --------------------------------------------------------------------------- |
| 158 | |
| 159 | export async function runInit(opts: InitOptions, deps: InitDeps = {}): Promise<void> { |
| 160 | const stderrFn = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 161 | const out = new Output(opts.output, { stdout: deps.stdout, stderr: deps.stderr }); |
| 162 | |
| 163 | // ------------------------------------------------------------------------- |
| 164 | // Fix 5: emit conflict warning when both --agent and --no-agent were given |
| 165 | // ------------------------------------------------------------------------- |
| 166 | if (opts.rawArgConflict) { |
| 167 | const effectiveLabel = opts.noAgent ? '--no-agent' : `--agent ${opts.agent}`; |
| 168 | stderrFn( |
| 169 | `[warn] both --no-agent and --agent supplied; using ${effectiveLabel} (last flag wins)`, |
| 170 | ); |
| 171 | } |
| 172 | |
| 173 | // ------------------------------------------------------------------------- |
| 174 | // Non-interactive guard: no TTY + no key source → exit 5 immediately |
| 175 | // ------------------------------------------------------------------------- |
| 176 | const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY); |
| 177 | const hasKeySource = Boolean(opts.apiKey) || opts.fromEnv; |
| 178 | // Non-interactive guard: no TTY + no key source → exit 5. Skipped under |
| 179 | // --dry-run, which is documented to work without credentials or network. |
| 180 | if (!isTTY && !hasKeySource && !opts.dryRun) { |
| 181 | throw new CLIError( |
| 182 | 'No API key available in non-interactive mode. ' + |
| 183 | 'Pass --api-key <key>, --from-env (reads TESTSPRITE_API_KEY), or run interactively.', |
| 184 | 5, |
| 185 | ); |
| 186 | } |
| 187 | // JSON-output guard: an interactive secret prompt writes to stdout and would |
| 188 | // corrupt init's single-JSON-object output contract. In --output json mode |
| 189 | // require a non-interactive key source. Skipped under --dry-run (never prompts). |
| 190 | if (opts.output === 'json' && !hasKeySource && !opts.dryRun) { |
| 191 | throw new CLIError( |
| 192 | 'Interactive API-key prompt is unavailable in --output json mode (it would corrupt JSON stdout). ' + |
| 193 | 'Pass --api-key <key> or --from-env.', |
| 194 | 5, |
| 195 | ); |
| 196 | } |
| 197 | |
| 198 | // ------------------------------------------------------------------------- |
| 199 | // Dry-run: zero network + zero FS writes; print preview only |
| 200 | // ------------------------------------------------------------------------- |
| 201 | if (opts.dryRun) { |
| 202 | stderrFn('[dry-run] no writes or network calls — preview only'); |
| 203 | stderrFn( |
| 204 | `[dry-run] would configure profile="${opts.profile}" (key source: ${ |
| 205 | opts.apiKey ? 'flag' : opts.fromEnv ? 'env' : 'prompt' |
| 206 | })`, |
| 207 | ); |
| 208 | |
| 209 | const agentTarget = opts.noAgent ? null : opts.agent; |
| 210 | |
| 211 | if (!opts.noAgent) { |
| 212 | // Delegate to runInstall's own dry-run for the file-listing preview. |
| 213 | // runInstall prints the would-write lines itself under dryRun. |
| 214 | await runInstall( |
| 215 | { |
| 216 | ...opts, |
no test coverage detected