(
opts: CodePutOptions,
deps: TestDeps = {},
)
| 3327 | * sense (v3 → v4 bump). Matches piece-2's pattern. |
| 3328 | */ |
| 3329 | export async function runCodePut( |
| 3330 | opts: CodePutOptions, |
| 3331 | deps: TestDeps = {}, |
| 3332 | ): Promise<CliPutTestCodeResponse> { |
| 3333 | assertIdempotencyKey(opts.idempotencyKey); |
| 3334 | requireNonEmpty('test-id', opts.testId); |
| 3335 | requireNonEmpty('code-file', opts.codeFile); |
| 3336 | |
| 3337 | if (opts.expectedVersion !== undefined && opts.force === true) { |
| 3338 | throw localValidationError( |
| 3339 | 'expected-version', |
| 3340 | 'is mutually exclusive with --force; pass one or the other (or neither for auto-fetch)', |
| 3341 | ); |
| 3342 | } |
| 3343 | if (opts.language !== undefined && !CODE_PUT_LANGUAGES.includes(opts.language)) { |
| 3344 | throw localValidationError('language', `must be one of: ${CODE_PUT_LANGUAGES.join(', ')}`, [ |
| 3345 | ...CODE_PUT_LANGUAGES, |
| 3346 | ]); |
| 3347 | } |
| 3348 | |
| 3349 | const code = opts.dryRun ? DRY_RUN_PLACEHOLDER_CODE : readCodeFileGuarded(opts.codeFile); |
| 3350 | |
| 3351 | const idempotencyKey = opts.idempotencyKey ?? `cli-code-put-${randomUUID()}`; |
| 3352 | if (opts.idempotencyKey === undefined && (opts.output === 'json' || opts.verbose || opts.debug)) { |
| 3353 | const stderr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 3354 | stderr(`idempotency-key: ${idempotencyKey}`); |
| 3355 | } |
| 3356 | |
| 3357 | // Resolve the If-Match header. Three cases: |
| 3358 | // 1. --force → '*' (skip etag check; audit-logged) |
| 3359 | // 2. --expected-version <v> → that string verbatim |
| 3360 | // 3. neither → auto-fetch via GET /tests/{id}/code, use |
| 3361 | // returned codeVersion. Tell the user (stderr) |
| 3362 | // so an operator watching can see the race |
| 3363 | // window we just opened. |
| 3364 | const client = makeClient(opts, deps); |
| 3365 | let ifMatch: string; |
| 3366 | if (opts.force === true) { |
| 3367 | ifMatch = '*'; |
| 3368 | } else if (opts.expectedVersion !== undefined) { |
| 3369 | requireNonEmpty('expected-version', opts.expectedVersion); |
| 3370 | ifMatch = opts.expectedVersion; |
| 3371 | } else { |
| 3372 | const fetched = await client.get<CliTestCode>(`/tests/${encodeURIComponent(opts.testId)}/code`); |
| 3373 | const cv = fetched.codeVersion; |
| 3374 | if (cv === null || cv === undefined) { |
| 3375 | // Server hasn't stamped a codeVersion yet (legacy row). Send `*` |
| 3376 | // so the backend's force path applies the bump. Audit will mark |
| 3377 | // force: true — visible signal that our auto-fetch hit a |
| 3378 | // legacy row, not a concurrent-write race. |
| 3379 | ifMatch = '*'; |
| 3380 | const stderr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 3381 | stderr( |
| 3382 | `auto-fetched codeVersion=null on legacy row; using If-Match: * for this put. Pass --expected-version explicitly to avoid this fallback.`, |
| 3383 | ); |
| 3384 | } else { |
| 3385 | ifMatch = cv; |
| 3386 | const stderr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
no test coverage detected