@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.
- package/README.md +1 -1
- package/dist/cli/index.mjs +271 -42
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +2 -2
- package/dist/compat/index.mjs +9 -8
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-CQzcGuFJ.mjs → diff-B7dMUCoJ.mjs} +235 -27
- package/dist/diff-B7dMUCoJ.mjs.map +1 -0
- package/dist/{differ-CC65Efzt.d.mts → differ-DMmw0tbA.d.mts} +5 -5
- package/dist/{differ-CC65Efzt.d.mts.map → differ-DMmw0tbA.d.mts.map} +1 -1
- package/dist/index.d.mts +64 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +26 -11
- package/dist/index.mjs.map +1 -1
- package/dist/{overlay-cb32jDBS.mjs → overlay-sQHQoN0I.mjs} +4 -4
- package/dist/overlay-sQHQoN0I.mjs.map +1 -0
- package/dist/scripts/smoke/baseline.mjs +25 -10
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/scripts/smoke/sdk-test.mjs +3 -3
- package/dist/scripts/smoke/sdk-test.mjs.map +1 -1
- package/dist/smoke.d.mts +19 -4
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +25 -10
- package/dist/smoke.mjs.map +1 -1
- package/dist/tree-sitter-DcUvm0KA.mjs.map +1 -1
- package/dist/{types-Cjh39AGQ.d.mts → types-nM5Kp7UP.d.mts} +16 -9
- package/dist/types-nM5Kp7UP.d.mts.map +1 -0
- package/dist/verify/index.d.mts +2 -2
- package/dist/verify/index.mjs +2 -2
- package/docs/architecture/extractor-contract.md +2 -2
- package/docs/architecture/pipeline.md +2 -2
- package/docs/cli.md +19 -19
- package/docs/core/compatibility-policy.md +1 -1
- package/docs/core/manifest-schema.md +2 -2
- package/package.json +1 -1
- package/skills/generate-emitter/SKILL.md +2 -2
- package/skills/generate-emitter/references/common-pitfalls.md +1 -1
- package/skills/generate-emitter/references/project-scaffold.md +3 -3
- package/skills/generate-extractor/SKILL.md +2 -2
- package/skills/generate-smoke-test/SKILL.md +1 -1
- package/skills/verify-smoke-test/SKILL.md +1 -1
- package/dist/diff-CQzcGuFJ.mjs.map +0 -1
- package/dist/overlay-cb32jDBS.mjs.map +0 -1
- 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
|
-
*
|
|
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 \[
|
|
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 (
|
|
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
|
|
2803
|
-
const
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
3321
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
4072
|
+
//# sourceMappingURL=diff-B7dMUCoJ.mjs.map
|