@workos/oagen 0.29.3 → 0.30.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/dist/cli/index.mjs +184 -6
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{diff-DqnHHXvr.mjs → diff-DMFiDAI6.mjs} +177 -7
- package/dist/diff-DMFiDAI6.mjs.map +1 -0
- package/dist/index.d.mts +30 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/verify/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/diff-DqnHHXvr.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -3775,6 +3775,7 @@ function extractKotlinClassMembers(classBody, source) {
|
|
|
3775
3775
|
const children = classBody.namedChildren;
|
|
3776
3776
|
for (let i = 0; i < children.length; i++) {
|
|
3777
3777
|
const child = children[i];
|
|
3778
|
+
const declIndex = i;
|
|
3778
3779
|
let memberName = null;
|
|
3779
3780
|
if (child.type === "property_declaration") memberName = child.children.find((c) => c.type === "variable_declaration")?.children.find((c) => c.type === "simple_identifier")?.text ?? null;
|
|
3780
3781
|
else if (child.type === "function_declaration") memberName = child.children.find((c) => c.type === "simple_identifier")?.text ?? null;
|
|
@@ -3789,7 +3790,7 @@ function extractKotlinClassMembers(classBody, source) {
|
|
|
3789
3790
|
} else break;
|
|
3790
3791
|
}
|
|
3791
3792
|
let startIdx = child.startIndex;
|
|
3792
|
-
for (let k =
|
|
3793
|
+
for (let k = declIndex - 1; k >= 0; k--) {
|
|
3793
3794
|
const prev = children[k];
|
|
3794
3795
|
if (prev.type === "multiline_comment") {
|
|
3795
3796
|
if (source.slice(prev.startIndex, prev.endIndex).startsWith("/**")) startIdx = prev.startIndex;
|
|
@@ -3858,6 +3859,13 @@ const kotlinMergeAdapter = {
|
|
|
3858
3859
|
isManagedMember(member) {
|
|
3859
3860
|
return KOTLIN_MANAGED_ACCESSOR_DOC.test(member.text);
|
|
3860
3861
|
},
|
|
3862
|
+
importedNames(imp) {
|
|
3863
|
+
const path = imp.text.replace(/^import\s+/, "").trim();
|
|
3864
|
+
const alias = path.match(/\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
3865
|
+
if (alias) return [alias[1]];
|
|
3866
|
+
const last = path.split(".").pop();
|
|
3867
|
+
return last && last !== "*" ? [last] : [];
|
|
3868
|
+
},
|
|
3861
3869
|
shouldSkipDeepMerge(_symbolName, existingMemberKeys, newMembers) {
|
|
3862
3870
|
const newText = newMembers.map((m) => m.text).join("\n");
|
|
3863
3871
|
for (const match of newText.matchAll(/this\.(\w+)/g)) {
|
|
@@ -4740,6 +4748,116 @@ function declarationLineStart(content, declStartIndex) {
|
|
|
4740
4748
|
return prevNewline === -1 ? 0 : prevNewline + 1;
|
|
4741
4749
|
}
|
|
4742
4750
|
/**
|
|
4751
|
+
* Widen a member's byte range so deleting it removes whole lines.
|
|
4752
|
+
*
|
|
4753
|
+
* The raw range starts at the declaration (or its docstring), which sits after
|
|
4754
|
+
* the member's indentation — splicing it out verbatim leaves that indentation
|
|
4755
|
+
* glued to the following line. Extends backwards over the indentation and
|
|
4756
|
+
* forwards over the trailing newline, then absorbs the blank line the removal
|
|
4757
|
+
* would otherwise duplicate (or strand in front of the block's closing brace).
|
|
4758
|
+
*/
|
|
4759
|
+
function expandPruneRange(source, rawStart, rawEnd) {
|
|
4760
|
+
let start = rawStart;
|
|
4761
|
+
while (start > 0 && (source[start - 1] === " " || source[start - 1] === " ")) start--;
|
|
4762
|
+
let end = rawEnd;
|
|
4763
|
+
while (end < source.length && (source[end] === " " || source[end] === " ")) end++;
|
|
4764
|
+
end = skipLineBreak(source, end);
|
|
4765
|
+
if (precededByBlankLine(source, start)) {
|
|
4766
|
+
if (startsWithLineBreak(source, end)) end = skipLineBreak(source, end);
|
|
4767
|
+
else if (/^[ \t]*\}/.test(source.slice(end, end + 40))) start = backOverLineBreak(source, start);
|
|
4768
|
+
}
|
|
4769
|
+
return {
|
|
4770
|
+
start,
|
|
4771
|
+
end
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
function startsWithLineBreak(source, idx) {
|
|
4775
|
+
return source[idx] === "\n" || source[idx] === "\r" && source[idx + 1] === "\n";
|
|
4776
|
+
}
|
|
4777
|
+
function skipLineBreak(source, idx) {
|
|
4778
|
+
let i = idx;
|
|
4779
|
+
if (source[i] === "\r") i++;
|
|
4780
|
+
return source[i] === "\n" ? i + 1 : i;
|
|
4781
|
+
}
|
|
4782
|
+
function backOverLineBreak(source, idx) {
|
|
4783
|
+
let i = idx;
|
|
4784
|
+
if (source[i - 1] === "\n") i--;
|
|
4785
|
+
if (source[i - 1] === "\r") i--;
|
|
4786
|
+
return i;
|
|
4787
|
+
}
|
|
4788
|
+
/** Whether the line ending immediately before `idx` (a line start) is empty. */
|
|
4789
|
+
function precededByBlankLine(source, idx) {
|
|
4790
|
+
if (idx === 0 || source[idx - 1] !== "\n") return false;
|
|
4791
|
+
const priorLineEnd = backOverLineBreak(source, idx);
|
|
4792
|
+
return priorLineEnd > 0 && source[priorLineEnd - 1] === "\n";
|
|
4793
|
+
}
|
|
4794
|
+
function referencesIdentifier(source, name) {
|
|
4795
|
+
return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(source);
|
|
4796
|
+
}
|
|
4797
|
+
/**
|
|
4798
|
+
* Blank out comments and string text, preserving offsets and line structure.
|
|
4799
|
+
*
|
|
4800
|
+
* An identifier lookup over raw source can't tell a live reference from a name
|
|
4801
|
+
* that merely survives in prose. That distinction decides whether an import
|
|
4802
|
+
* stays: keeping one whose only remaining mention is a KDoc leaves an import of
|
|
4803
|
+
* a class the generator no longer emits, which doesn't compile — the very
|
|
4804
|
+
* failure the prune exists to prevent.
|
|
4805
|
+
*
|
|
4806
|
+
* Interpolated expressions are the exception: `"${Agents.NAME}"` is code that
|
|
4807
|
+
* sits inside a string literal, and masking it would drop an import the file
|
|
4808
|
+
* genuinely uses. String extents are masked around those holes, not over them.
|
|
4809
|
+
*
|
|
4810
|
+
* Grammar node names come from the adapter's `urlFingerprintConfig`, which
|
|
4811
|
+
* already inventories exactly these three categories per language. Comments are
|
|
4812
|
+
* matched structurally instead — every grammar we support names them `*comment`.
|
|
4813
|
+
*/
|
|
4814
|
+
function maskNonCode(tree, source, config) {
|
|
4815
|
+
const stringNodeTypes = new Set(config?.stringNodeTypes ?? []);
|
|
4816
|
+
const interpolationNodeTypes = new Set(config?.interpolationNodeTypes ?? []);
|
|
4817
|
+
const spans = [];
|
|
4818
|
+
const collectStringSpans = (node) => {
|
|
4819
|
+
const holes = [];
|
|
4820
|
+
const findHoles = (n) => {
|
|
4821
|
+
if (interpolationNodeTypes.has(n.type)) {
|
|
4822
|
+
holes.push([n.startIndex, n.endIndex]);
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
for (const child of n.children) findHoles(child);
|
|
4826
|
+
};
|
|
4827
|
+
findHoles(node);
|
|
4828
|
+
let cursor = node.startIndex;
|
|
4829
|
+
for (const [holeStart, holeEnd] of holes) {
|
|
4830
|
+
if (holeStart > cursor) spans.push([cursor, holeStart]);
|
|
4831
|
+
cursor = holeEnd;
|
|
4832
|
+
}
|
|
4833
|
+
if (cursor < node.endIndex) spans.push([cursor, node.endIndex]);
|
|
4834
|
+
};
|
|
4835
|
+
const walk = (node) => {
|
|
4836
|
+
if (node.type.includes("comment")) {
|
|
4837
|
+
spans.push([node.startIndex, node.endIndex]);
|
|
4838
|
+
return;
|
|
4839
|
+
}
|
|
4840
|
+
if (stringNodeTypes.has(node.type)) {
|
|
4841
|
+
collectStringSpans(node);
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
for (const child of node.children) walk(child);
|
|
4845
|
+
};
|
|
4846
|
+
walk(tree.rootNode);
|
|
4847
|
+
if (spans.length === 0) return source;
|
|
4848
|
+
let masked = "";
|
|
4849
|
+
let cursor = 0;
|
|
4850
|
+
for (const [spanStart, spanEnd] of spans) {
|
|
4851
|
+
if (spanStart < cursor) continue;
|
|
4852
|
+
masked += source.slice(cursor, spanStart) + source.slice(spanStart, spanEnd).replace(/[^\r\n]/g, " ");
|
|
4853
|
+
cursor = spanEnd;
|
|
4854
|
+
}
|
|
4855
|
+
return masked + source.slice(cursor);
|
|
4856
|
+
}
|
|
4857
|
+
function removeLines(source, lines) {
|
|
4858
|
+
return source.split("\n").filter((line) => !lines.has(line.trim())).join("\n");
|
|
4859
|
+
}
|
|
4860
|
+
/**
|
|
4743
4861
|
* Resolve a docstring range by searching for the nearest matching text before
|
|
4744
4862
|
* the declaration start. Falls back to parser-provided offsets when search fails.
|
|
4745
4863
|
*/
|
|
@@ -4954,6 +5072,7 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
4954
5072
|
let deepAdded = 0;
|
|
4955
5073
|
let deepPruned = 0;
|
|
4956
5074
|
const insertions = [];
|
|
5075
|
+
const prunedTexts = [];
|
|
4957
5076
|
if (adapter.extractMembers) {
|
|
4958
5077
|
const parser = await getParser(language);
|
|
4959
5078
|
const resultTree = safeParse(parser, result);
|
|
@@ -4982,10 +5101,9 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
4982
5101
|
if (pruneEdits.length > 0) {
|
|
4983
5102
|
pruneEdits.sort((a, b) => b.start - a.start);
|
|
4984
5103
|
for (const edit of pruneEdits) {
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
result = result.slice(0, edit.start) + result.slice(end);
|
|
5104
|
+
const { start, end } = expandPruneRange(result, edit.start, edit.end);
|
|
5105
|
+
prunedTexts.push(result.slice(start, end));
|
|
5106
|
+
result = result.slice(0, start) + result.slice(end);
|
|
4989
5107
|
}
|
|
4990
5108
|
deepPruned = pruneEdits.length;
|
|
4991
5109
|
const reTree = safeParse(parser, result);
|
|
@@ -5050,6 +5168,21 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
5050
5168
|
result = lines.join("\n");
|
|
5051
5169
|
}
|
|
5052
5170
|
}
|
|
5171
|
+
if (prunedTexts.length > 0 && adapter.importedNames) {
|
|
5172
|
+
const prunedText = prunedTexts.join("\n");
|
|
5173
|
+
const codeOnly = maskNonCode(safeParse(await getParser(language), result), result, adapter.urlFingerprintConfig);
|
|
5174
|
+
const orphaned = /* @__PURE__ */ new Set();
|
|
5175
|
+
for (const imp of existingStatements.imports) {
|
|
5176
|
+
const names = adapter.importedNames(imp);
|
|
5177
|
+
if (names.length === 0) continue;
|
|
5178
|
+
if (!names.some((n) => referencesIdentifier(prunedText, n))) continue;
|
|
5179
|
+
const line = imp.text.trim();
|
|
5180
|
+
const withoutImport = removeLines(codeOnly, new Set([line]));
|
|
5181
|
+
if (names.some((n) => referencesIdentifier(withoutImport, n))) continue;
|
|
5182
|
+
orphaned.add(line);
|
|
5183
|
+
}
|
|
5184
|
+
if (orphaned.size > 0) result = removeLines(result, orphaned);
|
|
5185
|
+
}
|
|
5053
5186
|
let docstringUpdates = 0;
|
|
5054
5187
|
{
|
|
5055
5188
|
const parser = await getParser(language);
|
|
@@ -6227,6 +6360,43 @@ function resolveResponseModelName(op) {
|
|
|
6227
6360
|
if (ref.kind === "nullable" && ref.inner.kind === "model") return ref.inner.name;
|
|
6228
6361
|
return null;
|
|
6229
6362
|
}
|
|
6363
|
+
/**
|
|
6364
|
+
* Find every pair of distinct paths that resolve to the same SDK method name.
|
|
6365
|
+
*
|
|
6366
|
+
* Emitters already refuse to generate on a collision, but they only run inside
|
|
6367
|
+
* the per-language build matrix — so a spec addition that collides fails every
|
|
6368
|
+
* matrix job minutes in, with no cheap way to catch it first. Surfacing the same
|
|
6369
|
+
* check from `oagen resolve` turns that into a seconds-long precheck.
|
|
6370
|
+
*
|
|
6371
|
+
* Returns every collision rather than throwing on the first, so a spec that
|
|
6372
|
+
* introduces several is fixed in one pass instead of one CI round-trip each.
|
|
6373
|
+
*
|
|
6374
|
+
* Callers that generate a subset of services must filter to the operations they
|
|
6375
|
+
* will actually emit before calling this: a collision between two out-of-scope
|
|
6376
|
+
* paths is not that run's problem.
|
|
6377
|
+
*/
|
|
6378
|
+
function findResolvedMethodCollisions(resolved) {
|
|
6379
|
+
const seen = /* @__PURE__ */ new Map();
|
|
6380
|
+
const collisions = [];
|
|
6381
|
+
for (const r of resolved) {
|
|
6382
|
+
const key = `${r.mountOn}.${r.methodName}`;
|
|
6383
|
+
const current = {
|
|
6384
|
+
httpMethod: r.operation.httpMethod.toUpperCase(),
|
|
6385
|
+
path: r.operation.path
|
|
6386
|
+
};
|
|
6387
|
+
const existing = seen.get(key);
|
|
6388
|
+
if (existing && (existing.path !== current.path || existing.httpMethod !== current.httpMethod)) {
|
|
6389
|
+
collisions.push({
|
|
6390
|
+
key,
|
|
6391
|
+
first: existing,
|
|
6392
|
+
second: current
|
|
6393
|
+
});
|
|
6394
|
+
continue;
|
|
6395
|
+
}
|
|
6396
|
+
if (!existing) seen.set(key, current);
|
|
6397
|
+
}
|
|
6398
|
+
return collisions;
|
|
6399
|
+
}
|
|
6230
6400
|
//#endregion
|
|
6231
6401
|
//#region src/engine/scoped-services.ts
|
|
6232
6402
|
/**
|
|
@@ -10777,6 +10947,11 @@ async function resolveCommand(opts) {
|
|
|
10777
10947
|
});
|
|
10778
10948
|
if (opts.docUrl) ir = expandDocUrls(ir, opts.docUrl);
|
|
10779
10949
|
const resolved = resolveOperations(ir, opts.operationHints, opts.mountRules);
|
|
10950
|
+
const collisions = findResolvedMethodCollisions(resolved);
|
|
10951
|
+
if (collisions.length > 0) {
|
|
10952
|
+
const detail = collisions.map((c) => ` ${c.key}: ${c.first.httpMethod} ${c.first.path} conflicts with ${c.second.httpMethod} ${c.second.path}`).join("\n");
|
|
10953
|
+
throw new CommandError(`Resolved operation name collision${collisions.length > 1 ? "s" : ""}:\n${detail}`, "Give one of each pair an explicit `name` in operationHints. Rename the NEW path — renaming an already-shipped method breaks released SDKs.", 1);
|
|
10954
|
+
}
|
|
10780
10955
|
if ((opts.format ?? "table") === "json") {
|
|
10781
10956
|
const output = resolved.map(toJsonRow);
|
|
10782
10957
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -10861,7 +11036,10 @@ function applyConfig(config) {
|
|
|
10861
11036
|
function handleError(err) {
|
|
10862
11037
|
const exitCode = err instanceof CommandError ? err.exitCode : 1;
|
|
10863
11038
|
const message = err instanceof Error ? err.message : String(err);
|
|
10864
|
-
if (message)
|
|
11039
|
+
if (message) {
|
|
11040
|
+
const label = err instanceof CommandError ? "" : `${err instanceof Error ? err.name : "Error"}: `;
|
|
11041
|
+
console.error(`${label}${message}`);
|
|
11042
|
+
}
|
|
10865
11043
|
process.exit(exitCode);
|
|
10866
11044
|
}
|
|
10867
11045
|
const configArgIdx = process.argv.indexOf("--config");
|