@workos/oagen 0.8.0 → 0.9.0

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.
@@ -4771,8 +4771,7 @@ function generateAllFiles(spec, emitter, ctx) {
4771
4771
  ...emitter.generateClient(spec, ctx),
4772
4772
  ...emitter.generateErrors(ctx),
4773
4773
  ...emitter.generateTypeSignatures?.(reachableSpec, ctx) ?? [],
4774
- ...emitter.generateTests(reachableSpec, ctx),
4775
- ...emitter.generateManifest?.(spec, ctx) ?? []
4774
+ ...emitter.generateTests(reachableSpec, ctx)
4776
4775
  ];
4777
4776
  }
4778
4777
  /** Apply file header to generated files, respecting headerPlacement and JSON files. */
@@ -4786,11 +4785,13 @@ function applyFileHeaders(files, header) {
4786
4785
  function generateFiles(spec, emitter, options) {
4787
4786
  const ctx = buildEmitterContext(spec, options);
4788
4787
  const files = generateAllFiles(spec, emitter, ctx);
4788
+ const operations = emitter.buildOperationsMap?.(spec, ctx);
4789
4789
  const header = emitter.fileHeader();
4790
4790
  return {
4791
4791
  files: applyFileHeaders(files, header),
4792
4792
  ctx,
4793
- header
4793
+ header,
4794
+ operations
4794
4795
  };
4795
4796
  }
4796
4797
  //#endregion
@@ -5056,7 +5057,8 @@ async function writeManifest(dir, opts) {
5056
5057
  ...opts.emitterVersion !== void 0 ? { emitterVersion: opts.emitterVersion } : {},
5057
5058
  ...opts.configSha !== void 0 ? { configSha: opts.configSha } : {},
5058
5059
  ...opts.compatSchemaVersion !== void 0 ? { compatSchemaVersion: opts.compatSchemaVersion } : {},
5059
- files: [...new Set(opts.files)].sort()
5060
+ files: [...new Set(opts.files)].sort(),
5061
+ ...opts.operations !== void 0 ? { operations: opts.operations } : {}
5060
5062
  };
5061
5063
  const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
5062
5064
  await fs.mkdir(dir, { recursive: true });
@@ -5138,7 +5140,7 @@ async function removeEmptyParents(startDir, stopDir) {
5138
5140
  async function generate(spec, emitter, options) {
5139
5141
  const targetManifestForCtx = options.target && !options.noPrune ? await readManifest(options.target) : null;
5140
5142
  const priorTargetManifestPaths = targetManifestForCtx ? new Set(targetManifestForCtx.files) : void 0;
5141
- const { files: withHeaders, header } = generateFiles(spec, emitter, {
5143
+ const { files: withHeaders, header, operations } = generateFiles(spec, emitter, {
5142
5144
  ...options,
5143
5145
  priorTargetManifestPaths
5144
5146
  });
@@ -5164,7 +5166,8 @@ async function generate(spec, emitter, options) {
5164
5166
  currentPaths: outputEmittedPaths,
5165
5167
  language: emitter.language,
5166
5168
  header,
5167
- noPrune: options.noPrune
5169
+ noPrune: options.noPrune,
5170
+ operations
5168
5171
  });
5169
5172
  const allOutputFiles = [...writeResult.written, ...writeResult.merged];
5170
5173
  if (allOutputFiles.length > 0) await formatTargetFiles(emitter, options.outputDir, allOutputFiles);
@@ -5186,7 +5189,8 @@ async function generate(spec, emitter, options) {
5186
5189
  currentPaths: targetResult.emittedPaths,
5187
5190
  language: emitter.language,
5188
5191
  header,
5189
- noPrune: options.noPrune
5192
+ noPrune: options.noPrune,
5193
+ operations
5190
5194
  });
5191
5195
  const allTargetFiles = [
5192
5196
  ...targetResult.written,
@@ -5202,19 +5206,18 @@ async function generate(spec, emitter, options) {
5202
5206
  * then write the fresh manifest. Safe on first adoption (no previous manifest).
5203
5207
  */
5204
5208
  async function applyManifestPrune(opts) {
5209
+ const manifestOpts = {
5210
+ language: opts.language,
5211
+ files: opts.currentPaths,
5212
+ operations: opts.operations
5213
+ };
5205
5214
  if (opts.noPrune) {
5206
- await writeManifest(opts.dir, {
5207
- language: opts.language,
5208
- files: opts.currentPaths
5209
- });
5215
+ await writeManifest(opts.dir, manifestOpts);
5210
5216
  return;
5211
5217
  }
5212
5218
  if (!opts.prevManifest) {
5213
5219
  console.log(`${opts.label}: no ${MANIFEST_FILENAME} found — skipping prune (this baseline manifest will be written now).`);
5214
- await writeManifest(opts.dir, {
5215
- language: opts.language,
5216
- files: opts.currentPaths
5217
- });
5220
+ await writeManifest(opts.dir, manifestOpts);
5218
5221
  return;
5219
5222
  }
5220
5223
  const stale = computeStalePaths(opts.prevManifest, opts.currentPaths);
@@ -5226,10 +5229,7 @@ async function applyManifestPrune(opts) {
5226
5229
  for (const p of preserved) console.log(` ${p}`);
5227
5230
  }
5228
5231
  }
5229
- await writeManifest(opts.dir, {
5230
- language: opts.language,
5231
- files: opts.currentPaths
5232
- });
5232
+ await writeManifest(opts.dir, manifestOpts);
5233
5233
  }
5234
5234
  //#endregion
5235
5235
  //#region src/engine/registry.ts
@@ -5740,7 +5740,7 @@ function patchOverlay(overlay, changes, baseline) {
5740
5740
  if (baseClass && baseClass.methods[methodName]?.length > 0) {
5741
5741
  const httpKey = overlay.httpKeyByMethod.get(`${className}.${methodName}`);
5742
5742
  if (!httpKey && overlay.httpKeyByMethod.size === 0 && !warnedManifest) {
5743
- console.warn("Warning: No smoke-manifest.json available. Method-level violations cannot be auto-patched. Implement generateManifest in your emitter for the self-correcting loop to resolve these.");
5743
+ console.warn("Warning: No operations map in .oagen-manifest.json. Method-level violations cannot be auto-patched. Implement buildOperationsMap in your emitter for the self-correcting loop to resolve these.");
5744
5744
  warnedManifest = true;
5745
5745
  }
5746
5746
  if (httpKey) {
@@ -5774,6 +5774,49 @@ function getExtractor(language) {
5774
5774
  return extractor;
5775
5775
  }
5776
5776
  //#endregion
5777
+ //#region src/engine/manifest-reader.ts
5778
+ /**
5779
+ * Read the operations map from `.oagen-manifest.json` in the given directory
5780
+ * and convert it to `ManifestEntry[]` for the overlay builder.
5781
+ *
5782
+ * Returns undefined if the manifest is absent or has no operations field.
5783
+ */
5784
+ function readManifestSync(dir) {
5785
+ const manifestPath = path$1.join(dir, MANIFEST_FILENAME);
5786
+ if (!existsSync(manifestPath)) return void 0;
5787
+ try {
5788
+ const operations = JSON.parse(readFileSync(manifestPath, "utf-8"))?.operations;
5789
+ if (!operations || typeof operations !== "object") return void 0;
5790
+ return convertOperationsToManifestEntries(operations);
5791
+ } catch {
5792
+ return;
5793
+ }
5794
+ }
5795
+ /** Convert the operations map from .oagen-manifest.json into ManifestEntry[]. */
5796
+ function convertOperationsToManifestEntries(operations) {
5797
+ const entries = [];
5798
+ for (const [httpKey, value] of Object.entries(operations)) {
5799
+ const spaceIdx = httpKey.indexOf(" ");
5800
+ const httpMethod = httpKey.slice(0, spaceIdx);
5801
+ const httpPath = httpKey.slice(spaceIdx + 1);
5802
+ const values = Array.isArray(value) ? value : [value];
5803
+ for (const v of values) {
5804
+ const entry = v;
5805
+ entries.push({
5806
+ operationId: "",
5807
+ sdkResourceProperty: entry.service ?? "",
5808
+ sdkMethodName: entry.sdkMethod ?? "",
5809
+ httpMethod,
5810
+ path: httpPath,
5811
+ pathParams: [],
5812
+ bodyFields: [],
5813
+ queryFields: []
5814
+ });
5815
+ }
5816
+ }
5817
+ return entries;
5818
+ }
5819
+ //#endregion
5777
5820
  //#region src/cli/overlay-loader.ts
5778
5821
  /**
5779
5822
  * Load an API surface from disk, discover/load the manifest, resolve
@@ -5792,30 +5835,7 @@ function loadOverlayContext(opts) {
5792
5835
  } catch (err) {
5793
5836
  throw new ConfigError(`Failed to parse API surface JSON: ${err instanceof Error ? err.message : String(err)}`, `Ensure "${opts.apiSurfacePath}" contains valid JSON. Re-run \`oagen extract\` to regenerate it.`);
5794
5837
  }
5795
- let manifest;
5796
- const resolvedManifestPath = opts.manifestPath ?? path$1.join(opts.outputDir, "smoke-manifest.json");
5797
- if (opts.manifestPath || existsSync(resolvedManifestPath)) try {
5798
- const parsed = JSON.parse(readFileSync(resolvedManifestPath, "utf-8"));
5799
- if (Array.isArray(parsed)) manifest = parsed;
5800
- else if (typeof parsed === "object" && parsed !== null) manifest = Object.entries(parsed).map(([httpKey, value]) => {
5801
- const spaceIdx = httpKey.indexOf(" ");
5802
- const httpMethod = httpKey.slice(0, spaceIdx);
5803
- const httpPath = httpKey.slice(spaceIdx + 1);
5804
- const v = value;
5805
- return {
5806
- operationId: "",
5807
- sdkResourceProperty: v.service ?? "",
5808
- sdkMethodName: v.sdkMethod ?? "",
5809
- httpMethod,
5810
- path: httpPath,
5811
- pathParams: [],
5812
- bodyFields: [],
5813
- queryFields: []
5814
- };
5815
- });
5816
- } catch {
5817
- if (opts.manifestPath) throw new ConfigError(`Failed to read manifest: ${resolvedManifestPath}`, `Verify the manifest file exists and contains valid JSON. If using --manifest, check the path is correct.`);
5818
- }
5838
+ const manifest = readManifestSync(opts.outputDir);
5819
5839
  let hints = nodeHints;
5820
5840
  try {
5821
5841
  hints = getExtractor(opts.lang).hints;
@@ -5896,7 +5916,6 @@ async function generateCommand(opts) {
5896
5916
  if (opts.apiSurface && opts.compatCheck !== false) {
5897
5917
  const ctx = loadOverlayContext({
5898
5918
  apiSurfacePath: opts.apiSurface,
5899
- manifestPath: opts.manifest,
5900
5919
  outputDir: opts.output,
5901
5920
  lang: opts.lang,
5902
5921
  spec: ir
@@ -7454,37 +7473,9 @@ function cleanGeneratedFiles(dir) {
7454
7473
  } catch {}
7455
7474
  }
7456
7475
  }
7457
- /**
7458
- * Load a smoke-manifest.json from the generated SDK directory and convert
7459
- * it to ManifestEntry[] for the overlay builder.
7460
- */
7461
- function loadManifestFromDir(outputDir) {
7462
- const manifestPath = path$1.join(outputDir, "smoke-manifest.json");
7463
- if (!existsSync(manifestPath)) return void 0;
7464
- try {
7465
- const parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
7466
- if (Array.isArray(parsed)) return parsed;
7467
- if (typeof parsed === "object" && parsed !== null) return Object.entries(parsed).map(([httpKey, value]) => {
7468
- const spaceIdx = httpKey.indexOf(" ");
7469
- const httpMethod = httpKey.slice(0, spaceIdx);
7470
- const httpPath = httpKey.slice(spaceIdx + 1);
7471
- const v = value;
7472
- return {
7473
- operationId: "",
7474
- sdkResourceProperty: v.service ?? "",
7475
- sdkMethodName: v.sdkMethod ?? "",
7476
- httpMethod,
7477
- path: httpPath,
7478
- pathParams: [],
7479
- bodyFields: [],
7480
- queryFields: []
7481
- };
7482
- });
7483
- } catch {}
7484
- }
7485
7476
  async function runOverlayRetryLoop(opts) {
7486
7477
  const { baseline, parsedSpec, outputDir, lang, maxRetries, namespace, onRetry } = opts;
7487
- let overlay = buildOverlayLookup(baseline, loadManifestFromDir(outputDir), parsedSpec, getExtractor(lang).hints);
7478
+ let overlay = buildOverlayLookup(baseline, readManifestSync(outputDir), parsedSpec, getExtractor(lang).hints);
7488
7479
  let prevPreserved = -1;
7489
7480
  const patchedPerIteration = [];
7490
7481
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
@@ -8039,8 +8030,8 @@ export const ${`${toCamelCase(lang)}Emitter`}: Emitter = {
8039
8030
  return [];
8040
8031
  },
8041
8032
 
8042
- generateManifest(_spec: ApiSpec, _ctx: EmitterContext): GeneratedFile[] {
8043
- return [];
8033
+ buildOperationsMap(_spec: ApiSpec, _ctx: EmitterContext) {
8034
+ return {};
8044
8035
  },
8045
8036
 
8046
8037
  fileHeader(): string {
@@ -8254,7 +8245,7 @@ program.command("parse").description("Parse an OpenAPI spec and output IR as JSO
8254
8245
  }
8255
8246
  return parseCommand(opts);
8256
8247
  });
8257
- program.command("generate").description("Run a registered emitter against an OpenAPI spec").option("--spec <path>", "Path to OpenAPI spec file (or set OPENAPI_SPEC_PATH)").requiredOption("--lang <language>", "Target language").requiredOption("--output <dir>", "Output directory").option("--target <dir>", "Target directory for live SDK integration (merged output)").option("--namespace <name>", "SDK namespace/package name").option("--dry-run", "Preview files without writing").option("--api-surface <path>", "Path to baseline API surface JSON for compat overlay").option("--manifest <path>", "Path to smoke-manifest.json for method overlay").option("--no-compat-check", "Skip compat overlay even if --api-surface is provided").option("--no-prune", "Skip deletion of stale files recorded in the previous .oagen-manifest.json").action((opts) => {
8248
+ program.command("generate").description("Run a registered emitter against an OpenAPI spec").option("--spec <path>", "Path to OpenAPI spec file (or set OPENAPI_SPEC_PATH)").requiredOption("--lang <language>", "Target language").requiredOption("--output <dir>", "Output directory").option("--target <dir>", "Target directory for live SDK integration (merged output)").option("--namespace <name>", "SDK namespace/package name").option("--dry-run", "Preview files without writing").option("--api-surface <path>", "Path to baseline API surface JSON for compat overlay").option("--no-compat-check", "Skip compat overlay even if --api-surface is provided").option("--no-prune", "Skip deletion of stale files recorded in the previous .oagen-manifest.json").action((opts) => {
8258
8249
  opts.spec ??= process.env.OPENAPI_SPEC_PATH;
8259
8250
  if (!opts.spec) {
8260
8251
  console.error("error: --spec <path> or OPENAPI_SPEC_PATH env var is required");