@workos/oagen 0.3.0 → 0.5.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 +72 -1
- package/dist/cli/index.mjs +955 -90
- package/dist/cli/index.mjs.map +1 -1
- package/dist/compat/index.d.mts +1 -1
- package/dist/compat/index.mjs +9 -4
- package/dist/compat/index.mjs.map +1 -1
- package/dist/{diff-Cn6S74vV.mjs → diff-CSy3g-Bq.mjs} +541 -43
- package/dist/diff-CSy3g-Bq.mjs.map +1 -0
- package/dist/{differ-B6472Yef.mjs → differ-CG5ZErKl.mjs} +2 -2
- package/dist/{differ-B6472Yef.mjs.map → differ-CG5ZErKl.mjs.map} +1 -1
- package/dist/index.d.mts +111 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +340 -30
- package/dist/index.mjs.map +1 -1
- package/dist/scripts/smoke/baseline.mjs +317 -33
- package/dist/scripts/smoke/baseline.mjs.map +1 -1
- package/dist/smoke.d.mts +140 -0
- package/dist/smoke.d.mts.map +1 -1
- package/dist/smoke.mjs +317 -33
- package/dist/smoke.mjs.map +1 -1
- package/dist/{tree-sitter-DFRXfWg9.mjs → tree-sitter-C53w_0WE.mjs} +5 -7
- package/dist/tree-sitter-C53w_0WE.mjs.map +1 -0
- package/dist/{types-O25qZxMl.d.mts → types-D84LjrVG.d.mts} +167 -2
- package/dist/types-D84LjrVG.d.mts.map +1 -0
- package/dist/verify/index.d.mts +1 -1
- package/dist/verify/index.mjs +4 -4
- package/dist/verify/index.mjs.map +1 -1
- package/docs/agents/architecture.md +1 -1
- package/docs/architecture/emitter-contract.md +1 -2
- package/docs/architecture/ir-types.md +73 -1
- package/docs/architecture/pipeline.md +0 -1
- package/docs/cli.md +29 -8
- package/docs/core/minimal-emitter.md +0 -4
- package/docs/core/quickstart.md +0 -1
- package/examples/reference-emitter/README.md +0 -1
- package/examples/reference-emitter/src/emitter.ts +0 -5
- package/package.json +4 -4
- package/skills/review-operations/SKILL.md +83 -0
- package/dist/diff-Cn6S74vV.mjs.map +0 -1
- package/dist/tree-sitter-DFRXfWg9.mjs.map +0 -1
- package/dist/types-O25qZxMl.d.mts.map +0 -1
- package/examples/reference-emitter/src/generators/config.ts +0 -19
|
@@ -1,10 +1,149 @@
|
|
|
1
|
-
import { A as walkTypeRef, C as assertNever, f as toSnakeCase, i as normalizeJsExtension, t as safeParse, x as RegistryError } from "./tree-sitter-
|
|
1
|
+
import { A as walkTypeRef, C as assertNever, f as toSnakeCase, i as normalizeJsExtension, t as safeParse, x as RegistryError } from "./tree-sitter-C53w_0WE.mjs";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import Parser from "tree-sitter";
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
+
//#region src/ir/operation-hints.ts
|
|
9
|
+
/** Minimal snake_case converter for path segments (avoids cross-layer import). */
|
|
10
|
+
function toSnake(s) {
|
|
11
|
+
return s.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s.]+/g, "_").toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Action verbs recognised at the terminal path segment.
|
|
15
|
+
* When a path ends with one of these, we use it as the verb instead of
|
|
16
|
+
* deriving a CRUD verb from the HTTP method.
|
|
17
|
+
*/
|
|
18
|
+
const ACTION_VERBS = new Set([
|
|
19
|
+
"accept",
|
|
20
|
+
"activate",
|
|
21
|
+
"authorize",
|
|
22
|
+
"challenge",
|
|
23
|
+
"check",
|
|
24
|
+
"complete",
|
|
25
|
+
"confirm",
|
|
26
|
+
"deactivate",
|
|
27
|
+
"disable",
|
|
28
|
+
"enable",
|
|
29
|
+
"enroll",
|
|
30
|
+
"generate",
|
|
31
|
+
"reactivate",
|
|
32
|
+
"resend",
|
|
33
|
+
"revoke",
|
|
34
|
+
"send",
|
|
35
|
+
"verify"
|
|
36
|
+
]);
|
|
37
|
+
/** Map HTTP method → default CRUD verb. */
|
|
38
|
+
const CRUD_VERB = {
|
|
39
|
+
get: "list",
|
|
40
|
+
post: "create",
|
|
41
|
+
put: "update",
|
|
42
|
+
patch: "update",
|
|
43
|
+
delete: "delete",
|
|
44
|
+
head: "get",
|
|
45
|
+
options: "list",
|
|
46
|
+
trace: "list"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Naive singularization: strips trailing "s" with common-sense exceptions.
|
|
50
|
+
* Good enough for API resource nouns; not a general-purpose English stemmer.
|
|
51
|
+
*/
|
|
52
|
+
function singularize(word) {
|
|
53
|
+
if (word.length <= 2) return word;
|
|
54
|
+
if (word.endsWith("ies")) return word.slice(0, -3) + "y";
|
|
55
|
+
if (word.endsWith("ses") || word.endsWith("zes") || word.endsWith("xes") || word.endsWith("ches") || word.endsWith("shes")) return word.slice(0, -2);
|
|
56
|
+
if (word.endsWith("ss") || word.endsWith("us") || word.endsWith("is")) return word;
|
|
57
|
+
if (word.endsWith("s")) return word.slice(0, -1);
|
|
58
|
+
return word;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Derive a snake_case method name from an operation's HTTP method and path.
|
|
62
|
+
*
|
|
63
|
+
* Rules:
|
|
64
|
+
* 1. Strip path-param segments (`{id}`, `{slug}`, etc.)
|
|
65
|
+
* 2. If the terminal segment is an action verb, use it as the verb
|
|
66
|
+
* 3. Otherwise, use the CRUD verb for the HTTP method
|
|
67
|
+
* 4. For GET on a collection (no trailing {id}), use "list"; with {id}, use "get"
|
|
68
|
+
* 5. For POST on a resource path, if only one path param, use "create" singular
|
|
69
|
+
* 6. Singularize the resource noun for single-resource operations
|
|
70
|
+
*/
|
|
71
|
+
function deriveMethodName(op, _service) {
|
|
72
|
+
const segments = op.path.split("/").filter(Boolean);
|
|
73
|
+
const staticSegments = [];
|
|
74
|
+
let trailingParam = false;
|
|
75
|
+
for (const seg of segments) if (seg.startsWith("{") && seg.endsWith("}")) trailingParam = true;
|
|
76
|
+
else {
|
|
77
|
+
staticSegments.push(seg);
|
|
78
|
+
trailingParam = false;
|
|
79
|
+
}
|
|
80
|
+
if (staticSegments.length === 0) return `${CRUD_VERB[op.httpMethod] ?? "get"}_root`;
|
|
81
|
+
const terminal = toSnake(staticSegments[staticSegments.length - 1]);
|
|
82
|
+
if (ACTION_VERBS.has(terminal)) {
|
|
83
|
+
const context = staticSegments.slice(0, -1).filter((s) => !s.startsWith("{"));
|
|
84
|
+
const resource = context.length > 0 ? toSnake(singularize(context[context.length - 1])) : "";
|
|
85
|
+
if (resource) return `${terminal}_${resource}`;
|
|
86
|
+
return terminal;
|
|
87
|
+
}
|
|
88
|
+
let verb = CRUD_VERB[op.httpMethod] ?? "get";
|
|
89
|
+
if (op.httpMethod === "get" && trailingParam) verb = "get";
|
|
90
|
+
let resource = toSnake(terminal);
|
|
91
|
+
if (staticSegments.length >= 2) {
|
|
92
|
+
const penultimate = staticSegments[staticSegments.length - 2];
|
|
93
|
+
const penIdx = segments.lastIndexOf(penultimate);
|
|
94
|
+
const termIdx = segments.lastIndexOf(staticSegments[staticSegments.length - 1]);
|
|
95
|
+
if (segments.slice(penIdx + 1, termIdx).some((s) => s.startsWith("{"))) resource = `${toSnake(singularize(penultimate))}_${resource}`;
|
|
96
|
+
}
|
|
97
|
+
const noun = trailingParam ? singularize(resource) : resource;
|
|
98
|
+
return `${verb}_${verb === "list" ? resource : noun}`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolve all operations in the spec, applying hints and mount rules.
|
|
102
|
+
*
|
|
103
|
+
* @param spec - The parsed API spec
|
|
104
|
+
* @param hints - Per-operation overrides keyed by "METHOD /path"
|
|
105
|
+
* @param mountRules - Service name → target service mappings
|
|
106
|
+
* @returns Resolved operations ready for emitter consumption
|
|
107
|
+
*/
|
|
108
|
+
function resolveOperations(spec, hints, mountRules) {
|
|
109
|
+
const resolved = [];
|
|
110
|
+
const hintMap = hints ?? {};
|
|
111
|
+
const mounts = mountRules ?? {};
|
|
112
|
+
for (const service of spec.services) for (const op of service.operations) {
|
|
113
|
+
const hint = hintMap[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
114
|
+
const methodName = hint?.name ?? deriveMethodName(op, service);
|
|
115
|
+
const mountOn = hint?.mountOn ?? mounts[service.name] ?? service.name;
|
|
116
|
+
let wrappers;
|
|
117
|
+
if (hint?.split) wrappers = hint.split.map((sh) => ({
|
|
118
|
+
name: sh.name,
|
|
119
|
+
targetVariant: sh.targetVariant,
|
|
120
|
+
defaults: sh.defaults ?? {},
|
|
121
|
+
inferFromClient: sh.inferFromClient ?? [],
|
|
122
|
+
exposedParams: sh.exposedParams ?? [],
|
|
123
|
+
optionalParams: [],
|
|
124
|
+
responseModelName: resolveResponseModelName(op)
|
|
125
|
+
}));
|
|
126
|
+
resolved.push({
|
|
127
|
+
operation: op,
|
|
128
|
+
service,
|
|
129
|
+
methodName,
|
|
130
|
+
mountOn,
|
|
131
|
+
wrappers,
|
|
132
|
+
defaults: hint?.defaults ?? {},
|
|
133
|
+
inferFromClient: hint?.inferFromClient ?? []
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return resolved;
|
|
137
|
+
}
|
|
138
|
+
/** Extract the response model name from an operation (for wrapper metadata). */
|
|
139
|
+
function resolveResponseModelName(op) {
|
|
140
|
+
if (op.httpMethod === "delete") return null;
|
|
141
|
+
const ref = op.response;
|
|
142
|
+
if (ref.kind === "model") return ref.name;
|
|
143
|
+
if (ref.kind === "nullable" && ref.inner.kind === "model") return ref.inner.name;
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
8
147
|
//#region src/engine/merge-adapters/url-fingerprint.ts
|
|
9
148
|
/**
|
|
10
149
|
* Extract a URL path fingerprint from a method body using tree-sitter AST traversal.
|
|
@@ -913,8 +1052,9 @@ const phpMergeAdapter = {
|
|
|
913
1052
|
testFilePatterns: [/Test\.php$/],
|
|
914
1053
|
urlFingerprintConfig: PHP_URL_FINGERPRINT_CONFIG,
|
|
915
1054
|
resolveGrammar: (mod) => {
|
|
916
|
-
|
|
917
|
-
return
|
|
1055
|
+
const m = mod?.default ?? mod;
|
|
1056
|
+
if (typeof m === "object" && m !== null && "php" in m) return m.php;
|
|
1057
|
+
return m;
|
|
918
1058
|
},
|
|
919
1059
|
parseStatements(tree, source) {
|
|
920
1060
|
const imports = [];
|
|
@@ -1358,6 +1498,30 @@ function getMergeAdapter(language) {
|
|
|
1358
1498
|
* Supports any language with a tree-sitter grammar package.
|
|
1359
1499
|
*/
|
|
1360
1500
|
const parserCache = /* @__PURE__ */ new Map();
|
|
1501
|
+
/**
|
|
1502
|
+
* When replacing an existing docstring with a generated one, carry over any
|
|
1503
|
+
* `@deprecated` lines from the existing doc that aren't present in the
|
|
1504
|
+
* generated doc. This prevents hand-written deprecation notices from being
|
|
1505
|
+
* silently dropped during docstring refresh.
|
|
1506
|
+
*/
|
|
1507
|
+
function preserveDeprecatedTags(existingDoc, generatedDoc) {
|
|
1508
|
+
if (!existingDoc.includes("@deprecated")) return generatedDoc;
|
|
1509
|
+
if (generatedDoc.includes("@deprecated")) return generatedDoc;
|
|
1510
|
+
const deprecatedParts = [];
|
|
1511
|
+
for (const line of existingDoc.split("\n")) if (/@deprecated\b/.test(line)) {
|
|
1512
|
+
const match = line.match(/@deprecated\b.*/);
|
|
1513
|
+
if (match) {
|
|
1514
|
+
const cleaned = match[0].replace(/\s*\*\/\s*$/, "");
|
|
1515
|
+
deprecatedParts.push(` * ${cleaned}`);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (deprecatedParts.length === 0) return generatedDoc;
|
|
1519
|
+
const closingIdx = generatedDoc.lastIndexOf("*/");
|
|
1520
|
+
if (closingIdx === -1) return generatedDoc;
|
|
1521
|
+
const before = generatedDoc.slice(0, closingIdx);
|
|
1522
|
+
const after = generatedDoc.slice(closingIdx);
|
|
1523
|
+
return before + deprecatedParts.join("\n") + "\n " + after;
|
|
1524
|
+
}
|
|
1361
1525
|
function findIgnoredRegions(source) {
|
|
1362
1526
|
const regions = [];
|
|
1363
1527
|
const startTag = "@oagen-ignore-start";
|
|
@@ -1413,6 +1577,35 @@ async function extractStatements(source, language) {
|
|
|
1413
1577
|
return adapter.parseStatements(tree, source);
|
|
1414
1578
|
}
|
|
1415
1579
|
/**
|
|
1580
|
+
* Return a map of top-level class name → { bodyEndLine, bodyEndCol } using
|
|
1581
|
+
* tree-sitter. `bodyEndLine` is 0-based. For brace-delimited classes the
|
|
1582
|
+
* end points at the closing brace line; for indentation-delimited classes
|
|
1583
|
+
* it points one past the last line of the class body.
|
|
1584
|
+
*/
|
|
1585
|
+
async function extractClassEndLines(source, language) {
|
|
1586
|
+
const tree = safeParse(await getParser(language), source);
|
|
1587
|
+
const map = /* @__PURE__ */ new Map();
|
|
1588
|
+
function processClassNode(node) {
|
|
1589
|
+
const nameNode = node.childForFieldName("name");
|
|
1590
|
+
if (!nameNode) return;
|
|
1591
|
+
const name = nameNode.text;
|
|
1592
|
+
const body = node.childForFieldName("body");
|
|
1593
|
+
const endRow = body ? body.endPosition.row : node.endPosition.row;
|
|
1594
|
+
if (source[body ? body.endIndex - 1 : node.endIndex - 1] === "}") map.set(name, { bodyEndLine: endRow });
|
|
1595
|
+
else map.set(name, { bodyEndLine: endRow + 1 });
|
|
1596
|
+
}
|
|
1597
|
+
for (const child of tree.rootNode.children) {
|
|
1598
|
+
if (child.type === "class_definition" || child.type === "class_declaration") processClassNode(child);
|
|
1599
|
+
if (child.type === "decorated_definition") {
|
|
1600
|
+
for (const inner of child.namedChildren) if (inner.type === "class_definition") processClassNode(inner);
|
|
1601
|
+
}
|
|
1602
|
+
if (child.type === "export_statement") {
|
|
1603
|
+
for (const inner of child.namedChildren) if (inner.type === "class_declaration") processClassNode(inner);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return map;
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1416
1609
|
* Merge generated content into an existing file.
|
|
1417
1610
|
*
|
|
1418
1611
|
* - Parses both files with tree-sitter
|
|
@@ -1442,10 +1635,21 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1442
1635
|
}
|
|
1443
1636
|
for (const anchor of existingStatements.importAnchors) lastImportEndIndex = existingContent.slice(0, existingContent.indexOf(anchor)).split("\n").length - 1 + anchor.split("\n").length - 1;
|
|
1444
1637
|
if (lastImportEndIndex === -1 && existingStatements.importInsertionAnchor) lastImportEndIndex = existingContent.slice(0, existingContent.indexOf(existingStatements.importInsertionAnchor)).split("\n").length - 1 + existingStatements.importInsertionAnchor.split("\n").length - 1;
|
|
1638
|
+
const existingExportedNames = /* @__PURE__ */ new Set();
|
|
1445
1639
|
for (const stmt of existingStatements.statements) {
|
|
1446
|
-
if (stmt.kind === "reexport")
|
|
1447
|
-
|
|
1448
|
-
|
|
1640
|
+
if (stmt.kind === "reexport") {
|
|
1641
|
+
existingReexports.add(adapter.normalizeReexport ? adapter.normalizeReexport(stmt.text.trim()) : stmt.text.trim());
|
|
1642
|
+
const braceMatch = stmt.text.match(/\{([^}]+)\}/);
|
|
1643
|
+
if (braceMatch) for (const part of braceMatch[1].split(",")) {
|
|
1644
|
+
const segments = part.trim().split(/\s+as\s+/);
|
|
1645
|
+
const exportedName = (segments[1] ?? segments[0]).trim();
|
|
1646
|
+
if (exportedName) existingExportedNames.add(exportedName);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (stmt.key) {
|
|
1650
|
+
existingKeys.add(stmt.key);
|
|
1651
|
+
existingExportedNames.add(stmt.key);
|
|
1652
|
+
} else existingUnnamedTexts.add(stmt.text.trim());
|
|
1449
1653
|
}
|
|
1450
1654
|
const headerLine = header.trim();
|
|
1451
1655
|
const newImports = [];
|
|
@@ -1505,6 +1709,17 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1505
1709
|
preserved++;
|
|
1506
1710
|
continue;
|
|
1507
1711
|
}
|
|
1712
|
+
const braceMatch = stmt.text.match(/\{([^}]+)\}/);
|
|
1713
|
+
if (braceMatch) {
|
|
1714
|
+
const exportedNames = braceMatch[1].split(",").map((n) => {
|
|
1715
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
1716
|
+
return (parts[1] ?? parts[0]).trim();
|
|
1717
|
+
}).filter(Boolean);
|
|
1718
|
+
if (exportedNames.length > 0 && exportedNames.every((n) => existingExportedNames.has(n))) {
|
|
1719
|
+
preserved++;
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1508
1723
|
}
|
|
1509
1724
|
if (stmt.key && (existingKeys.has(stmt.key) || existingImportedNames.has(stmt.key))) {
|
|
1510
1725
|
preserved++;
|
|
@@ -1606,10 +1821,11 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1606
1821
|
const genDoc = genInfo.docstring && genInfo.docstring.text.trim() !== headerLine ? genInfo.docstring : null;
|
|
1607
1822
|
if (genDoc) if (existInfo.docstring) {
|
|
1608
1823
|
if (!existInfo.docstring.text.includes("@oagen-ignore") && existInfo.docstring.text !== genDoc.text) {
|
|
1824
|
+
const newText = preserveDeprecatedTags(existInfo.docstring.text, genDoc.text);
|
|
1609
1825
|
edits.push({
|
|
1610
1826
|
start: existInfo.docstring.startIndex,
|
|
1611
1827
|
end: existInfo.docstring.endIndex,
|
|
1612
|
-
newText
|
|
1828
|
+
newText
|
|
1613
1829
|
});
|
|
1614
1830
|
docstringUpdates++;
|
|
1615
1831
|
}
|
|
@@ -1626,14 +1842,16 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1626
1842
|
const matchedExistMembers = /* @__PURE__ */ new Set();
|
|
1627
1843
|
for (const [memberName, genMember] of genInfo.members) {
|
|
1628
1844
|
const existMember = existInfo.members.get(memberName);
|
|
1629
|
-
if (!existMember
|
|
1845
|
+
if (!existMember) continue;
|
|
1630
1846
|
matchedExistMembers.add(memberName);
|
|
1847
|
+
if (!genMember.docstring) continue;
|
|
1631
1848
|
if (existMember.docstring) {
|
|
1632
1849
|
if (!existMember.docstring.text.includes("@oagen-ignore") && existMember.docstring.text !== genMember.docstring.text) {
|
|
1850
|
+
const newText = preserveDeprecatedTags(existMember.docstring.text, genMember.docstring.text);
|
|
1633
1851
|
edits.push({
|
|
1634
1852
|
start: existMember.docstring.startIndex,
|
|
1635
1853
|
end: existMember.docstring.endIndex,
|
|
1636
|
-
newText
|
|
1854
|
+
newText
|
|
1637
1855
|
});
|
|
1638
1856
|
docstringUpdates++;
|
|
1639
1857
|
}
|
|
@@ -1648,34 +1866,59 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
|
|
|
1648
1866
|
docstringUpdates++;
|
|
1649
1867
|
}
|
|
1650
1868
|
}
|
|
1651
|
-
|
|
1869
|
+
const unmatchedGenByFp = /* @__PURE__ */ new Map();
|
|
1870
|
+
for (const [genName, genMember] of genInfo.members) {
|
|
1871
|
+
if (matchedExistMembers.has(genName)) continue;
|
|
1652
1872
|
if (!genMember.docstring || !genMember.urlFingerprint) continue;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1873
|
+
if (existInfo.members.has(genName)) continue;
|
|
1874
|
+
const bucket = unmatchedGenByFp.get(genMember.urlFingerprint) ?? [];
|
|
1875
|
+
bucket.push({
|
|
1876
|
+
name: genName,
|
|
1877
|
+
member: genMember
|
|
1878
|
+
});
|
|
1879
|
+
unmatchedGenByFp.set(genMember.urlFingerprint, bucket);
|
|
1880
|
+
}
|
|
1881
|
+
const unmatchedExistByFp = /* @__PURE__ */ new Map();
|
|
1882
|
+
for (const [existName, existMember] of existInfo.members) {
|
|
1883
|
+
if (matchedExistMembers.has(existName)) continue;
|
|
1884
|
+
if (!existMember.urlFingerprint) continue;
|
|
1885
|
+
const bucket = unmatchedExistByFp.get(existMember.urlFingerprint) ?? [];
|
|
1886
|
+
bucket.push({
|
|
1887
|
+
name: existName,
|
|
1888
|
+
member: existMember
|
|
1889
|
+
});
|
|
1890
|
+
unmatchedExistByFp.set(existMember.urlFingerprint, bucket);
|
|
1891
|
+
}
|
|
1892
|
+
for (const [fp, genBucket] of unmatchedGenByFp) {
|
|
1893
|
+
if (genBucket.length !== 1) continue;
|
|
1894
|
+
const existBucket = unmatchedExistByFp.get(fp);
|
|
1895
|
+
if (!existBucket || existBucket.length !== 1) continue;
|
|
1896
|
+
const genMember = genBucket[0].member;
|
|
1897
|
+
const existEntry = existBucket[0];
|
|
1898
|
+
const existMember = existEntry.member;
|
|
1899
|
+
if (!genMember.docstring) continue;
|
|
1900
|
+
if (existMember.docstring) {
|
|
1901
|
+
if (existMember.docstring.text.includes("@oagen-ignore")) continue;
|
|
1902
|
+
if (existMember.docstring.text !== genMember.docstring.text) {
|
|
1903
|
+
const newText = preserveDeprecatedTags(existMember.docstring.text, genMember.docstring.text);
|
|
1669
1904
|
edits.push({
|
|
1670
|
-
start:
|
|
1671
|
-
end:
|
|
1672
|
-
newText
|
|
1905
|
+
start: existMember.docstring.startIndex,
|
|
1906
|
+
end: existMember.docstring.endIndex,
|
|
1907
|
+
newText
|
|
1673
1908
|
});
|
|
1674
1909
|
docstringUpdates++;
|
|
1675
1910
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1911
|
+
} else {
|
|
1912
|
+
const lineStart = existMember.declStartIndex - existMember.declColumn;
|
|
1913
|
+
const indent = " ".repeat(existMember.declColumn);
|
|
1914
|
+
edits.push({
|
|
1915
|
+
start: lineStart,
|
|
1916
|
+
end: lineStart,
|
|
1917
|
+
newText: indent + genMember.docstring.text + "\n"
|
|
1918
|
+
});
|
|
1919
|
+
docstringUpdates++;
|
|
1678
1920
|
}
|
|
1921
|
+
matchedExistMembers.add(existEntry.name);
|
|
1679
1922
|
}
|
|
1680
1923
|
}
|
|
1681
1924
|
if (edits.length > 0) {
|
|
@@ -1761,15 +2004,18 @@ async function writeFiles(files, outputDir, options) {
|
|
|
1761
2004
|
}
|
|
1762
2005
|
const adapter = language ? getMergeAdapter(language) : void 0;
|
|
1763
2006
|
if (adapter?.testFilePatterns ? adapter.testFilePatterns.some((p) => p.test(file.path)) : isDefaultTestFile(file.path)) {
|
|
1764
|
-
|
|
1765
|
-
|
|
2007
|
+
if (!(header && existingContent.startsWith(header))) {
|
|
2008
|
+
result.skipped.push(file.path);
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
1766
2011
|
}
|
|
1767
2012
|
if (existingContent === file.content) {
|
|
1768
2013
|
result.identical.push(file.path);
|
|
1769
2014
|
continue;
|
|
1770
2015
|
}
|
|
1771
2016
|
if (file.overwriteExisting) {
|
|
1772
|
-
|
|
2017
|
+
const content = existingContent.includes("@oagen-ignore-start") && language && hasGrammar(language) ? await overwriteWithPreservedRegions(existingContent, file.content, language) : file.content;
|
|
2018
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
1773
2019
|
result.written.push(file.path);
|
|
1774
2020
|
continue;
|
|
1775
2021
|
}
|
|
@@ -1813,6 +2059,178 @@ async function writeFiles(files, outputDir, options) {
|
|
|
1813
2059
|
function isDefaultTestFile(filePath) {
|
|
1814
2060
|
return /\.(spec|test)\.[jt]sx?$/.test(filePath) || filePath.endsWith("_test.go") || filePath.endsWith("_test.rb") || filePath.endsWith("_spec.rb") || /(?:^|\/)test_.*\.py$/.test(filePath) || filePath.endsWith("_test.py") || filePath.endsWith("Test.php") || filePath.endsWith("_test.rs") || /(?:^|\/)tests\/.*\.rs$/.test(filePath);
|
|
1815
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Overwrite a file with generated content while preserving
|
|
2064
|
+
* `@oagen-ignore-start` / `@oagen-ignore-end` regions (and their
|
|
2065
|
+
* required imports) from the existing file.
|
|
2066
|
+
*
|
|
2067
|
+
* Uses tree-sitter via `extractClassEndLines` to find accurate class
|
|
2068
|
+
* boundaries so ignore blocks are inserted at the correct position even
|
|
2069
|
+
* when docstrings contain unindented text.
|
|
2070
|
+
*/
|
|
2071
|
+
async function overwriteWithPreservedRegions(existingContent, generatedContent, language) {
|
|
2072
|
+
const existingLines = existingContent.split("\n");
|
|
2073
|
+
const generatedLines = generatedContent.split("\n");
|
|
2074
|
+
const blocks = [];
|
|
2075
|
+
for (let i = 0; i < existingLines.length; i++) {
|
|
2076
|
+
if (!existingLines[i].includes("@oagen-ignore-start")) continue;
|
|
2077
|
+
const startIdx = i;
|
|
2078
|
+
while (i < existingLines.length && !existingLines[i].includes("@oagen-ignore-end")) i++;
|
|
2079
|
+
blocks.push({
|
|
2080
|
+
containingClass: findContainingClass(existingLines, startIdx),
|
|
2081
|
+
lines: existingLines.slice(startIdx, i + 1)
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
if (blocks.length === 0) return generatedContent;
|
|
2085
|
+
const result = [...generatedLines];
|
|
2086
|
+
spliceExtraImports(existingLines, result);
|
|
2087
|
+
const classEndLines = await extractClassEndLines(result.join("\n"), language);
|
|
2088
|
+
const blocksByClass = /* @__PURE__ */ new Map();
|
|
2089
|
+
for (const b of blocks) {
|
|
2090
|
+
if (!blocksByClass.has(b.containingClass)) blocksByClass.set(b.containingClass, []);
|
|
2091
|
+
blocksByClass.get(b.containingClass).push(b.lines);
|
|
2092
|
+
}
|
|
2093
|
+
const replacedClasses = /* @__PURE__ */ new Set();
|
|
2094
|
+
const handledBlockKeys = /* @__PURE__ */ new Set();
|
|
2095
|
+
const replacements = [];
|
|
2096
|
+
for (const [className, classBlocks] of blocksByClass) {
|
|
2097
|
+
let targetClass = className;
|
|
2098
|
+
if (!targetClass) for (const blockLines of classBlocks) {
|
|
2099
|
+
for (const l of blockLines) {
|
|
2100
|
+
const m = l.match(/^(?:export\s+)?class\s+(\w+)/);
|
|
2101
|
+
if (m && classEndLines.has(m[1])) {
|
|
2102
|
+
targetClass = m[1];
|
|
2103
|
+
break;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (targetClass) break;
|
|
2107
|
+
}
|
|
2108
|
+
if (!targetClass) continue;
|
|
2109
|
+
if (!classBlocks.some((blockLines) => blockLines.some((l) => {
|
|
2110
|
+
return l.match(/^(?:export\s+)?class\s+(\w+)/)?.[1] === targetClass;
|
|
2111
|
+
}))) continue;
|
|
2112
|
+
const info = classEndLines.get(targetClass);
|
|
2113
|
+
if (!info) continue;
|
|
2114
|
+
const startLine = findClassDeclarationStart(result, targetClass, info.bodyEndLine);
|
|
2115
|
+
if (startLine < 0) continue;
|
|
2116
|
+
const content = [];
|
|
2117
|
+
for (const block of classBlocks) {
|
|
2118
|
+
content.push("");
|
|
2119
|
+
content.push(...block);
|
|
2120
|
+
}
|
|
2121
|
+
replacements.push({
|
|
2122
|
+
startLine,
|
|
2123
|
+
endLine: info.bodyEndLine,
|
|
2124
|
+
content
|
|
2125
|
+
});
|
|
2126
|
+
replacedClasses.add(targetClass);
|
|
2127
|
+
handledBlockKeys.add(className);
|
|
2128
|
+
}
|
|
2129
|
+
replacements.sort((a, b) => b.startLine - a.startLine);
|
|
2130
|
+
for (const rep of replacements) result.splice(rep.startLine, rep.endLine - rep.startLine, ...rep.content);
|
|
2131
|
+
let effectiveClassEndLines = classEndLines;
|
|
2132
|
+
if (replacedClasses.size > 0) effectiveClassEndLines = await extractClassEndLines(result.join("\n"), language);
|
|
2133
|
+
const insertions = [];
|
|
2134
|
+
for (const [className, classBlocks] of blocksByClass) {
|
|
2135
|
+
if (handledBlockKeys.has(className) || replacedClasses.has(className)) continue;
|
|
2136
|
+
const info = className ? effectiveClassEndLines.get(className) : void 0;
|
|
2137
|
+
const insertLine = info ? info.bodyEndLine : result.length;
|
|
2138
|
+
const content = [];
|
|
2139
|
+
for (const block of classBlocks) {
|
|
2140
|
+
content.push("");
|
|
2141
|
+
content.push(...block);
|
|
2142
|
+
}
|
|
2143
|
+
insertions.push({
|
|
2144
|
+
line: insertLine,
|
|
2145
|
+
content
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
insertions.sort((a, b) => b.line - a.line);
|
|
2149
|
+
for (const ins of insertions) result.splice(ins.line, 0, ...ins.content);
|
|
2150
|
+
return result.join("\n");
|
|
2151
|
+
}
|
|
2152
|
+
/** Scan backwards from `lineIdx` to find the nearest class declaration name. */
|
|
2153
|
+
function findContainingClass(lines, lineIdx) {
|
|
2154
|
+
for (let j = lineIdx - 1; j >= 0; j--) {
|
|
2155
|
+
const m = lines[j].match(/^(?:export\s+)?class\s+(\w+)/);
|
|
2156
|
+
if (m) return m[1];
|
|
2157
|
+
}
|
|
2158
|
+
return "";
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Find the start line of a class declaration (including decorators above it)
|
|
2162
|
+
* by scanning backwards from `beforeLine`.
|
|
2163
|
+
*/
|
|
2164
|
+
function findClassDeclarationStart(lines, className, beforeLine) {
|
|
2165
|
+
const classPattern = new RegExp(`^(?:export\\s+)?class\\s+${className}\\b`);
|
|
2166
|
+
for (let i = beforeLine - 1; i >= 0; i--) {
|
|
2167
|
+
if (!classPattern.test(lines[i])) continue;
|
|
2168
|
+
let start = i;
|
|
2169
|
+
while (start > 0 && /^\s*@\w+/.test(lines[start - 1])) start--;
|
|
2170
|
+
return start;
|
|
2171
|
+
}
|
|
2172
|
+
return -1;
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Collect `from X import Y, Z` identifiers grouped by module, handling
|
|
2176
|
+
* both single-line and multiline (parenthesized) forms.
|
|
2177
|
+
*/
|
|
2178
|
+
function collectFromImports(lines) {
|
|
2179
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
2180
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2181
|
+
if (!/^from\s+/.test(lines[i])) continue;
|
|
2182
|
+
let fullText = lines[i];
|
|
2183
|
+
if (fullText.includes("(") && !fullText.includes(")")) while (i + 1 < lines.length) {
|
|
2184
|
+
i++;
|
|
2185
|
+
fullText += " " + lines[i].trim();
|
|
2186
|
+
if (lines[i].includes(")")) break;
|
|
2187
|
+
}
|
|
2188
|
+
const match = fullText.replace(/[()]/g, "").replace(/\s+/g, " ").trim().match(/^from\s+(\S+)\s+import\s+(.+)$/);
|
|
2189
|
+
if (!match) continue;
|
|
2190
|
+
const mod = match[1];
|
|
2191
|
+
if (!byModule.has(mod)) byModule.set(mod, /* @__PURE__ */ new Set());
|
|
2192
|
+
for (const id of match[2].split(",").map((s) => s.trim()).filter(Boolean)) byModule.get(mod).add(id);
|
|
2193
|
+
}
|
|
2194
|
+
return byModule;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Add imports from the existing file that are absent from the generated
|
|
2198
|
+
* result. Handles both `import X` and `from X import Y` forms.
|
|
2199
|
+
* Mutates `resultLines` in place.
|
|
2200
|
+
*/
|
|
2201
|
+
function spliceExtraImports(existingLines, resultLines) {
|
|
2202
|
+
const isSimpleImport = (l) => /^import\s+\w/.test(l);
|
|
2203
|
+
const existingSimple = new Set(existingLines.filter(isSimpleImport).map((l) => l.trim()));
|
|
2204
|
+
const generatedSimple = new Set(resultLines.filter(isSimpleImport).map((l) => l.trim()));
|
|
2205
|
+
const extraSimple = [...existingSimple].filter((l) => !generatedSimple.has(l));
|
|
2206
|
+
const existingFrom = collectFromImports(existingLines);
|
|
2207
|
+
const generatedFrom = collectFromImports(resultLines);
|
|
2208
|
+
const allGeneratedIds = /* @__PURE__ */ new Set();
|
|
2209
|
+
for (const ids of generatedFrom.values()) for (const id of ids) allGeneratedIds.add(id);
|
|
2210
|
+
const extraFromLines = [];
|
|
2211
|
+
for (const [mod, existIds] of existingFrom) {
|
|
2212
|
+
const genIds = generatedFrom.get(mod);
|
|
2213
|
+
if (!genIds) {
|
|
2214
|
+
const extra = [...existIds].filter((id) => !allGeneratedIds.has(id));
|
|
2215
|
+
if (extra.length > 0) extraFromLines.push(`from ${mod} import ${extra.join(", ")}`);
|
|
2216
|
+
} else {
|
|
2217
|
+
const extra = [...existIds].filter((id) => !genIds.has(id) && !allGeneratedIds.has(id));
|
|
2218
|
+
if (extra.length > 0) extraFromLines.push(`from ${mod} import ${extra.join(", ")}`);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
const allExtra = [...extraSimple, ...extraFromLines];
|
|
2222
|
+
if (allExtra.length === 0) return;
|
|
2223
|
+
let lastImportIdx = -1;
|
|
2224
|
+
for (let i = 0; i < resultLines.length; i++) if (isSimpleImport(resultLines[i]) || /^from\s+/.test(resultLines[i])) {
|
|
2225
|
+
lastImportIdx = i;
|
|
2226
|
+
if (resultLines[i].includes("(") && !resultLines[i].includes(")")) while (i + 1 < resultLines.length) {
|
|
2227
|
+
i++;
|
|
2228
|
+
lastImportIdx = i;
|
|
2229
|
+
if (resultLines[i].includes(")")) break;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
if (lastImportIdx >= 0) resultLines.splice(lastImportIdx + 1, 0, ...allExtra);
|
|
2233
|
+
}
|
|
1816
2234
|
//#endregion
|
|
1817
2235
|
//#region src/engine/generate-files.ts
|
|
1818
2236
|
function buildEmitterContext(spec, options) {
|
|
@@ -1822,7 +2240,8 @@ function buildEmitterContext(spec, options) {
|
|
|
1822
2240
|
spec,
|
|
1823
2241
|
outputDir: options.outputDir,
|
|
1824
2242
|
apiSurface: options.apiSurface,
|
|
1825
|
-
overlayLookup: options.overlayLookup
|
|
2243
|
+
overlayLookup: options.overlayLookup,
|
|
2244
|
+
resolvedOperations: resolveOperations(spec, options.operationHints, options.mountRules)
|
|
1826
2245
|
};
|
|
1827
2246
|
}
|
|
1828
2247
|
/**
|
|
@@ -1852,6 +2271,7 @@ function collectReferencedNames(services, models) {
|
|
|
1852
2271
|
for (const err of op.errors) if (err.type) collectFromTypeRef(err.type);
|
|
1853
2272
|
if (op.successResponses) for (const sr of op.successResponses) collectFromTypeRef(sr.type);
|
|
1854
2273
|
}
|
|
2274
|
+
for (const model of models) if (isDiscriminatedModel(model)) referencedModels.add(model.name);
|
|
1855
2275
|
const modelsByName = new Map(models.map((m) => [m.name, m]));
|
|
1856
2276
|
const visited = /* @__PURE__ */ new Set();
|
|
1857
2277
|
const queue = [...referencedModels];
|
|
@@ -1871,6 +2291,9 @@ function collectReferencedNames(services, models) {
|
|
|
1871
2291
|
enums: referencedEnums
|
|
1872
2292
|
};
|
|
1873
2293
|
}
|
|
2294
|
+
function isDiscriminatedModel(model) {
|
|
2295
|
+
return model.fields.some((field) => (field.name === "event" || field.name === "type" || field.name === "object") && field.type.kind === "literal");
|
|
2296
|
+
}
|
|
1874
2297
|
/** Collect all generated files from an emitter (no headers, no path prefixes). */
|
|
1875
2298
|
function generateAllFiles(spec, emitter, ctx) {
|
|
1876
2299
|
const referenced = collectReferencedNames(spec.services, spec.models);
|
|
@@ -1887,7 +2310,6 @@ function generateAllFiles(spec, emitter, ctx) {
|
|
|
1887
2310
|
...emitter.generateResources(spec.services, ctx),
|
|
1888
2311
|
...emitter.generateClient(spec, ctx),
|
|
1889
2312
|
...emitter.generateErrors(ctx),
|
|
1890
|
-
...emitter.generateConfig(ctx),
|
|
1891
2313
|
...emitter.generateTypeSignatures?.(reachableSpec, ctx) ?? [],
|
|
1892
2314
|
...emitter.generateTests(reachableSpec, ctx),
|
|
1893
2315
|
...emitter.generateManifest?.(spec, ctx) ?? []
|
|
@@ -1971,11 +2393,33 @@ async function treeShakeFiles(files, language) {
|
|
|
1971
2393
|
}
|
|
1972
2394
|
deps.set(file.path, fileDeps);
|
|
1973
2395
|
}
|
|
2396
|
+
const codeExtensions = new Set([
|
|
2397
|
+
".ts",
|
|
2398
|
+
".js",
|
|
2399
|
+
".tsx",
|
|
2400
|
+
".jsx",
|
|
2401
|
+
".py",
|
|
2402
|
+
".rb",
|
|
2403
|
+
".go",
|
|
2404
|
+
".rs",
|
|
2405
|
+
".kt",
|
|
2406
|
+
".cs",
|
|
2407
|
+
".ex",
|
|
2408
|
+
".exs",
|
|
2409
|
+
".php"
|
|
2410
|
+
]);
|
|
1974
2411
|
const reachable = /* @__PURE__ */ new Set();
|
|
1975
2412
|
const queue = [];
|
|
1976
|
-
for (const file of files)
|
|
1977
|
-
|
|
1978
|
-
|
|
2413
|
+
for (const file of files) {
|
|
2414
|
+
const ext = file.path.slice(file.path.lastIndexOf("."));
|
|
2415
|
+
if (!codeExtensions.has(ext)) {
|
|
2416
|
+
reachable.add(file.path);
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (isRootFile(file.path)) {
|
|
2420
|
+
reachable.add(file.path);
|
|
2421
|
+
queue.push(file.path);
|
|
2422
|
+
}
|
|
1979
2423
|
}
|
|
1980
2424
|
while (queue.length > 0) {
|
|
1981
2425
|
const current = queue.pop();
|
|
@@ -1999,11 +2443,61 @@ function mapFilesForTargetIntegration(files, language) {
|
|
|
1999
2443
|
};
|
|
2000
2444
|
});
|
|
2001
2445
|
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Resolve file-to-directory conflicts in the target.
|
|
2448
|
+
*
|
|
2449
|
+
* When a generated file creates a package directory (e.g., `api_keys/__init__.py`)
|
|
2450
|
+
* that would shadow an existing module file (e.g., `api_keys.py`), move the
|
|
2451
|
+
* module file's content into `_compat.py` inside the package and add a
|
|
2452
|
+
* re-export from `__init__.py`. This preserves backward-compatible imports
|
|
2453
|
+
* (e.g., `from workos.api_keys import ApiKeysModule`) while the package directory
|
|
2454
|
+
* provides the generated models and resource classes.
|
|
2455
|
+
*/
|
|
2456
|
+
async function resolveFileToDirectoryConflicts(files, targetDir) {
|
|
2457
|
+
const result = [...files];
|
|
2458
|
+
const removals = [];
|
|
2459
|
+
for (let i = 0; i < result.length; i++) {
|
|
2460
|
+
const file = result[i];
|
|
2461
|
+
const match = file.path.match(/^(.+)\/__init__\.py$/);
|
|
2462
|
+
if (!match) continue;
|
|
2463
|
+
const dirPath = match[1];
|
|
2464
|
+
const moduleFilePath = `${dirPath}.py`;
|
|
2465
|
+
const fullModulePath = path.join(targetDir, moduleFilePath);
|
|
2466
|
+
let moduleContent;
|
|
2467
|
+
try {
|
|
2468
|
+
moduleContent = await fs.readFile(fullModulePath, "utf-8");
|
|
2469
|
+
} catch {
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
2472
|
+
const compatPath = `${dirPath}/_compat.py`;
|
|
2473
|
+
result.push({
|
|
2474
|
+
path: compatPath,
|
|
2475
|
+
content: moduleContent,
|
|
2476
|
+
overwriteExisting: true
|
|
2477
|
+
});
|
|
2478
|
+
const compatImport = "from ._compat import * # noqa: F401,F403 # pyright: ignore[reportUnusedImport]";
|
|
2479
|
+
const existingContent = file.content.endsWith("\n") ? file.content : file.content + "\n";
|
|
2480
|
+
result[i] = {
|
|
2481
|
+
...file,
|
|
2482
|
+
content: existingContent + compatImport + "\n"
|
|
2483
|
+
};
|
|
2484
|
+
removals.push(fullModulePath);
|
|
2485
|
+
}
|
|
2486
|
+
return {
|
|
2487
|
+
files: result,
|
|
2488
|
+
removals
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2002
2491
|
async function integrateGeneratedFiles(opts) {
|
|
2003
|
-
|
|
2492
|
+
const { files: resolved, removals } = await resolveFileToDirectoryConflicts(await treeShakeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.language), opts.targetDir);
|
|
2493
|
+
const result = await writeFiles(resolved, opts.targetDir, {
|
|
2004
2494
|
language: opts.language,
|
|
2005
2495
|
header: opts.header
|
|
2006
2496
|
});
|
|
2497
|
+
for (const filePath of removals) try {
|
|
2498
|
+
await fs.unlink(filePath);
|
|
2499
|
+
} catch {}
|
|
2500
|
+
return result;
|
|
2007
2501
|
}
|
|
2008
2502
|
//#endregion
|
|
2009
2503
|
//#region src/engine/formatter.ts
|
|
@@ -2064,7 +2558,11 @@ async function generate(spec, emitter, options) {
|
|
|
2064
2558
|
if (targetResult.written.length > 0) console.log(`Target: created ${targetResult.written.length} new files`);
|
|
2065
2559
|
if (targetResult.merged.length > 0) console.log(`Target: merged into ${targetResult.merged.length} existing files (additive only)`);
|
|
2066
2560
|
if (targetResult.skipped.length > 0) console.log(`Target: skipped ${targetResult.skipped.length} files (excluded or no grammar)`);
|
|
2067
|
-
const allTargetFiles = [
|
|
2561
|
+
const allTargetFiles = [
|
|
2562
|
+
...targetResult.written,
|
|
2563
|
+
...targetResult.merged,
|
|
2564
|
+
...targetResult.identical
|
|
2565
|
+
];
|
|
2068
2566
|
if (allTargetFiles.length > 0) await formatTargetFiles(emitter, options.target, allTargetFiles);
|
|
2069
2567
|
}
|
|
2070
2568
|
return withHeaders;
|
|
@@ -2427,6 +2925,6 @@ function summarize(changes) {
|
|
|
2427
2925
|
};
|
|
2428
2926
|
}
|
|
2429
2927
|
//#endregion
|
|
2430
|
-
export { integrateGeneratedFiles as a, applyFileHeaders as c, generateAllFiles as d, generateFiles as f, generate as i, buildEmitterContext as l, getEmitter as n, mapFilesForTargetIntegration as o, registerEmitter as r, treeShakeFiles as s, diffSpecs as t, collectReferencedNames as u };
|
|
2928
|
+
export { integrateGeneratedFiles as a, applyFileHeaders as c, generateAllFiles as d, generateFiles as f, generate as i, buildEmitterContext as l, resolveOperations as m, getEmitter as n, mapFilesForTargetIntegration as o, deriveMethodName as p, registerEmitter as r, treeShakeFiles as s, diffSpecs as t, collectReferencedNames as u };
|
|
2431
2929
|
|
|
2432
|
-
//# sourceMappingURL=diff-
|
|
2930
|
+
//# sourceMappingURL=diff-CSy3g-Bq.mjs.map
|