@kubb/core 5.11.4 → 5.11.6

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.
@@ -14,7 +14,7 @@ var __name = (target, value) => __defProp(target, "name", {
14
14
  });
15
15
  //#endregion
16
16
  //#region package.json
17
- var version = "5.11.4";
17
+ var version = "5.11.6";
18
18
  //#endregion
19
19
  //#region ../../internals/utils/src/casing.ts
20
20
  /**
@@ -614,13 +614,15 @@ function serialize(diagnostic) {
614
614
  * with `at`, `fix`, the first cause, and `Learn more` for an error, and one row for a warning.
615
615
  * `verbose` adds the full stack.
616
616
  */
617
- function format(diagnostic, { audience = "agent", verbose = false, width = 0 } = {}) {
617
+ function format(diagnostic, { audience = "agent", verbose = false, width = 0, position } = {}) {
618
618
  const { code, severity, message } = diagnostic;
619
619
  const problem = Diagnostics.isProblem(diagnostic) ? diagnostic : void 0;
620
620
  const human = audience === "human";
621
621
  const paint = human ? styleText : (_format, text) => text;
622
622
  const [firstLine = "", ...rest] = message.split("\n");
623
623
  const pointer = problem?.location && "pointer" in problem.location ? problem.location.pointer : void 0;
624
+ const at = position ? `${position.file}:${position.line}:${position.column}` : void 0;
625
+ const where = at ?? pointer;
624
626
  const help = problem ? getHelp(problem) : void 0;
625
627
  const causes = problem ? getCauses(problem) : [];
626
628
  const crash = problem ? crashCodes.has(problem.code) : false;
@@ -630,7 +632,7 @@ function format(diagnostic, { audience = "agent", verbose = false, width = 0 } =
630
632
  const row = (label, value) => {
631
633
  if (value) details.push(` ${label}: ${value}`);
632
634
  };
633
- row("at", pointer);
635
+ row("at", at && pointer ? `${at} (${pointer})` : pointer);
634
636
  if (problem && severity !== "info") row("why", Diagnostics.explain(problem.code).cause);
635
637
  row("fix", help);
636
638
  for (const cause of causes) row("caused by", cause);
@@ -640,24 +642,25 @@ function format(diagnostic, { audience = "agent", verbose = false, width = 0 } =
640
642
  return [`${severity} ${code}${problem?.plugin ? ` [${problem.plugin}]` : ""}: ${firstLine}`, ...details];
641
643
  }
642
644
  const left = `${paint(severityStyle[severity], severityIcon[severity])} ${paint("bold", firstLine)}`;
643
- const gap = Math.max(2, width - stripVTControlCharacters(left).length - code.length);
644
- const headline = `${left}${" ".repeat(gap)}${paint("dim", code)}`;
645
+ const gap = width - stripVTControlCharacters(left).length - code.length;
646
+ const headline = width && gap < 2 ? [left, `${" ".repeat(Math.max(0, width - code.length))}${paint("dim", code)}`] : [`${left}${" ".repeat(Math.max(2, gap))}${paint("dim", code)}`];
645
647
  const frame = rest.filter((line) => line.trim()).map((line) => ` ${paint("dim", line)}`);
646
648
  const label = (name) => paint("dim", name.length < 4 ? name.padEnd(4) : `${name} `);
647
- const rows = [["at", pointer && paint("cyan", pointer)], ["fix", help]];
649
+ const rows = [["at", where && paint("cyan", where)], ["fix", help]];
648
650
  if (severity !== "error") {
649
651
  const first = rows.find(([, value]) => value);
650
652
  return [
651
- headline,
653
+ ...headline,
652
654
  ...frame,
653
655
  ...first ? [` ${label(first[0])} ${first[1]}`] : []
654
656
  ];
655
657
  }
656
658
  rows.push(["caused by", causes[0]], ["report", problem?.issues && paint("cyan", problem.issues)]);
657
- const details = [...frame, ...rows.flatMap(([name, value]) => value ? [` ${label(name)} ${value}`] : [])];
659
+ const codeFrame = (position?.frame ?? []).map((line) => ` ${line}`);
660
+ const details = [...frame, ...rows.flatMap(([name, value]) => value ? [` ${label(name)} ${value}`, ...name === "at" ? codeFrame : []] : [])];
658
661
  if (frames?.length) details.push(` ${paint("dim", "stack")}`, ...frames);
659
662
  if (code !== diagnosticCode.unknown) details.push(` ${paint("dim", "↳ Learn more")} ${paint("cyan", Diagnostics.docsUrl(code).replace(/^https:\/\//, ""))}`);
660
- return [headline, ...details];
663
+ return [...headline, ...details];
661
664
  }
662
665
  //#endregion
663
666
  //#region src/Diagnostics.ts
@@ -709,6 +712,7 @@ const failedCode = {
709
712
  parser: diagnosticCode.parserFailed
710
713
  };
711
714
  const docsByCode = new Map(Object.values(diagnosticCatalog).map((doc) => [doc.code, doc]));
715
+ const reporterKey = Symbol.for("kubb.diagnostics.reporter");
712
716
  const countKey = {
713
717
  error: "errors",
714
718
  warning: "warnings",
@@ -718,13 +722,13 @@ const countKey = {
718
722
  * Static helpers for working with {@link Diagnostic}s, plus the run-scoped sink
719
723
  * that lets deep code report a diagnostic without threading a callback.
720
724
  *
721
- * The sink lives in a single `AsyncLocalStorage` in the `@kubb/core` bundle.
725
+ * The sink lives in a single `AsyncLocalStorage` on `globalThis`, shared by every copy of `@kubb/core`.
722
726
  * `Diagnostics.scope` activates it for a run, so anything inside that run (the
723
727
  * adapter parse, a generator) reports through `Diagnostics.report` and lands
724
728
  * in the same run.
725
729
  */
726
730
  var Diagnostics = class Diagnostics {
727
- static #reporterStorage = new AsyncLocalStorage();
731
+ static #reporterStorage = globalThis[reporterKey] ??= new AsyncLocalStorage();
728
732
  /**
729
733
  * The diagnostic code catalog, exposed as `Diagnostics.code` (e.g. `Diagnostics.code.refNotFound`).
730
734
  */
@@ -1067,7 +1071,7 @@ function defineExtension(definition) {
1067
1071
  return Object.assign(build(options), {
1068
1072
  name: meta.name,
1069
1073
  meta,
1070
- options
1074
+ options: raw
1071
1075
  });
1072
1076
  }
1073
1077
  });
@@ -1562,7 +1566,8 @@ var Resolver = class Resolver {
1562
1566
  #resolvePath({ baseName, tag, path: groupPath, root, output, group }) {
1563
1567
  if (output.mode !== "directory") return path.resolve(root, output.path);
1564
1568
  const outputDir = path.resolve(root, output.path);
1565
- const result = group && (groupPath || tag) ? path.resolve(outputDir, Resolver.#resolveGroupDir(group, group.type === "path" ? groupPath : tag), baseName) : path.resolve(outputDir, baseName);
1569
+ const groupValue = group?.type === "path" ? groupPath : tag;
1570
+ const result = group && groupValue ? path.resolve(outputDir, Resolver.#resolveGroupDir(group, groupValue), baseName) : path.resolve(outputDir, baseName);
1566
1571
  assertInside({
1567
1572
  target: result,
1568
1573
  parent: outputDir,
@@ -1803,10 +1808,7 @@ function runParser(parser, print) {
1803
1808
  * Reads a `copy` file and prints it through the parser's `copy` hook, or verbatim with `banner`/`footer` when there is none.
1804
1809
  */
1805
1810
  async function parseCopy(file, parser) {
1806
- let content;
1807
- try {
1808
- content = await readFile(file.copy, "utf8");
1809
- } catch (err) {
1811
+ const content = await readFile(file.copy, "utf8").catch((err) => {
1810
1812
  throw new Diagnostics.Error({
1811
1813
  code: Diagnostics.code.pluginFailed,
1812
1814
  severity: "error",
@@ -1814,7 +1816,7 @@ async function parseCopy(file, parser) {
1814
1816
  help: "Check that the file a plugin copies into the output exists and is readable.",
1815
1817
  cause: toError(err)
1816
1818
  });
1817
- }
1819
+ });
1818
1820
  if (parser?.copy) return runParser(parser, () => parser.parse(ast.factory.createFile(parser.copy(file, content))));
1819
1821
  return [
1820
1822
  file.banner,
@@ -1828,9 +1830,9 @@ function mergeFile(a, b) {
1828
1830
  banner: b.banner,
1829
1831
  footer: b.footer,
1830
1832
  copy: b.copy ?? a.copy,
1831
- sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
1832
- imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
1833
- exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
1833
+ sources: [...a.sources, ...b.sources],
1834
+ imports: [...a.imports, ...b.imports],
1835
+ exports: [...a.exports, ...b.exports]
1834
1836
  };
1835
1837
  }
1836
1838
  function isIndexPath(path) {
@@ -1839,17 +1841,49 @@ function isIndexPath(path) {
1839
1841
  function compareFiles(a, b) {
1840
1842
  return a.path.length - b.path.length || Number(isIndexPath(a.path)) - Number(isIndexPath(b.path));
1841
1843
  }
1842
- function isUnchanged({ stored, source, key, manifest }) {
1843
- if (stored === null) return false;
1844
- if (matchesStored({
1844
+ /**
1845
+ * Writes `source` to `file.path` unless the storage already holds it, either verbatim or, per the
1846
+ * manifest, as what the output passes make of it. Checking here rather than in a driver gives a
1847
+ * storage writing to S3 or a database the same guarantee `fsStorage` has. `false` when skipped.
1848
+ */
1849
+ async function writeChanged({ storage, manifest, file, source, signal }) {
1850
+ signal?.throwIfAborted();
1851
+ const stored = await storage.readItem(file.path);
1852
+ if (stored !== null && (matchesStored({
1845
1853
  stored,
1846
1854
  source
1847
- })) return true;
1848
- return manifest?.isUpToDate({
1849
- key,
1855
+ }) || manifest?.isUpToDate({
1856
+ key: file.path,
1850
1857
  source,
1851
1858
  disk: stored
1852
- }) ?? false;
1859
+ }))) return false;
1860
+ signal?.throwIfAborted();
1861
+ await storage.writeItem(file.path, source, { stored });
1862
+ manifest?.track({
1863
+ key: file.path,
1864
+ source
1865
+ });
1866
+ return true;
1867
+ }
1868
+ /** Reports finished files in input order: each finished file flushes the run of finished files ahead of it as one update. */
1869
+ function createProgress(files, config, hooks) {
1870
+ const finished = [];
1871
+ let flushed = 0;
1872
+ return async (index) => {
1873
+ finished[index] = true;
1874
+ const updates = [];
1875
+ while (finished[flushed]) {
1876
+ flushed++;
1877
+ updates.push({
1878
+ file: files[flushed - 1],
1879
+ processed: flushed,
1880
+ total: files.length,
1881
+ percentage: flushed / files.length * 100,
1882
+ config
1883
+ });
1884
+ }
1885
+ if (updates.length) await hooks.callHook("kubb:files:processing:update", { files: updates });
1886
+ };
1853
1887
  }
1854
1888
  /**
1855
1889
  * In-memory file store for generated files, and the writer that turns them into source
@@ -1894,25 +1928,17 @@ var FileManager = class {
1894
1928
  }
1895
1929
  }
1896
1930
  #store(files, mergeExisting) {
1897
- const batch = files.length > 1 ? this.#dedupe(files) : files;
1898
- const resolved = [];
1899
- for (const file of batch) {
1931
+ const batch = /* @__PURE__ */ new Set();
1932
+ const resolved = files.map((file) => {
1900
1933
  const existing = this.#cache.get(file.path);
1901
- const merged = existing && mergeExisting ? ast.factory.createFile(mergeFile(existing, file)) : ast.factory.createFile(file);
1934
+ const merged = ast.factory.createFile(existing && (mergeExisting || batch.has(file.path)) ? mergeFile(existing, file) : file);
1902
1935
  this.#cache.set(merged.path, merged);
1903
- resolved.push(merged);
1904
- }
1936
+ batch.add(merged.path);
1937
+ return merged;
1938
+ });
1905
1939
  if (resolved.length > 0) this.#sorted = null;
1906
1940
  return resolved;
1907
1941
  }
1908
- #dedupe(files) {
1909
- const seen = /* @__PURE__ */ new Map();
1910
- for (const file of files) {
1911
- const prev = seen.get(file.path);
1912
- seen.set(file.path, prev ? mergeFile(prev, file) : file);
1913
- }
1914
- return [...seen.values()];
1915
- }
1916
1942
  /**
1917
1943
  * Releases all stored files. Called by the core after `kubb:build:end`.
1918
1944
  */
@@ -1940,50 +1966,38 @@ var FileManager = class {
1940
1966
  * Parses and writes every file through a bounded pool of workers. A small spec runs all its files
1941
1967
  * at once; a spec with thousands of files keeps at most {@link FILE_CONCURRENCY} parsed sources in
1942
1968
  * memory rather than holding every source, while still overlapping each file's write with the
1943
- * next file's parse. Files finish in whatever order they parse, so the update rows are sorted back
1944
- * into input order before `kubb:files:processing:update` fires once with the whole batch.
1969
+ * next file's parse. Files finish in whatever order they parse, so each finished file flushes the
1970
+ * run of finished files ahead of it as one `kubb:files:processing:update`, keeping rows in input order.
1945
1971
  *
1946
1972
  * A file the storage already holds is skipped, so a rebuild that generates identical output
1947
- * writes nothing and leaves every mtime where it was.
1973
+ * writes nothing and leaves every mtime where it was. `kubb:files:processing:end` lists the
1974
+ * skipped paths as `unchanged`.
1948
1975
  */
1949
1976
  async write(files, { storage, parsers, manifest, signal, hooks, config }) {
1950
1977
  if (files.length === 0) return;
1951
1978
  await hooks?.callHook("kubb:files:processing:start", { files });
1952
- const total = files.length;
1953
- const updates = [];
1979
+ const unchanged = [];
1980
+ const progress = hooks && config ? createProgress(files, config, hooks) : void 0;
1954
1981
  await inParallel({
1955
1982
  items: files,
1956
1983
  limit: 50,
1957
1984
  run: async (file, index) => {
1958
1985
  signal?.throwIfAborted();
1959
1986
  const source = await this.parse(file, { parsers });
1960
- if (config) updates.push({
1987
+ if (source && !await writeChanged({
1988
+ storage,
1989
+ manifest,
1961
1990
  file,
1962
- processed: index + 1,
1963
- total,
1964
- percentage: (index + 1) / total * 100,
1965
- config
1966
- });
1967
- if (!source) return;
1968
- signal?.throwIfAborted();
1969
- const stored = await storage.readItem(file.path);
1970
- if (isUnchanged({
1971
- stored,
1972
1991
  source,
1973
- key: file.path,
1974
- manifest
1975
- })) return;
1976
- signal?.throwIfAborted();
1977
- await storage.writeItem(file.path, source, { stored });
1978
- manifest?.track({
1979
- key: file.path,
1980
- source
1981
- });
1992
+ signal
1993
+ })) unchanged.push(file.path);
1994
+ await progress?.(index);
1982
1995
  }
1983
1996
  });
1984
- updates.sort((a, b) => a.processed - b.processed);
1985
- await hooks?.callHook("kubb:files:processing:update", { files: updates });
1986
- await hooks?.callHook("kubb:files:processing:end", { files });
1997
+ await hooks?.callHook("kubb:files:processing:end", {
1998
+ files,
1999
+ unchanged
2000
+ });
1987
2001
  }
1988
2002
  };
1989
2003
  //#endregion
@@ -2311,10 +2325,15 @@ var KubbDriver = class {
2311
2325
  plugin.options = normalizePluginOptions(plugin.options, plugin.name);
2312
2326
  }
2313
2327
  if (this.adapter && this.inputNode) {
2328
+ const { meta, schemas, operations } = this.inputNode;
2314
2329
  const buildStartContext = this.#withFiles({
2315
2330
  config,
2316
2331
  adapter: this.adapter,
2317
- meta: this.inputNode.meta,
2332
+ meta,
2333
+ nodeCounts: {
2334
+ schemas: schemas.length,
2335
+ operations: operations.length
2336
+ },
2318
2337
  getPlugin: this.getPlugin.bind(this)
2319
2338
  });
2320
2339
  await hooks.callHook("kubb:build:start", buildStartContext);
@@ -2599,7 +2618,10 @@ var KubbDriver = class {
2599
2618
  }
2600
2619
  return diagnostics;
2601
2620
  }
2602
- /** Runs `node` through the generators whose `match` accepts it; `generate` picks the method for the node kind. */
2621
+ /**
2622
+ * Runs `node` through the generators whose `match` accepts it; `generate` picks the method for the
2623
+ * node kind.
2624
+ */
2603
2625
  async #dispatchNode({ generators, node, ctx, generate }) {
2604
2626
  for (const generator of generators) {
2605
2627
  if (!(generator.match ? await generator.match(node, ctx) : true)) continue;
@@ -2836,4 +2858,4 @@ var Hookable = class {
2836
2858
  //#endregion
2837
2859
  export { inParallel as A, __name as B, useOptions as C, Diagnostics as D, resolveMeta as E, rm as F, write as I, writeRaw as L, isPathInside as M, listFiles as N, formatMs as O, read as P, toError as R, useKubb as S, defineExtension as T, setMacros as _, FileManager as a, tryUseKubb as b, Resolver as c, definePlugin as d, addGenerator as f, requirePlugin as g, kubbContext as h, createNodeCache as i, existsSync$1 as j, getElapsedMs as k, getInputKind as l, injectFile as m, KubbDriver as n, _usingCtx as o, addMacro as p, pluginNotFoundError as r, createResolver as s, Hookable as t, assertPluginOption as u, setOptions as v, checkCompatibility as w, useConfig as x, setResolver as y, camelCase as z };
2838
2860
 
2839
- //# sourceMappingURL=Hookable-C2bwJUE5.js.map
2861
+ //# sourceMappingURL=Hookable-CvStaYYq.js.map