@workos/oagen 0.2.0 → 0.3.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.
@@ -1,5 +1,5 @@
1
- import { t as safeParse$1, v as ExtractorError } from "../tree-sitter-BPNfbEzY.mjs";
2
- import { _ as registerExtractor, a as specDerivedHttpKeys, c as buildOverlayLookup, d as defaultIsNullableOnlyDifference, f as namedTypeWordsOverlap, g as getExtractor, h as typeExistsInSurface, i as specDerivedFieldPaths, l as patchOverlay, m as resolveHints, n as filterSurface, o as specDerivedMethodPaths, p as nodeHints, r as specDerivedEnumValues, s as specDerivedNames, t as diffSurfaces, u as NAMED_TYPE_RE } from "../differ-BwelBJN0.mjs";
1
+ import { t as safeParse$1, v as ExtractorError } from "../tree-sitter-DFRXfWg9.mjs";
2
+ import { _ as registerExtractor, a as specDerivedHttpKeys, c as buildOverlayLookup, d as defaultIsNullableOnlyDifference, f as namedTypeWordsOverlap, g as getExtractor, h as typeExistsInSurface, i as specDerivedFieldPaths, l as patchOverlay, m as resolveHints, n as filterSurface, o as specDerivedMethodPaths, p as nodeHints, r as specDerivedEnumValues, s as specDerivedNames, t as diffSurfaces, u as NAMED_TYPE_RE } from "../differ-B6472Yef.mjs";
3
3
  import path, { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import Parser from "tree-sitter";
5
5
  import ts from "typescript";
@@ -1,7 +1,10 @@
1
- import { C as assertNever, f as toSnakeCase, i as normalizeJsExtension, t as safeParse, x as RegistryError } from "./tree-sitter-BPNfbEzY.mjs";
1
+ import { A as walkTypeRef, C as assertNever, f as toSnakeCase, i as normalizeJsExtension, t as safeParse, x as RegistryError } from "./tree-sitter-DFRXfWg9.mjs";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path$1 from "node:path";
4
+ import path from "node:path";
4
5
  import Parser from "tree-sitter";
6
+ import { execFile } from "node:child_process";
7
+ import { promisify } from "node:util";
5
8
  //#region src/engine/merge-adapters/url-fingerprint.ts
6
9
  /**
7
10
  * Extract a URL path fingerprint from a method body using tree-sitter AST traversal.
@@ -1558,11 +1561,27 @@ async function mergeIntoExisting(existingContent, generatedContent, language, he
1558
1561
  const addedParts = [...toAppend];
1559
1562
  if (insertions) for (const ins of insertions) addedParts.push(ins.text);
1560
1563
  const addedCodeText = addedParts.join("\n");
1561
- const filteredImports = newImports.filter((imp) => {
1564
+ const filteredImports = [];
1565
+ for (const imp of newImports) {
1562
1566
  const braceMatch = imp.text.match(/\{([^}]+)\}/);
1563
- if (!braceMatch) return true;
1564
- return braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean).some((name) => addedCodeText.includes(name));
1565
- });
1567
+ if (!braceMatch) {
1568
+ filteredImports.push(imp);
1569
+ continue;
1570
+ }
1571
+ const names = braceMatch[1].split(",").map((n) => n.replace(/\btype\b/, "").trim()).filter(Boolean);
1572
+ const usedNames = names.filter((name) => addedCodeText.includes(name));
1573
+ if (usedNames.length === 0) continue;
1574
+ if (usedNames.length === names.length) filteredImports.push(imp);
1575
+ else {
1576
+ const prefix = imp.text.trimStart().startsWith("import type") ? "import type" : "import";
1577
+ const sourceMatch = imp.text.match(/from\s+(['"][^'"]+['"]);?/);
1578
+ if (sourceMatch) filteredImports.push({
1579
+ key: imp.key,
1580
+ text: `${prefix} { ${usedNames.join(", ")} } from ${sourceMatch[1]};`
1581
+ });
1582
+ else filteredImports.push(imp);
1583
+ }
1584
+ }
1566
1585
  if (filteredImports.length > 0) {
1567
1586
  const renderedImports = adapter.renderImports ? adapter.renderImports(filteredImports) : filteredImports.map((entry) => entry.text);
1568
1587
  const lines = result.split("\n");
@@ -1806,17 +1825,71 @@ function buildEmitterContext(spec, options) {
1806
1825
  overlayLookup: options.overlayLookup
1807
1826
  };
1808
1827
  }
1828
+ /**
1829
+ * Collect model and enum names transitively referenced by service operations.
1830
+ * Walks operation parameters, request bodies, responses, and pagination item
1831
+ * types, then chases model field references until the set stabilizes.
1832
+ */
1833
+ function collectReferencedNames(services, models) {
1834
+ const referencedModels = /* @__PURE__ */ new Set();
1835
+ const referencedEnums = /* @__PURE__ */ new Set();
1836
+ const collectFromTypeRef = (ref) => {
1837
+ walkTypeRef(ref, {
1838
+ model: (r) => referencedModels.add(r.name),
1839
+ enum: (r) => referencedEnums.add(r.name)
1840
+ });
1841
+ };
1842
+ for (const service of services) for (const op of service.operations) {
1843
+ for (const p of [
1844
+ ...op.pathParams,
1845
+ ...op.queryParams,
1846
+ ...op.headerParams,
1847
+ ...op.cookieParams ?? []
1848
+ ]) collectFromTypeRef(p.type);
1849
+ if (op.requestBody) collectFromTypeRef(op.requestBody);
1850
+ collectFromTypeRef(op.response);
1851
+ if (op.pagination) collectFromTypeRef(op.pagination.itemType);
1852
+ for (const err of op.errors) if (err.type) collectFromTypeRef(err.type);
1853
+ if (op.successResponses) for (const sr of op.successResponses) collectFromTypeRef(sr.type);
1854
+ }
1855
+ const modelsByName = new Map(models.map((m) => [m.name, m]));
1856
+ const visited = /* @__PURE__ */ new Set();
1857
+ const queue = [...referencedModels];
1858
+ while (queue.length > 0) {
1859
+ const name = queue.pop();
1860
+ if (visited.has(name)) continue;
1861
+ visited.add(name);
1862
+ const model = modelsByName.get(name);
1863
+ if (!model) continue;
1864
+ for (const field of model.fields) {
1865
+ collectFromTypeRef(field.type);
1866
+ for (const m of referencedModels) if (!visited.has(m)) queue.push(m);
1867
+ }
1868
+ }
1869
+ return {
1870
+ models: referencedModels,
1871
+ enums: referencedEnums
1872
+ };
1873
+ }
1809
1874
  /** Collect all generated files from an emitter (no headers, no path prefixes). */
1810
1875
  function generateAllFiles(spec, emitter, ctx) {
1876
+ const referenced = collectReferencedNames(spec.services, spec.models);
1877
+ const reachableModels = spec.models.filter((m) => referenced.models.has(m.name));
1878
+ const reachableEnums = spec.enums.filter((e) => referenced.enums.has(e.name));
1879
+ const reachableSpec = {
1880
+ ...spec,
1881
+ models: reachableModels,
1882
+ enums: reachableEnums
1883
+ };
1811
1884
  return [
1812
- ...emitter.generateModels(spec.models, ctx),
1813
- ...emitter.generateEnums(spec.enums, ctx),
1885
+ ...emitter.generateModels(reachableModels, ctx),
1886
+ ...emitter.generateEnums(reachableEnums, ctx),
1814
1887
  ...emitter.generateResources(spec.services, ctx),
1815
1888
  ...emitter.generateClient(spec, ctx),
1816
1889
  ...emitter.generateErrors(ctx),
1817
1890
  ...emitter.generateConfig(ctx),
1818
- ...emitter.generateTypeSignatures?.(spec, ctx) ?? [],
1819
- ...emitter.generateTests(spec, ctx),
1891
+ ...emitter.generateTypeSignatures?.(reachableSpec, ctx) ?? [],
1892
+ ...emitter.generateTests(reachableSpec, ctx),
1820
1893
  ...emitter.generateManifest?.(spec, ctx) ?? []
1821
1894
  ];
1822
1895
  }
@@ -1840,6 +1913,81 @@ function generateFiles(spec, emitter, options) {
1840
1913
  }
1841
1914
  //#endregion
1842
1915
  //#region src/engine/integrate.ts
1916
+ /**
1917
+ * Resolve a relative import path against a source file path to get the
1918
+ * target file path. Handles implicit .ts extensions and /index.ts.
1919
+ */
1920
+ function resolveImportPath(fromFilePath, importModulePath, knownPaths) {
1921
+ const cleaned = importModulePath.replace(/^['"]|['"]$/g, "");
1922
+ if (!cleaned.startsWith(".")) return void 0;
1923
+ const dir = path.dirname(fromFilePath);
1924
+ const resolved = path.normalize(path.join(dir, cleaned));
1925
+ for (const candidate of [
1926
+ resolved,
1927
+ `${resolved}.ts`,
1928
+ `${resolved}/index.ts`
1929
+ ]) if (knownPaths.has(candidate)) return candidate;
1930
+ }
1931
+ /**
1932
+ * Determine whether a generated file is a "root" — a file that should always
1933
+ * be integrated into the target. Roots are entry points from which we trace
1934
+ * the import graph. Everything else must be reachable from a root.
1935
+ *
1936
+ * Roots: resource classes, client, config, errors, enums, barrel index files.
1937
+ * Non-roots: serializers, fixtures, standalone interfaces, test files.
1938
+ */
1939
+ function isRootFile(filePath) {
1940
+ if (filePath.includes("/fixtures/")) return false;
1941
+ if (filePath.endsWith(".spec.ts") || filePath.endsWith(".test.ts")) return false;
1942
+ if (filePath.includes("/serializers/")) return false;
1943
+ if (filePath.includes("/interfaces/") && !filePath.endsWith("/index.ts")) return false;
1944
+ return true;
1945
+ }
1946
+ /**
1947
+ * Build a file-level dependency graph from generated files and return only
1948
+ * files reachable from root entry points. Uses tree-sitter to parse imports.
1949
+ */
1950
+ async function treeShakeFiles(files, language) {
1951
+ if (!hasGrammar(language)) return files;
1952
+ const knownPaths = new Set(files.map((f) => f.path));
1953
+ const deps = /* @__PURE__ */ new Map();
1954
+ for (const file of files) {
1955
+ if (!file.path.endsWith(".ts") && !file.path.endsWith(".js")) {
1956
+ deps.set(file.path, /* @__PURE__ */ new Set());
1957
+ continue;
1958
+ }
1959
+ const parsed = await extractStatements(file.content, language);
1960
+ const fileDeps = /* @__PURE__ */ new Set();
1961
+ for (const imp of parsed.imports) {
1962
+ const resolved = resolveImportPath(file.path, imp.key, knownPaths);
1963
+ if (resolved) fileDeps.add(resolved);
1964
+ }
1965
+ for (const stmt of parsed.statements) if (stmt.kind === "reexport") {
1966
+ const match = stmt.text.match(/from\s+['"]([^'"]+)['"]/);
1967
+ if (match) {
1968
+ const resolved = resolveImportPath(file.path, match[1], knownPaths);
1969
+ if (resolved) fileDeps.add(resolved);
1970
+ }
1971
+ }
1972
+ deps.set(file.path, fileDeps);
1973
+ }
1974
+ const reachable = /* @__PURE__ */ new Set();
1975
+ const queue = [];
1976
+ for (const file of files) if (isRootFile(file.path)) {
1977
+ reachable.add(file.path);
1978
+ queue.push(file.path);
1979
+ }
1980
+ while (queue.length > 0) {
1981
+ const current = queue.pop();
1982
+ const fileDeps = deps.get(current);
1983
+ if (!fileDeps) continue;
1984
+ for (const dep of fileDeps) if (!reachable.has(dep)) {
1985
+ reachable.add(dep);
1986
+ queue.push(dep);
1987
+ }
1988
+ }
1989
+ return files.filter((f) => reachable.has(f.path));
1990
+ }
1843
1991
  function mapFilesForTargetIntegration(files, language) {
1844
1992
  const langPrefix = `${language}/`;
1845
1993
  return files.filter((f) => f.integrateTarget !== false).map((f) => {
@@ -1852,12 +2000,44 @@ function mapFilesForTargetIntegration(files, language) {
1852
2000
  });
1853
2001
  }
1854
2002
  async function integrateGeneratedFiles(opts) {
1855
- return writeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.targetDir, {
2003
+ return writeFiles(await treeShakeFiles(mapFilesForTargetIntegration(opts.files, opts.language), opts.language), opts.targetDir, {
1856
2004
  language: opts.language,
1857
2005
  header: opts.header
1858
2006
  });
1859
2007
  }
1860
2008
  //#endregion
2009
+ //#region src/engine/formatter.ts
2010
+ /**
2011
+ * Post-integration formatter.
2012
+ *
2013
+ * After generating and merging files into a target directory, runs the
2014
+ * emitter-provided format command on all written/merged files so that
2015
+ * generated code matches the target project's style conventions.
2016
+ */
2017
+ const execFileAsync = promisify(execFile);
2018
+ /**
2019
+ * Run the emitter's format command on the given target files.
2020
+ * Silently skips if the emitter has no formatCommand or returns null.
2021
+ */
2022
+ async function formatTargetFiles(emitter, targetDir, filePaths) {
2023
+ if (filePaths.length === 0) return;
2024
+ if (!emitter.formatCommand) return;
2025
+ const formatCmd = emitter.formatCommand(targetDir);
2026
+ if (!formatCmd) return;
2027
+ const absolutePaths = filePaths.map((f) => path$1.resolve(targetDir, f));
2028
+ const batchSize = formatCmd.batchSize ?? 100;
2029
+ for (let i = 0; i < absolutePaths.length; i += batchSize) {
2030
+ const batch = absolutePaths.slice(i, i + batchSize);
2031
+ try {
2032
+ await execFileAsync(formatCmd.cmd, [...formatCmd.args, ...batch], {
2033
+ cwd: targetDir,
2034
+ maxBuffer: 10 * 1024 * 1024,
2035
+ timeout: 12e4
2036
+ });
2037
+ } catch {}
2038
+ }
2039
+ }
2040
+ //#endregion
1861
2041
  //#region src/engine/orchestrator.ts
1862
2042
  async function generate(spec, emitter, options) {
1863
2043
  const { files: withHeaders, header } = generateFiles(spec, emitter, options);
@@ -1884,6 +2064,8 @@ async function generate(spec, emitter, options) {
1884
2064
  if (targetResult.written.length > 0) console.log(`Target: created ${targetResult.written.length} new files`);
1885
2065
  if (targetResult.merged.length > 0) console.log(`Target: merged into ${targetResult.merged.length} existing files (additive only)`);
1886
2066
  if (targetResult.skipped.length > 0) console.log(`Target: skipped ${targetResult.skipped.length} files (excluded or no grammar)`);
2067
+ const allTargetFiles = [...targetResult.written, ...targetResult.merged];
2068
+ if (allTargetFiles.length > 0) await formatTargetFiles(emitter, options.target, allTargetFiles);
1887
2069
  }
1888
2070
  return withHeaders;
1889
2071
  }
@@ -2245,6 +2427,6 @@ function summarize(changes) {
2245
2427
  };
2246
2428
  }
2247
2429
  //#endregion
2248
- export { integrateGeneratedFiles as a, buildEmitterContext as c, generate as i, generateAllFiles as l, getEmitter as n, mapFilesForTargetIntegration as o, registerEmitter as r, applyFileHeaders as s, diffSpecs as t, generateFiles as u };
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 };
2249
2431
 
2250
- //# sourceMappingURL=diff-DyQF4sel.mjs.map
2432
+ //# sourceMappingURL=diff-Cn6S74vV.mjs.map