@workos/oagen 0.22.7 → 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 +142 -33
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +2 -2
- package/dist/compat/index.mjs +7 -7
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-D4XaiPeA.mjs → diff-B7dMUCoJ.mjs} +128 -19
- package/dist/diff-B7dMUCoJ.mjs.map +1 -0
- package/dist/{differ-Q13KY9Rd.d.mts → differ-DMmw0tbA.d.mts} +5 -5
- package/dist/{differ-Q13KY9Rd.d.mts.map → differ-DMmw0tbA.d.mts.map} +1 -1
- package/dist/index.d.mts +50 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +9 -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 +8 -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 +3 -4
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +8 -10
- package/dist/smoke.mjs.map +1 -1
- package/dist/{types-DZrKFHis.d.mts → types-nM5Kp7UP.d.mts} +7 -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-D4XaiPeA.mjs.map +0 -1
- package/dist/overlay-cb32jDBS.mjs.map +0 -1
- package/dist/types-DZrKFHis.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ export default {
|
|
|
165
165
|
retry: { backoff: { initialDelay: 0.5, maxDelay: 8.0 } },
|
|
166
166
|
timeout: {
|
|
167
167
|
defaultTimeoutSeconds: 30,
|
|
168
|
-
timeoutEnvVar: "
|
|
168
|
+
timeoutEnvVar: "SDK_REQUEST_TIMEOUT",
|
|
169
169
|
},
|
|
170
170
|
pagination: { autoPageDelayMs: 0 },
|
|
171
171
|
},
|
package/dist/cli/index.mjs
CHANGED
|
@@ -14,9 +14,8 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, rea
|
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
15
15
|
//#region src/ir/sdk-behavior.ts
|
|
16
16
|
/**
|
|
17
|
-
* Canonical SDK behavior defaults.
|
|
18
|
-
*
|
|
19
|
-
* via `mergeSdkBehavior()` in each SDK's `oagen.config.ts`.
|
|
17
|
+
* Canonical SDK behavior defaults. Per-language or per-project overrides can
|
|
18
|
+
* be applied via `mergeSdkBehavior()` in each SDK's `oagen.config.ts`.
|
|
20
19
|
*
|
|
21
20
|
* - Retry: exponential backoff with jitter, retries on 429/5xx
|
|
22
21
|
* - Errors: standard HTTP status → exception kind mapping
|
|
@@ -59,12 +58,11 @@ function defaultSdkBehavior() {
|
|
|
59
58
|
429: "RateLimitExceeded"
|
|
60
59
|
},
|
|
61
60
|
serverErrorKind: "Server",
|
|
62
|
-
clientErrorKind: "Api"
|
|
63
|
-
errorDocUrlTemplate: "https://workos.com/docs/errors/{code}"
|
|
61
|
+
clientErrorKind: "Api"
|
|
64
62
|
},
|
|
65
63
|
telemetry: {
|
|
66
64
|
enabledByDefault: true,
|
|
67
|
-
headerName: "X-
|
|
65
|
+
headerName: "X-Client-Telemetry",
|
|
68
66
|
requestIdHeader: "X-Request-ID"
|
|
69
67
|
},
|
|
70
68
|
pagination: { autoPageDelayMs: 0 },
|
|
@@ -2044,11 +2042,11 @@ function inferVerb(method, path) {
|
|
|
2044
2042
|
* Uses the last non-param segment (the resource being operated on).
|
|
2045
2043
|
*
|
|
2046
2044
|
* Examples:
|
|
2047
|
-
* /
|
|
2048
|
-
* /
|
|
2049
|
-
* /
|
|
2045
|
+
* /accounts/users → "Users"
|
|
2046
|
+
* /accounts/users/{id} → "User" (singular for item operations)
|
|
2047
|
+
* /accounts/users/{id}/auth_factors → "AuthFactors"
|
|
2050
2048
|
* /organizations/{id}/api_keys → "ApiKeys"
|
|
2051
|
-
* /
|
|
2049
|
+
* /auth/authorize → "Authorize" (action endpoint)
|
|
2052
2050
|
*/
|
|
2053
2051
|
function inferResourceFromPath(path) {
|
|
2054
2052
|
const segments = path.split("/").filter(Boolean);
|
|
@@ -3330,12 +3328,12 @@ function extractKotlinClassMembers(classBody, source) {
|
|
|
3330
3328
|
}
|
|
3331
3329
|
/**
|
|
3332
3330
|
* KDoc signature that identifies a generator-emitted service accessor inside
|
|
3333
|
-
*
|
|
3331
|
+
* a Kotlin client class. The Kotlin client emitter writes every accessor with this
|
|
3334
3332
|
* exact doc preamble — when the existing file carries a member with this doc
|
|
3335
3333
|
* but the regenerated content omits it, the member is stale (its mount target
|
|
3336
3334
|
* was remapped or removed) and gets pruned.
|
|
3337
3335
|
*/
|
|
3338
|
-
const KOTLIN_MANAGED_ACCESSOR_DOC = /\/\*\*[\s\S]*?Lazily-constructed \[[^\]]+\] accessor for this \[
|
|
3336
|
+
const KOTLIN_MANAGED_ACCESSOR_DOC = /\/\*\*[\s\S]*?Lazily-constructed \[[^\]]+\] accessor for this \[[^\]]+\] client\.[\s\S]*?\*\//;
|
|
3339
3337
|
const kotlinMergeAdapter = {
|
|
3340
3338
|
language: "kotlin",
|
|
3341
3339
|
grammarModule: "tree-sitter-kotlin",
|
|
@@ -3588,7 +3586,6 @@ const nodeMergeAdapter = {
|
|
|
3588
3586
|
const newText = newMembers.map((m) => m.text).join("\n");
|
|
3589
3587
|
for (const match of newText.matchAll(/this\.(\w+)/g)) {
|
|
3590
3588
|
const propName = match[1];
|
|
3591
|
-
if (propName === "workos") continue;
|
|
3592
3589
|
if (propName && !existingMemberKeys.has(propName)) return true;
|
|
3593
3590
|
}
|
|
3594
3591
|
return false;
|
|
@@ -5719,6 +5716,15 @@ function resolveResponseModelName(op) {
|
|
|
5719
5716
|
//#endregion
|
|
5720
5717
|
//#region src/engine/generate-files.ts
|
|
5721
5718
|
function buildEmitterContext(spec, options) {
|
|
5719
|
+
const resolvedOperations = resolveOperations(spec, options.operationHints, options.mountRules);
|
|
5720
|
+
let scopedModelNames;
|
|
5721
|
+
let scopedEnumNames;
|
|
5722
|
+
if (options.scopedServices && options.scopedServices.size > 0) {
|
|
5723
|
+
const selected = options.scopedServices;
|
|
5724
|
+
const reachable = collectReferencedNames(spec.services.filter((s) => resolvedOperations.some((r) => r.service.name === s.name && selected.has(r.mountOn))), spec.models, { preserveAllDiscriminated: false });
|
|
5725
|
+
scopedModelNames = reachable.models;
|
|
5726
|
+
scopedEnumNames = reachable.enums;
|
|
5727
|
+
}
|
|
5722
5728
|
return {
|
|
5723
5729
|
namespace: toSnakeCase(options.namespace),
|
|
5724
5730
|
namespacePascal: options.namespace,
|
|
@@ -5726,11 +5732,14 @@ function buildEmitterContext(spec, options) {
|
|
|
5726
5732
|
outputDir: options.outputDir,
|
|
5727
5733
|
apiSurface: options.apiSurface,
|
|
5728
5734
|
overlayLookup: options.overlayLookup,
|
|
5729
|
-
resolvedOperations
|
|
5735
|
+
resolvedOperations,
|
|
5730
5736
|
modelHints: options.modelHints,
|
|
5731
5737
|
emitterOptions: options.emitterOptions,
|
|
5732
5738
|
targetDir: options.target,
|
|
5733
|
-
priorTargetManifestPaths: options.priorTargetManifestPaths
|
|
5739
|
+
priorTargetManifestPaths: options.priorTargetManifestPaths,
|
|
5740
|
+
scopedServices: options.scopedServices,
|
|
5741
|
+
scopedModelNames,
|
|
5742
|
+
scopedEnumNames
|
|
5734
5743
|
};
|
|
5735
5744
|
}
|
|
5736
5745
|
/**
|
|
@@ -5738,7 +5747,8 @@ function buildEmitterContext(spec, options) {
|
|
|
5738
5747
|
* Walks operation parameters, request bodies, responses, and pagination item
|
|
5739
5748
|
* types, then chases model field references until the set stabilizes.
|
|
5740
5749
|
*/
|
|
5741
|
-
function collectReferencedNames(services, models) {
|
|
5750
|
+
function collectReferencedNames(services, models, opts = {}) {
|
|
5751
|
+
const preserveAllDiscriminated = opts.preserveAllDiscriminated !== false;
|
|
5742
5752
|
const referencedModels = /* @__PURE__ */ new Set();
|
|
5743
5753
|
const referencedEnums = /* @__PURE__ */ new Set();
|
|
5744
5754
|
const collectFromTypeRef = (ref) => {
|
|
@@ -5760,7 +5770,7 @@ function collectReferencedNames(services, models) {
|
|
|
5760
5770
|
for (const err of op.errors) if (err.type) collectFromTypeRef(err.type);
|
|
5761
5771
|
if (op.successResponses) for (const sr of op.successResponses) collectFromTypeRef(sr.type);
|
|
5762
5772
|
}
|
|
5763
|
-
for (const model of models) {
|
|
5773
|
+
if (preserveAllDiscriminated) for (const model of models) {
|
|
5764
5774
|
if (isDiscriminatedModel(model)) referencedModels.add(model.name);
|
|
5765
5775
|
const disc = model.discriminator;
|
|
5766
5776
|
if (disc?.mapping) for (const variantName of Object.values(disc.mapping)) referencedModels.add(variantName);
|
|
@@ -5774,6 +5784,13 @@ function collectReferencedNames(services, models) {
|
|
|
5774
5784
|
visited.add(name);
|
|
5775
5785
|
const model = modelsByName.get(name);
|
|
5776
5786
|
if (!model) continue;
|
|
5787
|
+
const disc = model.discriminator;
|
|
5788
|
+
if (disc?.mapping) {
|
|
5789
|
+
for (const variantName of Object.values(disc.mapping)) if (!referencedModels.has(variantName)) {
|
|
5790
|
+
referencedModels.add(variantName);
|
|
5791
|
+
queue.push(variantName);
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5777
5794
|
for (const field of model.fields) {
|
|
5778
5795
|
collectFromTypeRef(field.type);
|
|
5779
5796
|
for (const m of referencedModels) if (!visited.has(m)) queue.push(m);
|
|
@@ -5787,7 +5804,16 @@ function collectReferencedNames(services, models) {
|
|
|
5787
5804
|
function isDiscriminatedModel(model) {
|
|
5788
5805
|
return model.fields.some((field) => (field.name === "event" || field.name === "type" || field.name === "object") && field.type.kind === "literal");
|
|
5789
5806
|
}
|
|
5790
|
-
/**
|
|
5807
|
+
/**
|
|
5808
|
+
* Collect all generated files from an emitter (no headers, no path prefixes).
|
|
5809
|
+
*
|
|
5810
|
+
* In a scoped (`--services`) run the spec is NOT filtered: every generator runs
|
|
5811
|
+
* over the full spec so model placement, dedup, the root client, and all
|
|
5812
|
+
* aggregate/barrel files are byte-identical to a full run (and a brand-new
|
|
5813
|
+
* selected service is wired into the client automatically). The emitters
|
|
5814
|
+
* themselves consult `ctx.scopedServices` to emit only the selected services'
|
|
5815
|
+
* per-service resource/test files.
|
|
5816
|
+
*/
|
|
5791
5817
|
function generateAllFiles(spec, emitter, ctx) {
|
|
5792
5818
|
const referenced = collectReferencedNames(spec.services, spec.models);
|
|
5793
5819
|
const reachableModels = spec.models.filter((m) => referenced.models.has(m.name));
|
|
@@ -5828,6 +5854,47 @@ function generateFiles(spec, emitter, options) {
|
|
|
5828
5854
|
};
|
|
5829
5855
|
}
|
|
5830
5856
|
//#endregion
|
|
5857
|
+
//#region src/engine/scoped-services.ts
|
|
5858
|
+
/**
|
|
5859
|
+
* Validate a `--services` selection and return it as a set of POST-MOUNT service
|
|
5860
|
+
* names for emitters to scope on.
|
|
5861
|
+
*
|
|
5862
|
+
* Scoped generation does NOT filter the IR: model placement, dedup/aliasing, and
|
|
5863
|
+
* shared-schema decisions are global computations over the full service/model set
|
|
5864
|
+
* (filtering them churns shared `common/` files). Instead the full spec flows to
|
|
5865
|
+
* the emitters unchanged and this set is passed as `ctx.scopedServices`; each
|
|
5866
|
+
* emitter gates only its per-service resource/test emission on it.
|
|
5867
|
+
*
|
|
5868
|
+
* Matching is on the POST-MOUNT name. Mount resolution never mutates
|
|
5869
|
+
* `spec.services` — the post-mount target lives on `ResolvedOperation.mountOn`
|
|
5870
|
+
* (`mountOn = hint?.mountOn ?? mountRules[service.name] ?? service.name`) — so the
|
|
5871
|
+
* post-mount name of a source service is derived here as
|
|
5872
|
+
* `mountRules[service.name] ?? service.name`. Selecting a target (e.g.
|
|
5873
|
+
* `DirectorySync`) is valid because every source tag that mounts into it
|
|
5874
|
+
* (`DirectoryUsers`, `DirectoryGroups`, …) shares that post-mount name, and the
|
|
5875
|
+
* emitters group resources by mount, so all siblings emit together.
|
|
5876
|
+
*
|
|
5877
|
+
* @param spec - The parsed API spec (never mutated; never filtered).
|
|
5878
|
+
* @param selected - Post-mount service names from `--services` (normalized/trimmed).
|
|
5879
|
+
* @param mountRules - Source-service-name → post-mount-target mappings.
|
|
5880
|
+
* @returns The validated selection as a `Set` of post-mount names.
|
|
5881
|
+
* @throws A `ConfigError` listing valid post-mount names when a selection is
|
|
5882
|
+
* unknown — scoped generation never silently emits the wrong set.
|
|
5883
|
+
*/
|
|
5884
|
+
function resolveScopedServices(spec, selected, mountRules) {
|
|
5885
|
+
const mounts = mountRules ?? {};
|
|
5886
|
+
const postMountName = (name) => mounts[name] ?? name;
|
|
5887
|
+
const validPostMount = new Set(spec.services.map((s) => postMountName(s.name)));
|
|
5888
|
+
const selectedSet = new Set(selected);
|
|
5889
|
+
const unknown = [...selectedSet].filter((s) => !validPostMount.has(s));
|
|
5890
|
+
if (unknown.length > 0) {
|
|
5891
|
+
const err = /* @__PURE__ */ new Error(`Unknown --services: ${unknown.sort().join(", ")}. Valid post-mount services: ${[...validPostMount].sort().join(", ")}`);
|
|
5892
|
+
err.name = "ConfigError";
|
|
5893
|
+
throw err;
|
|
5894
|
+
}
|
|
5895
|
+
return selectedSet;
|
|
5896
|
+
}
|
|
5897
|
+
//#endregion
|
|
5831
5898
|
//#region src/engine/integrate.ts
|
|
5832
5899
|
/**
|
|
5833
5900
|
* Resolve a relative import path against a source file path to get the
|
|
@@ -5944,7 +6011,7 @@ function mapFilesForTargetIntegration(files, language) {
|
|
|
5944
6011
|
* that would shadow an existing module file (e.g., `api_keys.py`), move the
|
|
5945
6012
|
* module file's content into `_compat.py` inside the package and add a
|
|
5946
6013
|
* re-export from `__init__.py`. This preserves backward-compatible imports
|
|
5947
|
-
* (e.g., `from
|
|
6014
|
+
* (e.g., `from acme.api_keys import ApiKeysModule`) while the package directory
|
|
5948
6015
|
* provides the generated models and resource classes.
|
|
5949
6016
|
*/
|
|
5950
6017
|
async function resolveFileToDirectoryConflicts(files, targetDir) {
|
|
@@ -6110,6 +6177,35 @@ function computeStalePaths(prev, currentPaths) {
|
|
|
6110
6177
|
return prev.files.filter((p) => !current.has(p)).sort();
|
|
6111
6178
|
}
|
|
6112
6179
|
/**
|
|
6180
|
+
* Merge a scoped run's emitted records into the prior manifest's records.
|
|
6181
|
+
*
|
|
6182
|
+
* A scoped (`--services`) run only emits files for the selected services, but it
|
|
6183
|
+
* must NOT discard the prior manifest's record of unselected services' files
|
|
6184
|
+
* records. The result unions prior + scoped file paths and shallow-merges the
|
|
6185
|
+
* operations maps, with scoped operations overriding prior entries for the same
|
|
6186
|
+
* "METHOD /path" key (a re-emission updates that operation's record).
|
|
6187
|
+
*
|
|
6188
|
+
* When `prev` is null (the first-ever run is scoped) there is nothing to
|
|
6189
|
+
* preserve, so the scoped subset is returned unchanged — matching the
|
|
6190
|
+
* first-adoption behavior of a full run.
|
|
6191
|
+
*/
|
|
6192
|
+
function mergeScopedManifestRecords(prev, scopedFiles, scopedOperations) {
|
|
6193
|
+
const scopedFileList = [...scopedFiles];
|
|
6194
|
+
if (!prev) return {
|
|
6195
|
+
files: scopedFileList,
|
|
6196
|
+
operations: scopedOperations
|
|
6197
|
+
};
|
|
6198
|
+
const files = [...new Set([...prev.files, ...scopedFileList])].sort();
|
|
6199
|
+
const priorOps = prev.operations;
|
|
6200
|
+
return {
|
|
6201
|
+
files,
|
|
6202
|
+
operations: priorOps || scopedOperations ? {
|
|
6203
|
+
...priorOps,
|
|
6204
|
+
...scopedOperations
|
|
6205
|
+
} : void 0
|
|
6206
|
+
};
|
|
6207
|
+
}
|
|
6208
|
+
/**
|
|
6113
6209
|
* Delete stale files from `dir`.
|
|
6114
6210
|
*
|
|
6115
6211
|
* When `header` is provided, a file is only deleted if its contents start with
|
|
@@ -6178,12 +6274,17 @@ async function removeEmptyParents(startDir, stopDir) {
|
|
|
6178
6274
|
//#endregion
|
|
6179
6275
|
//#region src/engine/orchestrator.ts
|
|
6180
6276
|
async function generate(spec, emitter, options) {
|
|
6181
|
-
const
|
|
6182
|
-
const
|
|
6277
|
+
const scopedServices = options.services && options.services.length > 0 ? resolveScopedServices(spec, options.services, options.mountRules) : void 0;
|
|
6278
|
+
const scoped = scopedServices !== void 0;
|
|
6279
|
+
const noPrune = options.noPrune === true || scoped;
|
|
6280
|
+
const skipManifestRead = options.noPrune === true && !scoped;
|
|
6281
|
+
const outputPrevManifestForCtx = skipManifestRead ? null : await readManifest(options.outputDir);
|
|
6282
|
+
const targetManifestForCtx = options.target && !skipManifestRead ? await readManifest(options.target) : null;
|
|
6183
6283
|
const priorTargetManifestPaths = targetManifestForCtx ? new Set(targetManifestForCtx.files) : outputPrevManifestForCtx ? new Set(outputPrevManifestForCtx.files) : void 0;
|
|
6184
6284
|
const { files: withHeaders, header, operations } = generateFiles(spec, emitter, {
|
|
6185
6285
|
...options,
|
|
6186
|
-
priorTargetManifestPaths
|
|
6286
|
+
priorTargetManifestPaths,
|
|
6287
|
+
scopedServices
|
|
6187
6288
|
});
|
|
6188
6289
|
if (options.dryRun) {
|
|
6189
6290
|
if (options.target) {
|
|
@@ -6207,8 +6308,9 @@ async function generate(spec, emitter, options) {
|
|
|
6207
6308
|
currentPaths: outputEmittedPaths,
|
|
6208
6309
|
language: emitter.language,
|
|
6209
6310
|
header,
|
|
6210
|
-
noPrune
|
|
6211
|
-
operations
|
|
6311
|
+
noPrune,
|
|
6312
|
+
operations,
|
|
6313
|
+
scoped
|
|
6212
6314
|
});
|
|
6213
6315
|
const allOutputFiles = [...writeResult.written, ...writeResult.merged];
|
|
6214
6316
|
if (allOutputFiles.length > 0) await formatTargetFiles(emitter, options.outputDir, allOutputFiles);
|
|
@@ -6230,8 +6332,9 @@ async function generate(spec, emitter, options) {
|
|
|
6230
6332
|
currentPaths: targetResult.emittedPaths,
|
|
6231
6333
|
language: emitter.language,
|
|
6232
6334
|
header,
|
|
6233
|
-
noPrune
|
|
6234
|
-
operations
|
|
6335
|
+
noPrune,
|
|
6336
|
+
operations,
|
|
6337
|
+
scoped
|
|
6235
6338
|
});
|
|
6236
6339
|
const allTargetFiles = [
|
|
6237
6340
|
...targetResult.written,
|
|
@@ -6247,11 +6350,15 @@ async function generate(spec, emitter, options) {
|
|
|
6247
6350
|
* then write the fresh manifest. Safe on first adoption (no previous manifest).
|
|
6248
6351
|
*/
|
|
6249
6352
|
async function applyManifestPrune(opts) {
|
|
6250
|
-
const
|
|
6251
|
-
language: opts.language,
|
|
6353
|
+
const records = opts.scoped ? mergeScopedManifestRecords(opts.prevManifest, opts.currentPaths, opts.operations) : {
|
|
6252
6354
|
files: opts.currentPaths,
|
|
6253
6355
|
operations: opts.operations
|
|
6254
6356
|
};
|
|
6357
|
+
const manifestOpts = {
|
|
6358
|
+
language: opts.language,
|
|
6359
|
+
files: records.files,
|
|
6360
|
+
operations: records.operations
|
|
6361
|
+
};
|
|
6255
6362
|
if (opts.noPrune) {
|
|
6256
6363
|
await writeManifest(opts.dir, manifestOpts);
|
|
6257
6364
|
return;
|
|
@@ -6891,7 +6998,7 @@ function loadOverlayContext(opts) {
|
|
|
6891
6998
|
//#region src/utils/expand-doc-urls.ts
|
|
6892
6999
|
/**
|
|
6893
7000
|
* Walk all description fields in the IR and expand relative URL paths
|
|
6894
|
-
* (e.g. `/reference/
|
|
7001
|
+
* (e.g. `/reference/users/user`) into full URLs using the given base.
|
|
6895
7002
|
*
|
|
6896
7003
|
* Matches markdown-style links: `[text](/path)` → `[text](https://example.com/path)`
|
|
6897
7004
|
* and bare parenthetical refs: `(/path)` → `(https://example.com/path)`
|
|
@@ -6954,6 +7061,7 @@ async function generateCommand(opts) {
|
|
|
6954
7061
|
if (opts.docUrl) ir = expandDocUrls(ir, opts.docUrl);
|
|
6955
7062
|
const emitter = getEmitter(opts.lang);
|
|
6956
7063
|
const namespace = opts.namespace ?? ir.name;
|
|
7064
|
+
const services = (Array.isArray(opts.services) ? opts.services : opts.services?.split(","))?.map((s) => s.trim()).filter(Boolean);
|
|
6957
7065
|
let apiSurface;
|
|
6958
7066
|
let overlayLookup;
|
|
6959
7067
|
if (opts.apiSurface && opts.compatCheck !== false) {
|
|
@@ -6977,7 +7085,8 @@ async function generateCommand(opts) {
|
|
|
6977
7085
|
mountRules: opts.mountRules,
|
|
6978
7086
|
modelHints: opts.modelHints,
|
|
6979
7087
|
emitterOptions: opts.emitterOptions,
|
|
6980
|
-
noPrune: opts.prune === false
|
|
7088
|
+
noPrune: opts.prune === false,
|
|
7089
|
+
services: services && services.length > 0 ? services : void 0
|
|
6981
7090
|
});
|
|
6982
7091
|
if (opts.dryRun) for (const f of files) console.log(f.path);
|
|
6983
7092
|
else {
|
|
@@ -8240,7 +8349,7 @@ function specDerivedFieldPaths(spec, hints) {
|
|
|
8240
8349
|
return paths;
|
|
8241
8350
|
}
|
|
8242
8351
|
/**
|
|
8243
|
-
* Compute the set of method paths (e.g., "
|
|
8352
|
+
* Compute the set of method paths (e.g., "Auth.authorize") that are defined
|
|
8244
8353
|
* as operations in the OpenAPI spec's services. Used by filterSurface to
|
|
8245
8354
|
* exclude hand-written SDK methods that don't correspond to spec operations.
|
|
8246
8355
|
*/
|
|
@@ -8276,7 +8385,7 @@ function filterRecord(record, allowed) {
|
|
|
8276
8385
|
*
|
|
8277
8386
|
* For classes: when methodPaths is provided, only keeps methods whose names
|
|
8278
8387
|
* appear in the spec-derived method paths. This prevents false positives from
|
|
8279
|
-
* hand-written SDK methods (e.g.,
|
|
8388
|
+
* hand-written SDK methods (e.g., Auth.authorization_url, Webhooks.construct_event)
|
|
8280
8389
|
* that don't correspond to spec operations. Properties on service classes are
|
|
8281
8390
|
* also filtered: only UPPER_CASE constants that are spec-derivable are kept.
|
|
8282
8391
|
*/
|
|
@@ -9955,7 +10064,7 @@ program.command("parse").description("Parse an OpenAPI spec and output IR as JSO
|
|
|
9955
10064
|
transformSpec: configTransformSpec
|
|
9956
10065
|
});
|
|
9957
10066
|
});
|
|
9958
|
-
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) => {
|
|
10067
|
+
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").option("--services <list>", "Comma-separated post-mount service names to generate (default: all)").action((opts) => {
|
|
9959
10068
|
opts.spec ??= process.env.OPENAPI_SPEC_PATH;
|
|
9960
10069
|
if (!opts.spec) {
|
|
9961
10070
|
console.error("error: --spec <path> or OPENAPI_SPEC_PATH env var is required");
|