@workos/oagen 0.22.6 → 0.23.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.mjs +271 -42
  3. package/dist/cli/index.mjs.map +1 -1
  4. package/dist/compat/index.d.mts +2 -2
  5. package/dist/compat/index.mjs +9 -8
  6. package/dist/compat/index.mjs.map +1 -1
  7. package/dist/{diff-CQzcGuFJ.mjs → diff-B7dMUCoJ.mjs} +235 -27
  8. package/dist/diff-B7dMUCoJ.mjs.map +1 -0
  9. package/dist/{differ-CC65Efzt.d.mts → differ-DMmw0tbA.d.mts} +5 -5
  10. package/dist/{differ-CC65Efzt.d.mts.map → differ-DMmw0tbA.d.mts.map} +1 -1
  11. package/dist/index.d.mts +64 -9
  12. package/dist/index.d.mts.map +1 -1
  13. package/dist/index.mjs +26 -11
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/{overlay-cb32jDBS.mjs → overlay-sQHQoN0I.mjs} +4 -4
  16. package/dist/overlay-sQHQoN0I.mjs.map +1 -0
  17. package/dist/scripts/smoke/baseline.mjs +25 -10
  18. package/dist/scripts/smoke/baseline.mjs.map +1 -1
  19. package/dist/scripts/smoke/sdk-test.mjs +3 -3
  20. package/dist/scripts/smoke/sdk-test.mjs.map +1 -1
  21. package/dist/smoke.d.mts +19 -4
  22. package/dist/smoke.d.mts.map +1 -1
  23. package/dist/smoke.mjs +25 -10
  24. package/dist/smoke.mjs.map +1 -1
  25. package/dist/tree-sitter-DcUvm0KA.mjs.map +1 -1
  26. package/dist/{types-Cjh39AGQ.d.mts → types-nM5Kp7UP.d.mts} +16 -9
  27. package/dist/types-nM5Kp7UP.d.mts.map +1 -0
  28. package/dist/verify/index.d.mts +2 -2
  29. package/dist/verify/index.mjs +2 -2
  30. package/docs/architecture/extractor-contract.md +2 -2
  31. package/docs/architecture/pipeline.md +2 -2
  32. package/docs/cli.md +19 -19
  33. package/docs/core/compatibility-policy.md +1 -1
  34. package/docs/core/manifest-schema.md +2 -2
  35. package/package.json +1 -1
  36. package/skills/generate-emitter/SKILL.md +2 -2
  37. package/skills/generate-emitter/references/common-pitfalls.md +1 -1
  38. package/skills/generate-emitter/references/project-scaffold.md +3 -3
  39. package/skills/generate-extractor/SKILL.md +2 -2
  40. package/skills/generate-smoke-test/SKILL.md +1 -1
  41. package/skills/verify-smoke-test/SKILL.md +1 -1
  42. package/dist/diff-CQzcGuFJ.mjs.map +0 -1
  43. package/dist/overlay-cb32jDBS.mjs.map +0 -1
  44. package/dist/types-Cjh39AGQ.d.mts.map +0 -1
@@ -715,12 +715,12 @@ function extractKotlinClassMembers(classBody, source) {
715
715
  }
716
716
  /**
717
717
  * KDoc signature that identifies a generator-emitted service accessor inside
718
- * the WorkOS class. The Kotlin client emitter writes every accessor with this
718
+ * a Kotlin client class. The Kotlin client emitter writes every accessor with this
719
719
  * exact doc preamble — when the existing file carries a member with this doc
720
720
  * but the regenerated content omits it, the member is stale (its mount target
721
721
  * was remapped or removed) and gets pruned.
722
722
  */
723
- const KOTLIN_MANAGED_ACCESSOR_DOC = /\/\*\*[\s\S]*?Lazily-constructed \[[^\]]+\] accessor for this \[WorkOS\] client\.[\s\S]*?\*\//;
723
+ const KOTLIN_MANAGED_ACCESSOR_DOC = /\/\*\*[\s\S]*?Lazily-constructed \[[^\]]+\] accessor for this \[[^\]]+\] client\.[\s\S]*?\*\//;
724
724
  const kotlinMergeAdapter = {
725
725
  language: "kotlin",
726
726
  grammarModule: "tree-sitter-kotlin",
@@ -973,7 +973,6 @@ const nodeMergeAdapter = {
973
973
  const newText = newMembers.map((m) => m.text).join("\n");
974
974
  for (const match of newText.matchAll(/this\.(\w+)/g)) {
975
975
  const propName = match[1];
976
- if (propName === "workos") continue;
977
976
  if (propName && !existingMemberKeys.has(propName)) return true;
978
977
  }
979
978
  return false;
@@ -2667,6 +2666,60 @@ function collectTsImports(lines) {
2667
2666
  return out;
2668
2667
  }
2669
2668
  /**
2669
+ * Remove the given symbols from generated `import type` bindings, in place.
2670
+ *
2671
+ * Called when an existing *value* import for the same symbol is being spliced
2672
+ * back into the file. A value import satisfies both runtime and type uses, so
2673
+ * leaving the generated `import type { X }` would re-declare `X` (TS2300
2674
+ * duplicate identifier). Callers guarantee every symbol here is absent from
2675
+ * generated *value* imports, so each occurrence is type-only and safe to drop.
2676
+ *
2677
+ * Handles whole-line `import type { … }` and inline `import { …, type X }`,
2678
+ * single- or multi-line. A statement whose bindings all get stripped is
2679
+ * removed entirely.
2680
+ */
2681
+ function dropGeneratedTypeOnlyBindings(resultLines, symbols) {
2682
+ if (symbols.size === 0) return;
2683
+ const out = [];
2684
+ for (let i = 0; i < resultLines.length; i++) {
2685
+ const line = resultLines[i];
2686
+ const trimmed = line.trim();
2687
+ if (!/^import\b/.test(trimmed) || !trimmed.includes("{")) {
2688
+ out.push(line);
2689
+ continue;
2690
+ }
2691
+ const span = [line];
2692
+ while (!span.join(" ").includes("}") && i + 1 < resultLines.length) {
2693
+ i++;
2694
+ span.push(resultLines[i]);
2695
+ }
2696
+ const m = span.join(" ").replace(/\s+/g, " ").trim().replace(/;\s*$/, "").match(/^import\s+(type\s+)?(?:(\w+)\s*,\s*)?\{\s*([^}]*)\s*\}\s+from\s+(['"][^'"]+['"])$/);
2697
+ if (!m) {
2698
+ for (const s of span) out.push(s);
2699
+ continue;
2700
+ }
2701
+ const isTypeOnly = !!m[1];
2702
+ const defaultName = m[2];
2703
+ const items = m[3].split(",").map((s) => s.trim()).filter(Boolean);
2704
+ const modQuoted = m[4];
2705
+ const kept = items.filter((raw) => {
2706
+ const isTypeBinding = isTypeOnly || raw.startsWith("type ");
2707
+ const bare = raw.replace(/^type\s+/, "");
2708
+ return !(isTypeBinding && symbols.has(bare));
2709
+ });
2710
+ if (kept.length === items.length) {
2711
+ for (const s of span) out.push(s);
2712
+ continue;
2713
+ }
2714
+ if (kept.length === 0 && !defaultName) continue;
2715
+ const lead = line.match(/^(\s*)/)?.[1] ?? "";
2716
+ const prefix = isTypeOnly ? "import type " : "import ";
2717
+ const defaultPart = defaultName ? `${defaultName}, ` : "";
2718
+ out.push(`${lead}${prefix}${defaultPart}{ ${kept.join(", ")} } from ${modQuoted};`);
2719
+ }
2720
+ resultLines.splice(0, resultLines.length, ...out);
2721
+ }
2722
+ /**
2670
2723
  * Add imports from the existing file that are absent from the generated
2671
2724
  * result. Handles:
2672
2725
  *
@@ -2788,28 +2841,49 @@ function spliceExtraImports(existingLines, resultLines) {
2788
2841
  const existingTs = collectTsImports(existingOutsideIgnore);
2789
2842
  const generatedTs = collectTsImports(resultLines);
2790
2843
  const extraTsLines = [];
2844
+ const allGeneratedTsIds = /* @__PURE__ */ new Set();
2845
+ const generatedValueIds = /* @__PURE__ */ new Set();
2846
+ for (const ids of generatedTs.named.values()) for (const id of ids) {
2847
+ allGeneratedTsIds.add(id);
2848
+ generatedValueIds.add(id);
2849
+ }
2850
+ for (const ids of generatedTs.typeOnly.values()) for (const id of ids) allGeneratedTsIds.add(id);
2851
+ for (const name of generatedTs.defaults.values()) {
2852
+ allGeneratedTsIds.add(name);
2853
+ generatedValueIds.add(name);
2854
+ }
2855
+ for (const name of generatedTs.namespaces.values()) {
2856
+ allGeneratedTsIds.add(name);
2857
+ generatedValueIds.add(name);
2858
+ }
2859
+ const reclaimedValueIds = /* @__PURE__ */ new Set();
2860
+ const generatedHasValue = (id) => generatedValueIds.has(id);
2861
+ const generatedHasTypeOnly = (id) => allGeneratedTsIds.has(id) && !generatedValueIds.has(id);
2791
2862
  for (const mod of existingTs.sideEffects) if (!generatedTs.sideEffects.has(mod)) extraTsLines.push(`import '${mod}';`);
2792
2863
  for (const [mod, name] of existingTs.defaults) {
2793
2864
  if (generatedTs.defaults.has(mod)) continue;
2794
2865
  if (!identifierUsed(ignoreRegionText, name)) continue;
2866
+ if (generatedHasValue(name)) continue;
2867
+ if (generatedHasTypeOnly(name)) reclaimedValueIds.add(name);
2795
2868
  extraTsLines.push(`import ${name} from '${mod}';`);
2796
2869
  }
2797
2870
  for (const [mod, name] of existingTs.namespaces) {
2798
2871
  if (!identifierUsed(ignoreRegionText, name)) continue;
2799
- if (!generatedTs.namespaces.has(mod)) extraTsLines.push(`import * as ${name} from '${mod}';`);
2872
+ if (generatedHasValue(name)) continue;
2873
+ if (generatedTs.namespaces.has(mod)) continue;
2874
+ if (generatedHasTypeOnly(name)) reclaimedValueIds.add(name);
2875
+ extraTsLines.push(`import * as ${name} from '${mod}';`);
2800
2876
  }
2801
2877
  for (const [mod, existIds] of existingTs.named) {
2802
- const genIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
2803
- const genTypeIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
2804
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genTypeIds.has(id) && identifierUsed(ignoreRegionText, id));
2878
+ const extra = [...existIds].filter((id) => !generatedHasValue(id) && identifierUsed(ignoreRegionText, id));
2879
+ for (const id of extra) if (generatedHasTypeOnly(id)) reclaimedValueIds.add(id);
2805
2880
  if (extra.length > 0) extraTsLines.push(`import { ${extra.join(", ")} } from '${mod}';`);
2806
2881
  }
2807
2882
  for (const [mod, existIds] of existingTs.typeOnly) {
2808
- const genIds = generatedTs.typeOnly.get(mod) ?? /* @__PURE__ */ new Set();
2809
- const genNamedIds = generatedTs.named.get(mod) ?? /* @__PURE__ */ new Set();
2810
- const extra = [...existIds].filter((id) => !genIds.has(id) && !genNamedIds.has(id) && identifierUsed(ignoreRegionText, id));
2883
+ const extra = [...existIds].filter((id) => !allGeneratedTsIds.has(id) && identifierUsed(ignoreRegionText, id));
2811
2884
  if (extra.length > 0) extraTsLines.push(`import type { ${extra.join(", ")} } from '${mod}';`);
2812
2885
  }
2886
+ dropGeneratedTypeOnlyBindings(resultLines, reclaimedValueIds);
2813
2887
  const isPhpUseImport = (l) => /^use\s+[\w\\]+\\[\w\\]*(\s+as\s+\w+)?\s*;?$/.test(l);
2814
2888
  const existingPhpUse = new Set(existingOutsideIgnore.filter(isPhpUseImport).map((l) => l.trim()));
2815
2889
  const generatedPhpUse = new Set(resultLines.filter(isPhpUseImport).map((l) => l.trim()));
@@ -2853,11 +2927,44 @@ function spliceExtraImports(existingLines, resultLines) {
2853
2927
  if ((resultLines[i].match(/[)}]/g) ?? []).length > 0) break;
2854
2928
  }
2855
2929
  }
2856
- if (lastImportIdx >= 0) resultLines.splice(lastImportIdx + 1, 0, ...allExtra);
2930
+ if (lastImportIdx >= 0) {
2931
+ resultLines.splice(lastImportIdx + 1, 0, ...allExtra);
2932
+ return;
2933
+ }
2934
+ let insertAt = 0;
2935
+ let inBlockComment = false;
2936
+ for (let i = 0; i < resultLines.length; i++) {
2937
+ const t = resultLines[i].trim();
2938
+ if (inBlockComment) {
2939
+ insertAt = i + 1;
2940
+ if (t.includes("*/")) inBlockComment = false;
2941
+ continue;
2942
+ }
2943
+ if (t.startsWith("//")) {
2944
+ insertAt = i + 1;
2945
+ continue;
2946
+ }
2947
+ if (t.startsWith("/*")) {
2948
+ insertAt = i + 1;
2949
+ inBlockComment = !t.includes("*/");
2950
+ continue;
2951
+ }
2952
+ break;
2953
+ }
2954
+ resultLines.splice(insertAt, 0, ...allExtra);
2857
2955
  }
2858
2956
  //#endregion
2859
2957
  //#region src/engine/generate-files.ts
2860
2958
  function buildEmitterContext(spec, options) {
2959
+ const resolvedOperations = resolveOperations(spec, options.operationHints, options.mountRules);
2960
+ let scopedModelNames;
2961
+ let scopedEnumNames;
2962
+ if (options.scopedServices && options.scopedServices.size > 0) {
2963
+ const selected = options.scopedServices;
2964
+ const reachable = collectReferencedNames(spec.services.filter((s) => resolvedOperations.some((r) => r.service.name === s.name && selected.has(r.mountOn))), spec.models, { preserveAllDiscriminated: false });
2965
+ scopedModelNames = reachable.models;
2966
+ scopedEnumNames = reachable.enums;
2967
+ }
2861
2968
  return {
2862
2969
  namespace: toSnakeCase(options.namespace),
2863
2970
  namespacePascal: options.namespace,
@@ -2865,11 +2972,14 @@ function buildEmitterContext(spec, options) {
2865
2972
  outputDir: options.outputDir,
2866
2973
  apiSurface: options.apiSurface,
2867
2974
  overlayLookup: options.overlayLookup,
2868
- resolvedOperations: resolveOperations(spec, options.operationHints, options.mountRules),
2975
+ resolvedOperations,
2869
2976
  modelHints: options.modelHints,
2870
2977
  emitterOptions: options.emitterOptions,
2871
2978
  targetDir: options.target,
2872
- priorTargetManifestPaths: options.priorTargetManifestPaths
2979
+ priorTargetManifestPaths: options.priorTargetManifestPaths,
2980
+ scopedServices: options.scopedServices,
2981
+ scopedModelNames,
2982
+ scopedEnumNames
2873
2983
  };
2874
2984
  }
2875
2985
  /**
@@ -2877,7 +2987,8 @@ function buildEmitterContext(spec, options) {
2877
2987
  * Walks operation parameters, request bodies, responses, and pagination item
2878
2988
  * types, then chases model field references until the set stabilizes.
2879
2989
  */
2880
- function collectReferencedNames(services, models) {
2990
+ function collectReferencedNames(services, models, opts = {}) {
2991
+ const preserveAllDiscriminated = opts.preserveAllDiscriminated !== false;
2881
2992
  const referencedModels = /* @__PURE__ */ new Set();
2882
2993
  const referencedEnums = /* @__PURE__ */ new Set();
2883
2994
  const collectFromTypeRef = (ref) => {
@@ -2899,7 +3010,7 @@ function collectReferencedNames(services, models) {
2899
3010
  for (const err of op.errors) if (err.type) collectFromTypeRef(err.type);
2900
3011
  if (op.successResponses) for (const sr of op.successResponses) collectFromTypeRef(sr.type);
2901
3012
  }
2902
- for (const model of models) {
3013
+ if (preserveAllDiscriminated) for (const model of models) {
2903
3014
  if (isDiscriminatedModel(model)) referencedModels.add(model.name);
2904
3015
  const disc = model.discriminator;
2905
3016
  if (disc?.mapping) for (const variantName of Object.values(disc.mapping)) referencedModels.add(variantName);
@@ -2913,6 +3024,13 @@ function collectReferencedNames(services, models) {
2913
3024
  visited.add(name);
2914
3025
  const model = modelsByName.get(name);
2915
3026
  if (!model) continue;
3027
+ const disc = model.discriminator;
3028
+ if (disc?.mapping) {
3029
+ for (const variantName of Object.values(disc.mapping)) if (!referencedModels.has(variantName)) {
3030
+ referencedModels.add(variantName);
3031
+ queue.push(variantName);
3032
+ }
3033
+ }
2916
3034
  for (const field of model.fields) {
2917
3035
  collectFromTypeRef(field.type);
2918
3036
  for (const m of referencedModels) if (!visited.has(m)) queue.push(m);
@@ -2926,7 +3044,16 @@ function collectReferencedNames(services, models) {
2926
3044
  function isDiscriminatedModel(model) {
2927
3045
  return model.fields.some((field) => (field.name === "event" || field.name === "type" || field.name === "object") && field.type.kind === "literal");
2928
3046
  }
2929
- /** Collect all generated files from an emitter (no headers, no path prefixes). */
3047
+ /**
3048
+ * Collect all generated files from an emitter (no headers, no path prefixes).
3049
+ *
3050
+ * In a scoped (`--services`) run the spec is NOT filtered: every generator runs
3051
+ * over the full spec so model placement, dedup, the root client, and all
3052
+ * aggregate/barrel files are byte-identical to a full run (and a brand-new
3053
+ * selected service is wired into the client automatically). The emitters
3054
+ * themselves consult `ctx.scopedServices` to emit only the selected services'
3055
+ * per-service resource/test files.
3056
+ */
2930
3057
  function generateAllFiles(spec, emitter, ctx) {
2931
3058
  const referenced = collectReferencedNames(spec.services, spec.models);
2932
3059
  const reachableModels = spec.models.filter((m) => referenced.models.has(m.name));
@@ -2967,6 +3094,47 @@ function generateFiles(spec, emitter, options) {
2967
3094
  };
2968
3095
  }
2969
3096
  //#endregion
3097
+ //#region src/engine/scoped-services.ts
3098
+ /**
3099
+ * Validate a `--services` selection and return it as a set of POST-MOUNT service
3100
+ * names for emitters to scope on.
3101
+ *
3102
+ * Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
3103
+ * shared-schema decisions are global computations over the full service/model set
3104
+ * (filtering them churns shared `common/` files). Instead the full spec flows to
3105
+ * the emitters unchanged and this set is passed as `ctx.scopedServices`; each
3106
+ * emitter gates only its per-service resource/test emission on it.
3107
+ *
3108
+ * Matching is on the POST-MOUNT name. Mount resolution never mutates
3109
+ * `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
3110
+ * (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
3111
+ * post-mount name of a source service is derived here as
3112
+ * `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
3113
+ * `DirectorySync`) is valid because every source tag that mounts into it
3114
+ * (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
3115
+ * emitters group resources by mount, so all siblings emit together.
3116
+ *
3117
+ * @param spec - The parsed API spec (never mutated; never filtered).
3118
+ * @param selected - Post-mount service names from `--services` (normalized/trimmed).
3119
+ * @param mountRules - Source-service-name → post-mount-target mappings.
3120
+ * @returns The validated selection as a `Set` of post-mount names.
3121
+ * @throws A `ConfigError` listing valid post-mount names when a selection is
3122
+ * unknown — scoped generation never silently emits the wrong set.
3123
+ */
3124
+ function resolveScopedServices(spec, selected, mountRules) {
3125
+ const mounts = mountRules ?? {};
3126
+ const postMountName = (name) => mounts[name] ?? name;
3127
+ const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
3128
+ const selectedSet = new Set(selected);
3129
+ const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
3130
+ if (unknown.length > 0) {
3131
+ const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
3132
+ err.name = "ConfigError";
3133
+ throw err;
3134
+ }
3135
+ return selectedSet;
3136
+ }
3137
+ //#endregion
2970
3138
  //#region src/engine/integrate.ts
2971
3139
  /**
2972
3140
  * Resolve a relative import path against a source file path to get the
@@ -3083,7 +3251,7 @@ function mapFilesForTargetIntegration(files, language) {
3083
3251
  * that would shadow an existing module file (e.g., `api_keys.py`), move the
3084
3252
  * module file's content into `_compat.py` inside the package and add a
3085
3253
  * re-export from `__init__.py`. This preserves backward-compatible imports
3086
- * (e.g., `from workos.api_keys import ApiKeysModule`) while the package directory
3254
+ * (e.g., `from acme.api_keys import ApiKeysModule`) while the package directory
3087
3255
  * provides the generated models and resource classes.
3088
3256
  */
3089
3257
  async function resolveFileToDirectoryConflicts(files, targetDir) {
@@ -3249,6 +3417,35 @@ function computeStalePaths(prev, currentPaths) {
3249
3417
  return prev.files.filter((p) => !current.has(p)).sort();
3250
3418
  }
3251
3419
  /**
3420
+ * Merge a scoped run's emitted records into the prior manifest's records.
3421
+ *
3422
+ * A scoped (`--services`) run only emits files for the selected services, but it
3423
+ * must NOT discard the prior manifest's record of unselected services' files
3424
+ * records. The result unions prior + scoped file paths and shallow-merges the
3425
+ * operations maps, with scoped operations overriding prior entries for the same
3426
+ * "METHOD /path" key (a re-emission updates that operation's record).
3427
+ *
3428
+ * When `prev` is null (the first-ever run is scoped) there is nothing to
3429
+ * preserve, so the scoped subset is returned unchanged — matching the
3430
+ * first-adoption behavior of a full run.
3431
+ */
3432
+ function mergeScopedManifestRecords(prev, scopedFiles, scopedOperations) {
3433
+ const scopedFileList = [...scopedFiles];
3434
+ if (!prev) return {
3435
+ files: scopedFileList,
3436
+ operations: scopedOperations
3437
+ };
3438
+ const files = [...new Set([...prev.files, ...scopedFileList])].sort();
3439
+ const priorOps = prev.operations;
3440
+ return {
3441
+ files,
3442
+ operations: priorOps || scopedOperations ? {
3443
+ ...priorOps,
3444
+ ...scopedOperations
3445
+ } : void 0
3446
+ };
3447
+ }
3448
+ /**
3252
3449
  * Delete stale files from `dir`.
3253
3450
  *
3254
3451
  * When `header` is provided, a file is only deleted if its contents start with
@@ -3317,12 +3514,17 @@ async function removeEmptyParents(startDir, stopDir) {
3317
3514
  //#endregion
3318
3515
  //#region src/engine/orchestrator.ts
3319
3516
  async function generate(spec, emitter, options) {
3320
- const outputPrevManifestForCtx = options.noPrune ? null : await readManifest(options.outputDir);
3321
- const targetManifestForCtx = options.target && !options.noPrune ? await readManifest(options.target) : null;
3517
+ const scopedServices = options.services && options.services.length > 0 ? resolveScopedServices(spec, options.services, options.mountRules) : void 0;
3518
+ const scoped = scopedServices !== void 0;
3519
+ const noPrune = options.noPrune === true || scoped;
3520
+ const skipManifestRead = options.noPrune === true && !scoped;
3521
+ const outputPrevManifestForCtx = skipManifestRead ? null : await readManifest(options.outputDir);
3522
+ const targetManifestForCtx = options.target && !skipManifestRead ? await readManifest(options.target) : null;
3322
3523
  const priorTargetManifestPaths = targetManifestForCtx ? new Set(targetManifestForCtx.files) : outputPrevManifestForCtx ? new Set(outputPrevManifestForCtx.files) : void 0;
3323
3524
  const { files: withHeaders, header, operations } = generateFiles(spec, emitter, {
3324
3525
  ...options,
3325
- priorTargetManifestPaths
3526
+ priorTargetManifestPaths,
3527
+ scopedServices
3326
3528
  });
3327
3529
  if (options.dryRun) {
3328
3530
  if (options.target) {
@@ -3346,8 +3548,9 @@ async function generate(spec, emitter, options) {
3346
3548
  currentPaths: outputEmittedPaths,
3347
3549
  language: emitter.language,
3348
3550
  header,
3349
- noPrune: options.noPrune,
3350
- operations
3551
+ noPrune,
3552
+ operations,
3553
+ scoped
3351
3554
  });
3352
3555
  const allOutputFiles = [...writeResult.written, ...writeResult.merged];
3353
3556
  if (allOutputFiles.length > 0) await formatTargetFiles(emitter, options.outputDir, allOutputFiles);
@@ -3369,8 +3572,9 @@ async function generate(spec, emitter, options) {
3369
3572
  currentPaths: targetResult.emittedPaths,
3370
3573
  language: emitter.language,
3371
3574
  header,
3372
- noPrune: options.noPrune,
3373
- operations
3575
+ noPrune,
3576
+ operations,
3577
+ scoped
3374
3578
  });
3375
3579
  const allTargetFiles = [
3376
3580
  ...targetResult.written,
@@ -3386,11 +3590,15 @@ async function generate(spec, emitter, options) {
3386
3590
  * then write the fresh manifest. Safe on first adoption (no previous manifest).
3387
3591
  */
3388
3592
  async function applyManifestPrune(opts) {
3389
- const manifestOpts = {
3390
- language: opts.language,
3593
+ const records = opts.scoped ? mergeScopedManifestRecords(opts.prevManifest, opts.currentPaths, opts.operations) : {
3391
3594
  files: opts.currentPaths,
3392
3595
  operations: opts.operations
3393
3596
  };
3597
+ const manifestOpts = {
3598
+ language: opts.language,
3599
+ files: records.files,
3600
+ operations: records.operations
3601
+ };
3394
3602
  if (opts.noPrune) {
3395
3603
  await writeManifest(opts.dir, manifestOpts);
3396
3604
  return;
@@ -3861,4 +4069,4 @@ function summarize(changes) {
3861
4069
  //#endregion
3862
4070
  export { MANIFEST_FILENAME as a, treeShakeFiles as c, collectReferencedNames as d, generateAllFiles as f, resolveOperations as h, generate as i, applyFileHeaders as l, deriveMethodName as m, getEmitter as n, integrateGeneratedFiles as o, generateFiles as p, registerEmitter as r, mapFilesForTargetIntegration as s, diffSpecs as t, buildEmitterContext as u };
3863
4071
 
3864
- //# sourceMappingURL=diff-CQzcGuFJ.mjs.map
4072
+ //# sourceMappingURL=diff-B7dMUCoJ.mjs.map