(deps: ProjectDeps = {})
| 317 | } |
| 318 | |
| 319 | export function createProjectCommand(deps: ProjectDeps = {}): Command { |
| 320 | const project = new Command('project').description('Manage TestSprite projects'); |
| 321 | |
| 322 | project |
| 323 | .command('list') |
| 324 | .description( |
| 325 | 'List projects visible to the API key\n' + |
| 326 | '\nExit codes:\n' + |
| 327 | ' 0 success\n' + |
| 328 | ' 3 auth error\n' + |
| 329 | ' 5 validation error (e.g., bad --page-size)\n' + |
| 330 | ' 10 transport/network failure (UNAVAILABLE) — retry the command', |
| 331 | ) |
| 332 | .option('--page-size <n>', 'service page-size hint (1-100, default 25)') |
| 333 | .option('--starting-token <token>', 'opaque cursor from a previous list response') |
| 334 | .option('--max-items <n>', 'stop after this many items across auto-paged pages') |
| 335 | .addHelpText('after', GLOBAL_OPTS_HINT) |
| 336 | .action(async (cmdOpts: ListFlagOpts, command: Command) => { |
| 337 | // Don't parse numeric flags via Commander — its parser throws a |
| 338 | // plain `Error`, which `index.ts` maps to exit code 1. Local |
| 339 | // validation lives in `runList → validatePaginationFlags`, which |
| 340 | // raises a typed `ApiError(VALIDATION_ERROR)` and surfaces with |
| 341 | // the contract-mandated exit code 5. |
| 342 | await runList( |
| 343 | { |
| 344 | ...resolveCommonOptions(command), |
| 345 | pageSize: parseFlag(cmdOpts.pageSize, 'page-size'), |
| 346 | startingToken: cmdOpts.startingToken, |
| 347 | maxItems: parseFlag(cmdOpts.maxItems, 'max-items'), |
| 348 | }, |
| 349 | deps, |
| 350 | ); |
| 351 | }); |
| 352 | |
| 353 | project |
| 354 | .command('get <project-id>') |
| 355 | .description('Get a project by id') |
| 356 | .addHelpText('after', GLOBAL_OPTS_HINT) |
| 357 | .action(async (projectId: string, _cmdOpts, command: Command) => { |
| 358 | await runGet({ ...resolveCommonOptions(command), projectId }, deps); |
| 359 | }); |
| 360 | |
| 361 | project |
| 362 | .command('create') |
| 363 | .description('Create a new project') |
| 364 | .option('--type <frontend|backend>', 'project type (required)') |
| 365 | .option('--name <name>', 'project name (required)') |
| 366 | .option('--url <url>', 'target URL (required for frontend)') |
| 367 | .option('--description <text>', 'optional human description') |
| 368 | .option('--username <user>', 'optional auth username') |
| 369 | .option('--password <pw>', 'optional auth password (use --password-file for non-interactive)') |
| 370 | .option('--password-file <path>', 'read password from file instead of inline flag') |
| 371 | .option('--instruction <text>', 'optional FE plan-gen instruction hint') |
| 372 | .option( |
| 373 | '--idempotency-key <token>', |
| 374 | 'opaque idempotency token. Defaults to a UUIDv4 minted per invocation.', |
| 375 | ) |
| 376 | .addHelpText('after', GLOBAL_OPTS_HINT) |
no test coverage detected