poe-code 3.0.375 → 3.0.377-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ export interface CLIControls {
13
13
  export interface RunCLIOptions<TServices extends object = Record<string, unknown>> {
14
14
  apiVersion?: string;
15
15
  approvals?: boolean;
16
+ argv?: readonly string[];
16
17
  casing?: Casing;
17
18
  controls?: CLIControls;
18
19
  fetch?: typeof globalThis.fetch;
@@ -3189,15 +3189,16 @@ function configureCommanderSuggestionOutput(command) {
3189
3189
  }
3190
3190
  export async function runCLI(roots, options = {}) {
3191
3191
  enableSourceMaps();
3192
- const normalizedRoot = normalizeRoots(roots, process.argv);
3192
+ const argv = [...(options.argv ?? process.argv)];
3193
+ const normalizedRoot = normalizeRoots(roots, argv);
3193
3194
  const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
3194
3195
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
3195
3196
  const casing = options.casing ?? "kebab";
3196
3197
  const services = (options.services ?? {});
3197
3198
  const runtimeOptions = options.humanInLoop ?? {};
3198
3199
  const runtimeFetch = options.fetch ?? globalThis.fetch;
3199
- const version = options.version ?? findEntrypointPackageMetadata(process.argv[1])?.version;
3200
- const rootUsageName = options.rootUsageName ?? inferProgramName(process.argv);
3200
+ const version = options.version ?? findEntrypointPackageMetadata(argv[1])?.version;
3201
+ const rootUsageName = options.rootUsageName ?? inferProgramName(argv);
3201
3202
  const controls = resolveCLIControls(options.controls);
3202
3203
  const servicesWithBuiltIns = {
3203
3204
  ...services,
@@ -3208,8 +3209,8 @@ export async function runCLI(roots, options = {}) {
3208
3209
  apiVersion: options.apiVersion
3209
3210
  };
3210
3211
  validateServices(services);
3211
- if (hasHelpFlag(process.argv)) {
3212
- await renderGeneratedHelp(root, process.argv, { ...options, version });
3212
+ if (hasHelpFlag(argv)) {
3213
+ await renderGeneratedHelp(root, argv, { ...options, version });
3213
3214
  return;
3214
3215
  }
3215
3216
  const program = new CommanderCommand();
@@ -3250,7 +3251,7 @@ export async function runCLI(roots, options = {}) {
3250
3251
  addCommanderChild(program, command, isDefaultChild, rootChildNames);
3251
3252
  }
3252
3253
  configureCommanderSuggestionOutput(program);
3253
- const unknownCommand = findUnknownCommanderCommand(program, process.argv);
3254
+ const unknownCommand = findUnknownCommanderCommand(program, argv);
3254
3255
  if (unknownCommand !== undefined) {
3255
3256
  createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
3256
3257
  rootUsageName,
@@ -3260,7 +3261,7 @@ export async function runCLI(roots, options = {}) {
3260
3261
  return;
3261
3262
  }
3262
3263
  try {
3263
- await program.parseAsync(process.argv);
3264
+ await program.parseAsync(argv);
3264
3265
  }
3265
3266
  catch (error) {
3266
3267
  if (error instanceof ApprovalDeclinedError) {
@@ -3269,7 +3270,7 @@ export async function runCLI(roots, options = {}) {
3269
3270
  }
3270
3271
  const resolvedFlags = lastActionCommand ? getResolvedFlags(lastActionCommand) : undefined;
3271
3272
  const report = await writeErrorReport({
3272
- argv: process.argv,
3273
+ argv,
3273
3274
  command: errorReportContext?.command,
3274
3275
  commandPath: errorReportContext?.commandPath ?? resolvedCommandPath,
3275
3276
  env: process.env,
@@ -3286,13 +3287,13 @@ export async function runCLI(roots, options = {}) {
3286
3287
  await handleRunError(error, {
3287
3288
  debugStackMode: resolvedFlags !== undefined
3288
3289
  ? resolveDebugStackMode(resolvedFlags.debug)
3289
- : getDebugStackModeFromArgv(process.argv),
3290
+ : getDebugStackModeFromArgv(argv),
3290
3291
  output: resolvedFlags !== undefined
3291
3292
  ? resolveOutput(resolvedFlags)
3292
- : resolveOutputFromArgv(process.argv),
3293
- verbose: resolvedFlags ? Boolean(resolvedFlags.verbose) : process.argv.includes("--verbose"),
3293
+ : resolveOutputFromArgv(argv),
3294
+ verbose: resolvedFlags ? Boolean(resolvedFlags.verbose) : argv.includes("--verbose"),
3294
3295
  program,
3295
- argv: process.argv,
3296
+ argv,
3296
3297
  rootUsageName,
3297
3298
  commandPath: resolvedCommandPath,
3298
3299
  userErrorPattern: errorReportContext?.params === undefined ? "usage" : "runtime-user"
@@ -38,6 +38,7 @@ interface GenerateCliOptions {
38
38
  diff: boolean;
39
39
  input: string;
40
40
  inspect: boolean;
41
+ lockPath: string;
41
42
  outputFormat: OutputFormat;
42
43
  outputDir: string;
43
44
  }
@@ -19,6 +19,7 @@ const DEFAULT_OPTIONS = {
19
19
  diff: false,
20
20
  input: "openapi.json",
21
21
  inspect: false,
22
+ lockPath: "openapi.lock",
22
23
  outputFormat: "terminal",
23
24
  outputDir: "src/generated"
24
25
  };
@@ -27,6 +28,7 @@ const HELP_TEXT = `Usage: toolcraft-openapi-generate [options]
27
28
  Options:
28
29
  --input <path-or-url> OpenAPI document to read (default: openapi.json)
29
30
  --output <dir> Directory for generated command files (default: src/generated)
31
+ --lock <path> Lock file path (default: openapi.lock)
30
32
  --check Exit non-zero if generated output would change
31
33
  --diff Print a diff of generated changes without writing files
32
34
  --inspect Inspect route compatibility without writing files
@@ -113,18 +115,27 @@ export async function syncGeneratedClient(options, services) {
113
115
  const effectiveConfig = hasErrorDiagnostics(diagnostics) ? undefined : configResult.config;
114
116
  const generatedFiles = generate(document, { specSha, config: effectiveConfig });
115
117
  const outputDir = path.resolve(services.cwd, options.outputDir);
118
+ const lockPath = path.resolve(services.cwd, options.lockPath);
119
+ const currentLockContents = await readOpenApiLockText(services.fs, lockPath);
120
+ const desiredLockContents = stringifyOpenApiLock({ specSha });
116
121
  const currentFiles = await readGeneratedFiles(services.fs, outputDir);
117
122
  const desiredFiles = new Map([
118
123
  ...generatedFiles.map((file) => [path.resolve(outputDir, file.path), file.contents]),
119
124
  ...createDownloadedSpecFiles(options.input, sourceText).map((file) => [path.resolve(outputDir, file.path), file.contents])
120
125
  ]);
121
126
  const updatedFiles = collectUpdatedFiles(currentFiles, desiredFiles);
127
+ const updatedLockFile = currentLockContents === desiredLockContents
128
+ ? undefined
129
+ : { path: lockPath, contents: desiredLockContents, previousContents: currentLockContents };
122
130
  const deletedFiles = collectDeletedFiles(currentFiles, desiredFiles);
123
- const drifted = updatedFiles.length > 0 || deletedFiles.length > 0;
131
+ const drifted = updatedFiles.length > 0 || updatedLockFile !== undefined || deletedFiles.length > 0;
124
132
  if (!options.check && !options.diff && drifted && !hasErrorDiagnostics(diagnostics)) {
125
133
  try {
126
134
  await writeGeneratedFiles(services.fs, outputDir, updatedFiles);
127
135
  await deleteGeneratedFiles(services.fs, outputDir, deletedFiles);
136
+ if (updatedLockFile !== undefined) {
137
+ await writeOpenApiLock(services.fs, lockPath, { specSha });
138
+ }
128
139
  }
129
140
  catch (error) {
130
141
  await restoreGeneratedFiles(services.fs, outputDir, currentFiles, updatedFiles, deletedFiles);
@@ -137,8 +148,8 @@ export async function syncGeneratedClient(options, services) {
137
148
  diagnostics,
138
149
  drifted,
139
150
  specSha,
140
- updatedFiles,
141
- updatedFileCount: updatedFiles.length
151
+ updatedFiles: updatedLockFile === undefined ? updatedFiles : [...updatedFiles, updatedLockFile],
152
+ updatedFileCount: updatedFiles.length + (updatedLockFile === undefined ? 0 : 1)
142
153
  };
143
154
  }
144
155
  async function readAdjacentToolcraftConfig(input, services) {
@@ -243,7 +254,10 @@ function parseGenerateCliArgs(argv) {
243
254
  options.inspect = true;
244
255
  continue;
245
256
  }
246
- if (argument === "--input" || argument === "--output" || argument === "--output-format") {
257
+ if (argument === "--input" ||
258
+ argument === "--output" ||
259
+ argument === "--lock" ||
260
+ argument === "--output-format") {
247
261
  const value = argv[index + 1];
248
262
  if (value === undefined) {
249
263
  throw new UserError(`Missing value for ${JSON.stringify(argument)}.`);
@@ -260,6 +274,10 @@ function parseGenerateCliArgs(argv) {
260
274
  assignOptionValue(options, "--output", argument.slice("--output=".length));
261
275
  continue;
262
276
  }
277
+ if (argument.startsWith("--lock=")) {
278
+ assignOptionValue(options, "--lock", argument.slice("--lock=".length));
279
+ continue;
280
+ }
263
281
  if (argument.startsWith("--output-format=")) {
264
282
  assignOptionValue(options, "--output-format", argument.slice("--output-format=".length));
265
283
  continue;
@@ -283,8 +301,57 @@ function assignOptionValue(options, argument, value) {
283
301
  options.outputFormat = value;
284
302
  return;
285
303
  }
304
+ if (argument === "--lock") {
305
+ options.lockPath = value;
306
+ return;
307
+ }
286
308
  options.outputDir = value;
287
309
  }
310
+ async function readOpenApiLockText(fs, lockPath) {
311
+ try {
312
+ const contents = await fs.readFile(lockPath, "utf8");
313
+ parseOpenApiLock(contents, lockPath);
314
+ return contents;
315
+ }
316
+ catch (error) {
317
+ if (isNotFoundError(error)) {
318
+ return undefined;
319
+ }
320
+ throw error;
321
+ }
322
+ }
323
+ function parseOpenApiLock(contents, lockPath) {
324
+ let parsed;
325
+ try {
326
+ parsed = JSON.parse(contents);
327
+ }
328
+ catch (error) {
329
+ throw new UserError(`Lock file ${JSON.stringify(lockPath)} is not valid JSON: ${getErrorMessage(error)}.`, { cause: error });
330
+ }
331
+ if (typeof parsed !== "object" ||
332
+ parsed === null ||
333
+ !("version" in parsed) ||
334
+ parsed.version !== 1 ||
335
+ !("specSha" in parsed) ||
336
+ typeof parsed.specSha !== "string" ||
337
+ parsed.specSha.length === 0) {
338
+ return null;
339
+ }
340
+ return { specSha: parsed.specSha };
341
+ }
342
+ function stringifyOpenApiLock(lock) {
343
+ return `${JSON.stringify({ version: 1, specSha: lock.specSha }, null, 2)}\n`;
344
+ }
345
+ async function writeOpenApiLock(fs, lockPath, lock) {
346
+ await fs.mkdir(path.dirname(lockPath), { recursive: true });
347
+ try {
348
+ await fs.writeFile(lockPath, stringifyOpenApiLock(lock), "utf8");
349
+ }
350
+ catch (error) {
351
+ const code = getErrorCode(error);
352
+ throw new UserError(`Failed to write lock file ${JSON.stringify(lockPath)}${code === undefined ? "" : ` (${code})`}: ${getErrorMessage(error)}`, { cause: error });
353
+ }
354
+ }
288
355
  function createSpecSha(sourceText) {
289
356
  return `sha256:${createHash("sha256").update(sourceText).digest("hex")}`;
290
357
  }
@@ -446,6 +513,15 @@ function isNotFoundError(error) {
446
513
  function isAlreadyExistsError(error) {
447
514
  return hasOwnErrorCode(error, "EEXIST");
448
515
  }
516
+ function getErrorCode(error) {
517
+ if (typeof error !== "object" || error === null || !("code" in error)) {
518
+ return undefined;
519
+ }
520
+ return typeof error.code === "string" ? error.code : undefined;
521
+ }
522
+ function getErrorMessage(error) {
523
+ return error instanceof Error ? error.message : String(error);
524
+ }
449
525
  function isDirectExecution(moduleUrl, argv) {
450
526
  const entryPoint = argv[1];
451
527
  if (entryPoint === undefined) {