@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
|
@@ -177,6 +177,43 @@ function resolveResponseModelName(op) {
|
|
|
177
177
|
if (ref.kind === "nullable" && ref.inner.kind === "model") return ref.inner.name;
|
|
178
178
|
return null;
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Find every pair of distinct paths that resolve to the same SDK method name.
|
|
182
|
+
*
|
|
183
|
+
* Emitters already refuse to generate on a collision, but they only run inside
|
|
184
|
+
* the per-language build matrix — so a spec addition that collides fails every
|
|
185
|
+
* matrix job minutes in, with no cheap way to catch it first. Surfacing the same
|
|
186
|
+
* check from `oagen resolve` turns that into a seconds-long precheck.
|
|
187
|
+
*
|
|
188
|
+
* Returns every collision rather than throwing on the first, so a spec that
|
|
189
|
+
* introduces several is fixed in one pass instead of one CI round-trip each.
|
|
190
|
+
*
|
|
191
|
+
* Callers that generate a subset of services must filter to the operations they
|
|
192
|
+
* will actually emit before calling this: a collision between two out-of-scope
|
|
193
|
+
* paths is not that run's problem.
|
|
194
|
+
*/
|
|
195
|
+
function findResolvedMethodCollisions(resolved) {
|
|
196
|
+
const seen = /* @__PURE__ */ new Map();
|
|
197
|
+
const collisions = [];
|
|
198
|
+
for (const r of resolved) {
|
|
199
|
+
const key = `${r.mountOn}.${r.methodName}`;
|
|
200
|
+
const current = {
|
|
201
|
+
httpMethod: r.operation.httpMethod.toUpperCase(),
|
|
202
|
+
path: r.operation.path
|
|
203
|
+
};
|
|
204
|
+
const existing = seen.get(key);
|
|
205
|
+
if (existing && (existing.path !== current.path || existing.httpMethod !== current.httpMethod)) {
|
|
206
|
+
collisions.push({
|
|
207
|
+
key,
|
|
208
|
+
first: existing,
|
|
209
|
+
second: current
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!existing) seen.set(key, current);
|
|
214
|
+
}
|
|
215
|
+
return collisions;
|
|
216
|
+
}
|
|
180
217
|
//#endregion
|
|
181
218
|
//#region src/engine/merge-adapters/url-fingerprint.ts
|
|
182
219
|
/**
|
|
@@ -714,6 +751,7 @@ function extractKotlinClassMembers(classBody, source) {
|
|
|
714
751
|
const children = classBody.namedChildren;
|
|
715
752
|
for (let i = 0; i < children.length; i++) {
|
|
716
753
|
const child = children[i];
|
|
754
|
+
const declIndex = i;
|
|
717
755
|
let memberName = null;
|
|
718
756
|
if (child.type === "property_declaration") memberName = child.children.find((c) => c.type === "variable_declaration")?.children.find((c) => c.type === "simple_identifier")?.text ?? null;
|
|
719
757
|
else if (child.type === "function_declaration") memberName = child.children.find((c) => c.type === "simple_identifier")?.text ?? null;
|
|
@@ -728,7 +766,7 @@ function extractKotlinClassMembers(classBody, source) {
|
|
|
728
766
|
} else break;
|
|
729
767
|
}
|
|
730
768
|
let startIdx = child.startIndex;
|
|
731
|
-
for (let k =
|
|
769
|
+
for (let k = declIndex - 1; k >= 0; k--) {
|
|
732
770
|
const prev = children[k];
|
|
733
771
|
if (prev.type === "multiline_comment") {
|
|
734
772
|
if (source.slice(prev.startIndex, prev.endIndex).startsWith("/**")) startIdx = prev.startIndex;
|
|
@@ -797,6 +835,13 @@ const kotlinMergeAdapter = {
|
|
|
797
835
|
isManagedMember(member) {
|
|
798
836
|
return KOTLIN_MANAGED_ACCESSOR_DOC.test(member.text);
|
|
799
837
|
},
|
|
838
|
+
importedNames(imp) {
|
|
839
|
+
const path = imp.text.replace(/^import\s+/, "").trim();
|
|
840
|
+
const alias = path.match(/\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
841
|
+
if (alias) return [alias[1]];
|
|
842
|
+
const last = path.split(".").pop();
|
|
843
|
+
return last && last !== "*" ? [last] : [];
|
|
844
|
+
},
|
|
800
845
|
shouldSkipDeepMerge(_symbolName, existingMemberKeys, newMembers) {
|
|
801
846
|
const newText = newMembers.map((m) => m.text).join("\n");
|
|
802
847
|
for (const match of newText.matchAll(/this\.(\w+)/g)) {
|
|
@@ -1672,6 +1717,116 @@ function declarationLineStart(content, declStartIndex) {
|
|
|
1672
1717
|
return prevNewline === -1 ? 0 : prevNewline + 1;
|
|
1673
1718
|
}
|
|
1674
1719
|
/**
|
|
1720
|
+
* Widen a member's byte range so deleting it removes whole lines.
|
|
1721
|
+
*
|
|
1722
|
+
* The raw range starts at the declaration (or its docstring), which sits after
|
|
1723
|
+
* the member's indentation — splicing it out verbatim leaves that indentation
|
|
1724
|
+
* glued to the following line. Extends backwards over the indentation and
|
|
1725
|
+
* forwards over the trailing newline, then absorbs the blank line the removal
|
|
1726
|
+
* would otherwise duplicate (or strand in front of the block's closing brace).
|
|
1727
|
+
*/
|
|
1728
|
+
function expandPruneRange(source, rawStart, rawEnd) {
|
|
1729
|
+
let start = rawStart;
|
|
1730
|
+
while (start > 0 && (source[start - 1] === " " || source[start - 1] === " ")) start--;
|
|
1731
|
+
let end = rawEnd;
|
|
1732
|
+
while (end < source.length && (source[end] === " " || source[end] === " ")) end++;
|
|
1733
|
+
end = skipLineBreak(source, end);
|
|
1734
|
+
if (precededByBlankLine(source, start)) {
|
|
1735
|
+
if (startsWithLineBreak(source, end)) end = skipLineBreak(source, end);
|
|
1736
|
+
else if (/^[ \t]*\}/.test(source.slice(end, end + 40))) start = backOverLineBreak(source, start);
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
start,
|
|
1740
|
+
end
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
function startsWithLineBreak(source, idx) {
|
|
1744
|
+
return source[idx] === "\n" || source[idx] === "\r" && source[idx + 1] === "\n";
|
|
1745
|
+
}
|
|
1746
|
+
function skipLineBreak(source, idx) {
|
|
1747
|
+
let i = idx;
|
|
1748
|
+
if (source[i] === "\r") i++;
|
|
1749
|
+
return source[i] === "\n" ? i + 1 : i;
|
|
1750
|
+
}
|
|
1751
|
+
function backOverLineBreak(source, idx) {
|
|
1752
|
+
let i = idx;
|
|
1753
|
+
if (source[i - 1] === "\n") i--;
|
|
1754
|
+
if (source[i - 1] === "\r") i--;
|
|
1755
|
+
return i;
|
|
1756
|
+
}
|
|
1757
|
+
/** Whether the line ending immediately before `idx` (a line start) is empty. */
|
|
1758
|
+
function precededByBlankLine(source, idx) {
|
|
1759
|
+
if (idx === 0 || source[idx - 1] !== "\n") return false;
|
|
1760
|
+
const priorLineEnd = backOverLineBreak(source, idx);
|
|
1761
|
+
return priorLineEnd > 0 && source[priorLineEnd - 1] === "\n";
|
|
1762
|
+
}
|
|
1763
|
+
function referencesIdentifier(source, name) {
|
|
1764
|
+
return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(source);
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Blank out comments and string text, preserving offsets and line structure.
|
|
1768
|
+
*
|
|
1769
|
+
* An identifier lookup over raw source can't tell a live reference from a name
|
|
1770
|
+
* that merely survives in prose. That distinction decides whether an import
|
|
1771
|
+
* stays: keeping one whose only remaining mention is a KDoc leaves an import of
|
|
1772
|
+
* a class the generator no longer emits, which doesn't compile — the very
|
|
1773
|
+
* failure the prune exists to prevent.
|
|
1774
|
+
*
|
|
1775
|
+
* Interpolated expressions are the exception: `"${Agents.NAME}"` is code that
|
|
1776
|
+
* sits inside a string literal, and masking it would drop an import the file
|
|
1777
|
+
* genuinely uses. String extents are masked around those holes, not over them.
|
|
1778
|
+
*
|
|
1779
|
+
* Grammar node names come from the adapter's `urlFingerprintConfig`, which
|
|
1780
|
+
* already inventories exactly these three categories per language. Comments are
|
|
1781
|
+
* matched structurally instead — every grammar we support names them `*comment`.
|
|
1782
|
+
*/
|
|
1783
|
+
function maskNonCode(tree, source, config) {
|
|
1784
|
+
const stringNodeTypes = new Set(config?.stringNodeTypes ?? []);
|
|
1785
|
+
const interpolationNodeTypes = new Set(config?.interpolationNodeTypes ?? []);
|
|
1786
|
+
const spans = [];
|
|
1787
|
+
const collectStringSpans = (node) => {
|
|
1788
|
+
const holes = [];
|
|
1789
|
+
const findHoles = (n) => {
|
|
1790
|
+
if (interpolationNodeTypes.has(n.type)) {
|
|
1791
|
+
holes.push([n.startIndex, n.endIndex]);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
for (const child of n.children) findHoles(child);
|
|
1795
|
+
};
|
|
1796
|
+
findHoles(node);
|
|
1797
|
+
let cursor = node.startIndex;
|
|
1798
|
+
for (const [holeStart, holeEnd] of holes) {
|
|
1799
|
+
if (holeStart > cursor) spans.push([cursor, holeStart]);
|
|
1800
|
+
cursor = holeEnd;
|
|
1801
|
+
}
|
|
1802
|
+
if (cursor < node.endIndex) spans.push([cursor, node.endIndex]);
|
|
1803
|
+
};
|
|
1804
|
+
const walk = (node) => {
|
|
1805
|
+
if (node.type.includes("comment")) {
|
|
1806
|
+
spans.push([node.startIndex, node.endIndex]);
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (stringNodeTypes.has(node.type)) {
|
|
1810
|
+
collectStringSpans(node);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
for (const child of node.children) walk(child);
|
|
1814
|
+
};
|
|
1815
|
+
walk(tree.rootNode);
|
|
1816
|
+
if (spans.length === 0) return source;
|
|
1817
|
+
let masked = "";
|
|
1818
|
+
let cursor = 0;
|
|
1819
|
+
for (const [spanStart, spanEnd] of spans) {
|
|
1820
|
+
if (spanStart < cursor) continue;
|
|
1821
|
+
masked += source.slice(cursor, spanStart) + source.slice(spanStart, spanEnd).replace(/[^\r\n]/g, " ");
|
|
1822
|
+
cursor = spanEnd;
|
|
1823
|
+
}
|
|
1824
|
+
return masked + source.slice(cursor);
|
|
1825
|
+
}
|
|
1826
|
+
function removeLines(source, lines) {
|
|
1827
|
+
return source.split("\n").filter((line) => !lines.has(line.trim())).join("\n");
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1675
1830
|
* Resolve a docstring range by searching for the nearest matching text before
|
|
1676
1831
|
* the declaration start. Falls back to parser-provided offsets when search fails.
|
|
1677
1832
|
*/
|
|
@@ -1886,6 +2041,7 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1886
2041
|
let deepAdded = 0;
|
|
1887
2042
|
let deepPruned = 0;
|
|
1888
2043
|
const insertions = [];
|
|
2044
|
+
const prunedTexts = [];
|
|
1889
2045
|
if (adapter.extractMembers) {
|
|
1890
2046
|
const parser = await getParser(language);
|
|
1891
2047
|
const resultTree = safeParse(parser, result);
|
|
@@ -1914,10 +2070,9 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1914
2070
|
if (pruneEdits.length > 0) {
|
|
1915
2071
|
pruneEdits.sort((a, b) => b.start - a.start);
|
|
1916
2072
|
for (const edit of pruneEdits) {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
result = result.slice(0, edit.start) + result.slice(end);
|
|
2073
|
+
const { start, end } = expandPruneRange(result, edit.start, edit.end);
|
|
2074
|
+
prunedTexts.push(result.slice(start, end));
|
|
2075
|
+
result = result.slice(0, start) + result.slice(end);
|
|
1921
2076
|
}
|
|
1922
2077
|
deepPruned = pruneEdits.length;
|
|
1923
2078
|
const reTree = safeParse(parser, result);
|
|
@@ -1982,6 +2137,21 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1982
2137
|
result = lines.join("\n");
|
|
1983
2138
|
}
|
|
1984
2139
|
}
|
|
2140
|
+
if (prunedTexts.length > 0 && adapter.importedNames) {
|
|
2141
|
+
const prunedText = prunedTexts.join("\n");
|
|
2142
|
+
const codeOnly = maskNonCode(safeParse(await getParser(language), result), result, adapter.urlFingerprintConfig);
|
|
2143
|
+
const orphaned = /* @__PURE__ */ new Set();
|
|
2144
|
+
for (const imp of existingStatements.imports) {
|
|
2145
|
+
const names = adapter.importedNames(imp);
|
|
2146
|
+
if (names.length === 0) continue;
|
|
2147
|
+
if (!names.some((n) => referencesIdentifier(prunedText, n))) continue;
|
|
2148
|
+
const line = imp.text.trim();
|
|
2149
|
+
const withoutImport = removeLines(codeOnly, new Set([line]));
|
|
2150
|
+
if (names.some((n) => referencesIdentifier(withoutImport, n))) continue;
|
|
2151
|
+
orphaned.add(line);
|
|
2152
|
+
}
|
|
2153
|
+
if (orphaned.size > 0) result = removeLines(result, orphaned);
|
|
2154
|
+
}
|
|
1985
2155
|
let docstringUpdates = 0;
|
|
1986
2156
|
{
|
|
1987
2157
|
const parser = await getParser(language);
|
|
@@ -4282,6 +4452,6 @@ function summarize(changes) {
|
|
|
4282
4452
|
};
|
|
4283
4453
|
}
|
|
4284
4454
|
//#endregion
|
|
4285
|
-
export {
|
|
4455
|
+
export { findResolvedMethodCollisions as _, registerEmitter as a, integrateGeneratedFiles as c, applyFileHeaders as d, buildEmitterContext as f, deriveMethodName as g, generateFiles as h, getEmitter as i, mapFilesForTargetIntegration as l, generateAllFiles as m, buildDirectionIndex as n, generate as o, collectReferencedNames as p, directionOf as r, MANIFEST_FILENAME as s, diffSpecs as t, treeShakeFiles as u, resolveMountTarget as v, resolveOperations as y };
|
|
4286
4456
|
|
|
4287
|
-
//# sourceMappingURL=diff-
|
|
4457
|
+
//# sourceMappingURL=diff-DMFiDAI6.mjs.map
|