@workos/oagen 0.0.1 → 0.1.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 -3
- package/dist/{chunk-P5XUDUGY.js → chunk-VUSRNIG2.js} +5 -92
- package/dist/cli/index.js +177 -466
- package/dist/compat/index.js +40 -2
- package/dist/index.d.ts +2 -25
- package/dist/index.js +122 -290
- package/dist/scripts/smoke/baseline.js +88 -26
- package/dist/smoke.js +88 -26
- package/dist/verify/index.js +1 -1
- package/docs/agents/architecture.md +1 -1
- package/docs/agents/emitters.md +2 -2
- package/docs/architecture/dependency-layers.md +1 -5
- package/docs/architecture/emitter-contract.md +13 -10
- package/docs/architecture/extractor-contract.md +30 -22
- package/docs/architecture/ir-types.md +2 -1
- package/docs/architecture/pipeline.md +1 -1
- package/docs/architecture/workflows.md +21 -20
- package/docs/cli.md +10 -14
- package/docs/contributor/public-api.md +2 -2
- package/package.json +1 -1
- package/skills/check-emitter-parity/SKILL.md +232 -0
package/README.md
CHANGED
|
@@ -72,9 +72,7 @@ import {
|
|
|
72
72
|
diffSpecs,
|
|
73
73
|
generate,
|
|
74
74
|
generateFiles,
|
|
75
|
-
generateIncremental,
|
|
76
75
|
getEmitter,
|
|
77
|
-
mapChangesToFiles,
|
|
78
76
|
parseSpec,
|
|
79
77
|
planOperation,
|
|
80
78
|
registerEmitter,
|
|
@@ -147,7 +145,7 @@ Start with:
|
|
|
147
145
|
| `oagen parse` | Parse a spec and print IR JSON |
|
|
148
146
|
| `oagen init` | Scaffold an emitter project |
|
|
149
147
|
| `oagen generate` | Run a registered emitter |
|
|
150
|
-
| `oagen diff` |
|
|
148
|
+
| `oagen diff` | Compare two specs and output a diff report |
|
|
151
149
|
| `oagen extract` | Advanced: extract an SDK API surface for compat use |
|
|
152
150
|
| `oagen verify` | Advanced: smoke-test output and run compat checks |
|
|
153
151
|
|
|
@@ -44,10 +44,6 @@ function generateFiles(spec, emitter, options) {
|
|
|
44
44
|
return { files: applyFileHeaders(files, header), ctx, header };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// src/engine/integrate.ts
|
|
48
|
-
import * as fs2 from "fs";
|
|
49
|
-
import * as path2 from "path";
|
|
50
|
-
|
|
51
47
|
// src/engine/writer.ts
|
|
52
48
|
import * as fs from "fs/promises";
|
|
53
49
|
import * as path from "path";
|
|
@@ -95,11 +91,11 @@ function declarationKey(node) {
|
|
|
95
91
|
function importEntries(node, source) {
|
|
96
92
|
const entries = [];
|
|
97
93
|
const collect = (spec) => {
|
|
98
|
-
const
|
|
94
|
+
const path2 = spec.childForFieldName("path")?.text ?? spec.text;
|
|
99
95
|
const name = spec.childForFieldName("name")?.text ?? "";
|
|
100
96
|
const text = source.slice(spec.startIndex, spec.endIndex);
|
|
101
97
|
entries.push({
|
|
102
|
-
key: `${name}:${
|
|
98
|
+
key: `${name}:${path2.replace(/^"|"$/g, "")}`,
|
|
103
99
|
text
|
|
104
100
|
});
|
|
105
101
|
};
|
|
@@ -896,75 +892,11 @@ async function extractStatements(source, language) {
|
|
|
896
892
|
const tree = safeParse(parser, source);
|
|
897
893
|
return adapter.parseStatements(tree, source);
|
|
898
894
|
}
|
|
899
|
-
async function mergeIntoExisting(existingContent, generatedContent, language, header
|
|
895
|
+
async function mergeIntoExisting(existingContent, generatedContent, language, header) {
|
|
900
896
|
const adapter = getMergeAdapter(language);
|
|
901
897
|
if (!adapter) {
|
|
902
898
|
throw new Error(`No merge adapter configured for language "${language}"`);
|
|
903
899
|
}
|
|
904
|
-
if (options?.docstringOnly) {
|
|
905
|
-
let result2 = existingContent;
|
|
906
|
-
let docstringUpdates2 = 0;
|
|
907
|
-
const parser = await getParser(language);
|
|
908
|
-
const resultTree = safeParse(parser, result2);
|
|
909
|
-
const generatedTree = safeParse(parser, generatedContent);
|
|
910
|
-
const resultDocs = adapter.extractDocstrings(resultTree, result2);
|
|
911
|
-
const generatedDocs = adapter.extractDocstrings(generatedTree, generatedContent);
|
|
912
|
-
const ignoredRegions = findIgnoredRegions(result2);
|
|
913
|
-
const ignoredSymbols = buildIgnoredSymbolNames(resultDocs, ignoredRegions);
|
|
914
|
-
const headerLine2 = header.trim();
|
|
915
|
-
const edits = [];
|
|
916
|
-
for (const [symbolName, genInfo] of generatedDocs) {
|
|
917
|
-
const existInfo = resultDocs.get(symbolName);
|
|
918
|
-
if (!existInfo) continue;
|
|
919
|
-
if (ignoredSymbols.has(symbolName)) continue;
|
|
920
|
-
const genDoc = genInfo.docstring && genInfo.docstring.text.trim() !== headerLine2 ? genInfo.docstring : null;
|
|
921
|
-
if (genDoc) {
|
|
922
|
-
if (existInfo.docstring) {
|
|
923
|
-
const isPreserved = existInfo.docstring.text.includes("@oagen-ignore");
|
|
924
|
-
if (!isPreserved && existInfo.docstring.text !== genDoc.text) {
|
|
925
|
-
edits.push({
|
|
926
|
-
start: existInfo.docstring.startIndex,
|
|
927
|
-
end: existInfo.docstring.endIndex,
|
|
928
|
-
newText: genDoc.text
|
|
929
|
-
});
|
|
930
|
-
docstringUpdates2++;
|
|
931
|
-
}
|
|
932
|
-
} else {
|
|
933
|
-
const lineStart = existInfo.declStartIndex - existInfo.declColumn;
|
|
934
|
-
const indent = " ".repeat(existInfo.declColumn);
|
|
935
|
-
edits.push({ start: lineStart, end: lineStart, newText: indent + genDoc.text + "\n" });
|
|
936
|
-
docstringUpdates2++;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
for (const [memberName, genMember] of genInfo.members) {
|
|
940
|
-
const existMember = existInfo.members.get(memberName);
|
|
941
|
-
if (!existMember || !genMember.docstring) continue;
|
|
942
|
-
if (existMember.docstring) {
|
|
943
|
-
const isPreserved = existMember.docstring.text.includes("@oagen-ignore");
|
|
944
|
-
if (!isPreserved && existMember.docstring.text !== genMember.docstring.text) {
|
|
945
|
-
edits.push({
|
|
946
|
-
start: existMember.docstring.startIndex,
|
|
947
|
-
end: existMember.docstring.endIndex,
|
|
948
|
-
newText: genMember.docstring.text
|
|
949
|
-
});
|
|
950
|
-
docstringUpdates2++;
|
|
951
|
-
}
|
|
952
|
-
} else {
|
|
953
|
-
const lineStart = existMember.declStartIndex - existMember.declColumn;
|
|
954
|
-
const indent = " ".repeat(existMember.declColumn);
|
|
955
|
-
edits.push({ start: lineStart, end: lineStart, newText: indent + genMember.docstring.text + "\n" });
|
|
956
|
-
docstringUpdates2++;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
if (edits.length > 0) {
|
|
961
|
-
edits.sort((a, b) => b.start - a.start);
|
|
962
|
-
for (const edit of edits) {
|
|
963
|
-
result2 = result2.slice(0, edit.start) + edit.newText + result2.slice(edit.end);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
return { content: result2, added: 0, preserved: 0, changed: docstringUpdates2 > 0 };
|
|
967
|
-
}
|
|
968
900
|
const existingStatements = await extractStatements(existingContent, language);
|
|
969
901
|
const generatedStatements = await extractStatements(generatedContent, language);
|
|
970
902
|
const existingKeys = /* @__PURE__ */ new Set();
|
|
@@ -1237,22 +1169,6 @@ async function writeFiles(files, outputDir, options) {
|
|
|
1237
1169
|
}
|
|
1238
1170
|
if (language && hasGrammar(language)) {
|
|
1239
1171
|
try {
|
|
1240
|
-
if (file.mergeMode === "docstring-only") {
|
|
1241
|
-
const mergeResult2 = await mergeIntoExisting(existingContent, file.content, language, header, {
|
|
1242
|
-
docstringOnly: true
|
|
1243
|
-
});
|
|
1244
|
-
let finalContent2 = mergeResult2.content;
|
|
1245
|
-
if (header && !finalContent2.startsWith(header)) {
|
|
1246
|
-
finalContent2 = header + "\n\n" + finalContent2;
|
|
1247
|
-
}
|
|
1248
|
-
if (finalContent2 === existingContent) {
|
|
1249
|
-
result.identical.push(file.path);
|
|
1250
|
-
} else {
|
|
1251
|
-
await fs.writeFile(fullPath, finalContent2, "utf-8");
|
|
1252
|
-
result.merged.push(file.path);
|
|
1253
|
-
}
|
|
1254
|
-
continue;
|
|
1255
|
-
}
|
|
1256
1172
|
const mergeResult = await mergeIntoExisting(existingContent, file.content, language, header);
|
|
1257
1173
|
let finalContent = mergeResult.content;
|
|
1258
1174
|
if (header && !finalContent.startsWith(header)) {
|
|
@@ -1280,22 +1196,20 @@ async function writeFiles(files, outputDir, options) {
|
|
|
1280
1196
|
}
|
|
1281
1197
|
|
|
1282
1198
|
// src/engine/integrate.ts
|
|
1283
|
-
function mapFilesForTargetIntegration(files, language
|
|
1199
|
+
function mapFilesForTargetIntegration(files, language) {
|
|
1284
1200
|
const langPrefix = `${language}/`;
|
|
1285
1201
|
return files.filter((f) => f.integrateTarget !== false).map((f) => {
|
|
1286
1202
|
const stripped = f.path.startsWith(langPrefix) ? f.path.replace(langPrefix, "") : f.path;
|
|
1287
|
-
const existsInTarget = targetDir ? fs2.existsSync(path2.join(targetDir, stripped)) : false;
|
|
1288
1203
|
return {
|
|
1289
1204
|
...f,
|
|
1290
1205
|
skipIfExists: false,
|
|
1291
1206
|
// Always merge in target — never hard-skip
|
|
1292
|
-
mergeMode: existsInTarget ? "docstring-only" : "full",
|
|
1293
1207
|
path: stripped
|
|
1294
1208
|
};
|
|
1295
1209
|
});
|
|
1296
1210
|
}
|
|
1297
1211
|
async function integrateGeneratedFiles(opts) {
|
|
1298
|
-
return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language
|
|
1212
|
+
return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.targetDir, {
|
|
1299
1213
|
language: opts.language,
|
|
1300
1214
|
header: opts.header
|
|
1301
1215
|
});
|
|
@@ -1734,7 +1648,6 @@ function summarize(changes) {
|
|
|
1734
1648
|
}
|
|
1735
1649
|
|
|
1736
1650
|
export {
|
|
1737
|
-
writeFiles,
|
|
1738
1651
|
buildEmitterContext,
|
|
1739
1652
|
generateAllFiles,
|
|
1740
1653
|
applyFileHeaders,
|