@kubb/cli 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.
Files changed (100) hide show
  1. package/dist/{Telemetry-Be1nltUh.cjs → Telemetry-Be3hIy6_.cjs} +2 -42
  2. package/dist/Telemetry-Be3hIy6_.cjs.map +1 -0
  3. package/dist/{Telemetry-CwaVQsvD.js → Telemetry-CfJAUDnh.js} +3 -31
  4. package/dist/Telemetry-CfJAUDnh.js.map +1 -0
  5. package/dist/{background-CQPyCSn0.js → background-BBpH2jmk.js} +7 -5
  6. package/dist/background-BBpH2jmk.js.map +1 -0
  7. package/dist/{background-mKdZ5HRx.cjs → background-BW-e9FCX.cjs} +5 -4
  8. package/dist/background-BW-e9FCX.cjs.map +1 -0
  9. package/dist/{background-aTbwrgui.js → background-DS2zOde-.js} +1 -1
  10. package/dist/config-Bh00Hwpw.js +132 -0
  11. package/dist/config-Bh00Hwpw.js.map +1 -0
  12. package/dist/config-D63Gum4F.cjs +169 -0
  13. package/dist/config-D63Gum4F.cjs.map +1 -0
  14. package/dist/config-iVdOcpro.js +1 -0
  15. package/dist/credentials-CZK6zPLv.js.map +1 -1
  16. package/dist/credentials-DDaKqtJn.cjs.map +1 -1
  17. package/dist/errors-Cl69p_vI.cjs +43 -0
  18. package/dist/errors-Cl69p_vI.cjs.map +1 -0
  19. package/dist/errors-DmC83ALX.js +32 -0
  20. package/dist/errors-DmC83ALX.js.map +1 -0
  21. package/dist/index.cjs +872 -307
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +863 -298
  24. package/dist/index.js.map +1 -1
  25. package/dist/registry-BlKLO4ft.cjs +272 -0
  26. package/dist/registry-BlKLO4ft.cjs.map +1 -0
  27. package/dist/registry-CgQYxUju.js +200 -0
  28. package/dist/registry-CgQYxUju.js.map +1 -0
  29. package/dist/reporters-Dl8lrJZG.js +817 -0
  30. package/dist/reporters-Dl8lrJZG.js.map +1 -0
  31. package/dist/reporters-TkCgd4dS.cjs +829 -0
  32. package/dist/reporters-TkCgd4dS.cjs.map +1 -0
  33. package/dist/{run-YHth5Afz.js → run-BJn0RSW2.js} +170 -76
  34. package/dist/run-BJn0RSW2.js.map +1 -0
  35. package/dist/{run-BWRmQxtE.cjs → run-BszyZwY3.cjs} +176 -82
  36. package/dist/run-BszyZwY3.cjs.map +1 -0
  37. package/dist/{run-Bczs5vbI.cjs → run-BtVKUjdK.cjs} +4 -3
  38. package/dist/{run-Bczs5vbI.cjs.map → run-BtVKUjdK.cjs.map} +1 -1
  39. package/dist/run-CFf0yxyY.cjs +94 -0
  40. package/dist/run-CFf0yxyY.cjs.map +1 -0
  41. package/dist/run-D_dD7xHp.js +93 -0
  42. package/dist/run-D_dD7xHp.js.map +1 -0
  43. package/dist/run-DsJJCzQD.js +1 -0
  44. package/dist/{run-B6VxJ_VM.js → run-KuzAqsjg.js} +3 -2
  45. package/dist/{run-B6VxJ_VM.js.map → run-KuzAqsjg.js.map} +1 -1
  46. package/dist/run-TrjCNL8e.cjs +227 -0
  47. package/dist/run-TrjCNL8e.cjs.map +1 -0
  48. package/dist/run-wkN1irUo.js +224 -0
  49. package/dist/run-wkN1irUo.js.map +1 -0
  50. package/dist/{snapshot-Cilp10vE.js → snapshot-CdGwHH15.js} +49 -62
  51. package/dist/snapshot-CdGwHH15.js.map +1 -0
  52. package/dist/{snapshot-D_rXnnMH.cjs → snapshot-u4L8mh4Y.cjs} +51 -64
  53. package/dist/snapshot-u4L8mh4Y.cjs.map +1 -0
  54. package/dist/status-DHPeR3bq.js +77 -0
  55. package/dist/status-DHPeR3bq.js.map +1 -0
  56. package/dist/status-dpSvxxtN.cjs +78 -0
  57. package/dist/status-dpSvxxtN.cjs.map +1 -0
  58. package/dist/studioWorker.js +2 -2
  59. package/dist/summary-6-rotl2L.cjs +46 -0
  60. package/dist/summary-6-rotl2L.cjs.map +1 -0
  61. package/dist/summary-DkSLti6f.js +41 -0
  62. package/dist/summary-DkSLti6f.js.map +1 -0
  63. package/dist/text-CMP4M19Z.js +239 -0
  64. package/dist/text-CMP4M19Z.js.map +1 -0
  65. package/dist/text-CknhKMvM.cjs +341 -0
  66. package/dist/text-CknhKMvM.cjs.map +1 -0
  67. package/dist/utils-CnjEJIdZ.js +612 -0
  68. package/dist/utils-CnjEJIdZ.js.map +1 -0
  69. package/dist/utils-DpKrXR2t.cjs +727 -0
  70. package/dist/utils-DpKrXR2t.cjs.map +1 -0
  71. package/package.json +7 -7
  72. package/dist/Telemetry-Be1nltUh.cjs.map +0 -1
  73. package/dist/Telemetry-CwaVQsvD.js.map +0 -1
  74. package/dist/background-CQPyCSn0.js.map +0 -1
  75. package/dist/background-mKdZ5HRx.cjs.map +0 -1
  76. package/dist/config-B56ZuVhi.js +0 -95
  77. package/dist/config-B56ZuVhi.js.map +0 -1
  78. package/dist/config-Cs4N79Fm.cjs +0 -113
  79. package/dist/config-Cs4N79Fm.cjs.map +0 -1
  80. package/dist/output-HgzJqkSF.cjs +0 -1076
  81. package/dist/output-HgzJqkSF.cjs.map +0 -1
  82. package/dist/output-PhkPtNP7.js +0 -937
  83. package/dist/output-PhkPtNP7.js.map +0 -1
  84. package/dist/registry-DYUhC7rH.js +0 -115
  85. package/dist/registry-DYUhC7rH.js.map +0 -1
  86. package/dist/registry-tk-v78fd.cjs +0 -157
  87. package/dist/registry-tk-v78fd.cjs.map +0 -1
  88. package/dist/run-AfjIBtiL.js +0 -39
  89. package/dist/run-AfjIBtiL.js.map +0 -1
  90. package/dist/run-BWRmQxtE.cjs.map +0 -1
  91. package/dist/run-B_Gr9TJ4.cjs +0 -345
  92. package/dist/run-B_Gr9TJ4.cjs.map +0 -1
  93. package/dist/run-CwrI0wRy.js +0 -1
  94. package/dist/run-DDoJGjWT.cjs +0 -40
  95. package/dist/run-DDoJGjWT.cjs.map +0 -1
  96. package/dist/run-SYKj8-Q5.js +0 -343
  97. package/dist/run-SYKj8-Q5.js.map +0 -1
  98. package/dist/run-YHth5Afz.js.map +0 -1
  99. package/dist/snapshot-Cilp10vE.js.map +0 -1
  100. package/dist/snapshot-D_rXnnMH.cjs.map +0 -1
@@ -0,0 +1,817 @@
1
+ import "./rolldown-runtime-CRm0XQPb.js";
2
+ import { a as getElapsedMs, i as formatMs } from "./Telemetry-CfJAUDnh.js";
3
+ import { _ as isPathInside, f as formatBar, o as readOutputFiles, p as formatMsWithColor, r as diffOutput, v as read } from "./utils-CnjEJIdZ.js";
4
+ import { n as toPermissionFlag, p as writer, t as pluralize } from "./text-CMP4M19Z.js";
5
+ import { styleText } from "node:util";
6
+ import process from "node:process";
7
+ import { readFile } from "node:fs/promises";
8
+ import { relative, resolve } from "node:path";
9
+ import { Diagnostics, cacheStorage, createCliReporter, createOutputManifest, getInputKind, logLevel } from "@kubb/core";
10
+ //#region src/loggers/features/context.ts
11
+ function createLoggerContext({ hooks, writer, logLevel: logLevel$5 }) {
12
+ const text = (message) => logLevel$5 >= logLevel.verbose ? `${styleText("dim", `[${clock()}]`)} ${message}` : message;
13
+ const ctx = {
14
+ hooks,
15
+ writer,
16
+ logLevel: logLevel$5,
17
+ silent: logLevel$5 <= logLevel.silent,
18
+ human: writer.audience === "human",
19
+ session: false,
20
+ groupOpen: false,
21
+ text,
22
+ line: (symbol, message, info) => text([
23
+ symbol,
24
+ message,
25
+ info && styleText("dim", info)
26
+ ].filter(Boolean).join(" ")),
27
+ onRun(name, fn) {
28
+ return hooks.hook(name, (...args) => ctx.session ? void 0 : fn(...args));
29
+ }
30
+ };
31
+ return ctx;
32
+ }
33
+ /** `HH:MM:SS` in 24-hour time. */
34
+ function clock(date = /* @__PURE__ */ new Date()) {
35
+ return date.toLocaleTimeString("en-US", {
36
+ hour12: false,
37
+ hour: "2-digit",
38
+ minute: "2-digit",
39
+ second: "2-digit"
40
+ });
41
+ }
42
+ /** Display path for a config's input: the string form, or its `path` field when the input is an object. */
43
+ function getInputPath(config) {
44
+ const { input } = config;
45
+ if (typeof input === "string") return input;
46
+ return typeof input?.path === "string" ? input.path : void 0;
47
+ }
48
+ //#endregion
49
+ //#region src/loggers/features/generation.ts
50
+ /**
51
+ * One config's run: the group it opens, the state every feature resets at its start, and the
52
+ * closing line, which the lifecycle end draws too for a run that threw before its summary.
53
+ */
54
+ function createGeneration(ctx, { spinner, progress, walk, phases, problems }) {
55
+ const { writer, silent, human, text, hooks, onRun } = ctx;
56
+ function reset() {
57
+ spinner.reset();
58
+ progress.reset();
59
+ walk.reset();
60
+ phases.reset();
61
+ problems.reset();
62
+ }
63
+ function closeGroup(status) {
64
+ if (!ctx.groupOpen) return;
65
+ ctx.groupOpen = false;
66
+ spinner.stop();
67
+ progress.stop();
68
+ writer.groupEnd(status === "failed" ? styleText("red", problems.failedTitle()) : styleText("green", "✓ Generation succeeded"));
69
+ }
70
+ hooks.hook("kubb:lifecycle:start", () => {
71
+ reset();
72
+ });
73
+ onRun("kubb:generation:start", ({ config }) => {
74
+ closeGroup("failed");
75
+ reset();
76
+ walk.state.totalPlugins = config.plugins?.length ?? 0;
77
+ progress.state.inMemory = config.storage?.name === "memory";
78
+ problems.setSpec(config);
79
+ if (silent) return;
80
+ const input = getInputPath(config);
81
+ writer.group(text([styleText("bold", config.name ?? "kubb"), input ? styleText("dim", input) : void 0].filter(Boolean).join(" ")));
82
+ ctx.groupOpen = true;
83
+ spinner.start("Parsing spec");
84
+ });
85
+ onRun("kubb:generation:end", async ({ diagnostics = [] }) => {
86
+ spinner.stop();
87
+ progress.stop();
88
+ problems.state.diagnostics = diagnostics;
89
+ await problems.flush();
90
+ });
91
+ hooks.hook("kubb:lifecycle:end", async () => {
92
+ await problems.flush();
93
+ closeGroup("failed");
94
+ reset();
95
+ });
96
+ const renderSummary = (lines, { status }) => {
97
+ if (silent || !ctx.groupOpen) return;
98
+ spinner.stop();
99
+ progress.stop();
100
+ if (human) closeGroup(status);
101
+ ctx.groupOpen = false;
102
+ writer.diagnostic([...lines]);
103
+ };
104
+ return { renderSummary };
105
+ }
106
+ //#endregion
107
+ //#region src/loggers/features/phases.ts
108
+ /**
109
+ * The three output phases, in the order the generate runner runs them.
110
+ */
111
+ const PHASES = {
112
+ format: {
113
+ running: "Formatting",
114
+ success: "Formatted",
115
+ failure: "Formatting failed"
116
+ },
117
+ lint: {
118
+ running: "Linting",
119
+ success: "Linted",
120
+ failure: "Linting failed"
121
+ },
122
+ hooks: {
123
+ running: "Running post-generate hooks",
124
+ success: "Post-generate hooks completed",
125
+ failure: "Post-generate hooks failed",
126
+ showHookResults: true
127
+ }
128
+ };
129
+ /** A hook's display name: its `name`, or the command line it runs. */
130
+ function formatHookLabel({ command, name, args }) {
131
+ return name ?? (args?.length ? `${command} ${args.join(" ")}` : command);
132
+ }
133
+ /**
134
+ * Drops the blank lines a subprocess leaves at either end of its output, so its block does not open
135
+ * or close on an empty row.
136
+ */
137
+ function trimBlankEdges(lines) {
138
+ const start = lines.findIndex((line) => line.trim());
139
+ if (start === -1) return [];
140
+ return lines.slice(start, lines.findLastIndex((line) => line.trim()) + 1);
141
+ }
142
+ /** The format, lint and post-generate phases, and the hooks and messages they buffer while their step owns the line. */
143
+ function createPhases({ writer, silent, logLevel: logLevel$4, text, line, onRun }, spinner) {
144
+ const state = {
145
+ phase: null,
146
+ hooks: /* @__PURE__ */ new Map()
147
+ };
148
+ function startPhase(options) {
149
+ state.phase = {
150
+ ...options,
151
+ failed: false,
152
+ hrStart: process.hrtime(),
153
+ lines: [],
154
+ output: []
155
+ };
156
+ spinner.start(options.running);
157
+ }
158
+ function endPhase() {
159
+ const phase = state.phase;
160
+ state.phase = null;
161
+ if (!phase || silent) return;
162
+ const duration = formatMsWithColor(getElapsedMs(phase.hrStart));
163
+ spinner.stop(phase.failed ? `${phase.failure} after ${duration}` : `${phase.success} in ${duration}`, phase.failed ? "failed" : "success");
164
+ if (phase.lines.length) writer.block(phase.lines);
165
+ writeOutput(phase.output);
166
+ }
167
+ function writeOutput(lines) {
168
+ const output = trimBlankEdges(lines);
169
+ if (output.length) writer.raw(output);
170
+ }
171
+ onRun("kubb:format:start", () => startPhase(PHASES.format));
172
+ onRun("kubb:format:end", endPhase);
173
+ onRun("kubb:lint:start", () => startPhase(PHASES.lint));
174
+ onRun("kubb:lint:end", endPhase);
175
+ onRun("kubb:hooks:start", () => startPhase(PHASES.hooks));
176
+ onRun("kubb:hooks:end", endPhase);
177
+ onRun("kubb:hook:end", (ctx) => {
178
+ const { id, command, name, args, success, error } = ctx;
179
+ if (!id) return;
180
+ if (!success && state.phase) state.phase.failed = true;
181
+ if (silent) {
182
+ if (!ctx.success) {
183
+ if (ctx.stdout) console.log(ctx.stdout);
184
+ if (ctx.stderr) console.error(ctx.stderr);
185
+ }
186
+ return;
187
+ }
188
+ const active = state.hooks.get(id);
189
+ if (!active) return;
190
+ state.hooks.delete(id);
191
+ const label = styleText("dim", formatHookLabel({
192
+ command,
193
+ name,
194
+ args
195
+ }));
196
+ const reason = error?.message ? ` (${error.message})` : "";
197
+ const result = success ? `${styleText("green", "✓")} ${label} in ${formatMsWithColor(getElapsedMs(active.hrStart))}` : `${styleText("red", "✗")} ${label} failed${reason}`;
198
+ const phase = state.phase;
199
+ if (phase) {
200
+ if (phase.showHookResults || !success) phase.lines.push(result);
201
+ phase.output.push(...active.lines);
202
+ return;
203
+ }
204
+ spinner.stop();
205
+ writer.step(text(result));
206
+ writeOutput(active.lines);
207
+ });
208
+ if (!silent) {
209
+ onRun("kubb:info", ({ message, info }) => {
210
+ const formatted = line(styleText("blue", "ℹ"), message, info);
211
+ if (state.phase) {
212
+ state.phase.lines.push(formatted);
213
+ return;
214
+ }
215
+ if (spinner.message(formatted)) return;
216
+ writer.info(formatted);
217
+ });
218
+ onRun("kubb:success", ({ message, info }) => {
219
+ const formatted = line(styleText("green", "✓"), message, logLevel$4 >= logLevel.info ? info : void 0);
220
+ if (spinner.message(formatted)) return;
221
+ writer.success(formatted);
222
+ });
223
+ onRun("kubb:hook:start", ({ id, command, name, args }) => {
224
+ if (!id) return;
225
+ state.hooks.set(id, {
226
+ hrStart: process.hrtime(),
227
+ lines: []
228
+ });
229
+ spinner.message(text(`Running ${styleText("dim", formatHookLabel({
230
+ command,
231
+ name,
232
+ args
233
+ }))}`));
234
+ });
235
+ onRun("kubb:hook:line", ({ id, line }) => {
236
+ const active = state.hooks.get(id);
237
+ if (!active) return;
238
+ if (state.phase) {
239
+ active.lines.push(line);
240
+ return;
241
+ }
242
+ writer.raw([line]);
243
+ });
244
+ }
245
+ return {
246
+ /** The phase whose step owns the line, so a failure or an info line can wait for it. */
247
+ get phase() {
248
+ return state.phase;
249
+ },
250
+ reset() {
251
+ state.phase = null;
252
+ state.hooks = /* @__PURE__ */ new Map();
253
+ }
254
+ };
255
+ }
256
+ //#endregion
257
+ //#region src/loggers/specFrame.ts
258
+ /**
259
+ * Where `location` sits in `source` according to its adapter, with a code frame. `undefined` when the adapter cannot place it.
260
+ */
261
+ function locateInSpec({ adapter, source, location }) {
262
+ const lines = source.split(/\r?\n/);
263
+ try {
264
+ const range = adapter.locate?.(source, location);
265
+ if (!range) return void 0;
266
+ const before = source.slice(0, range.start).split("\n");
267
+ const line = before.length;
268
+ const column = before.at(-1).length + 1;
269
+ const text = lines[line - 1] ?? "";
270
+ const width = String(line).length;
271
+ const gutter = (number) => styleText("dim", `${number === void 0 ? "".padStart(width) : String(number).padStart(width)} │`);
272
+ const carets = "^".repeat(Math.max(1, Math.min(range.end - range.start, text.length - column + 1)));
273
+ return {
274
+ line,
275
+ column,
276
+ frame: [
277
+ ...line > 1 ? [`${gutter(line - 1)} ${lines[line - 2]}`] : [],
278
+ `${gutter(line)} ${text}`,
279
+ `${gutter()} ${" ".repeat(column - 1)}${styleText("red", carets)}`
280
+ ]
281
+ };
282
+ } catch {
283
+ return;
284
+ }
285
+ }
286
+ //#endregion
287
+ //#region src/loggers/features/problems.ts
288
+ /** Warnings, errors and diagnostics: printed as they come, or held back and grouped by plugin once a human's build ends. */
289
+ function createProblems(ctx, spinner, progress, phases) {
290
+ const { writer, silent, human, logLevel: logLevel$3, text, line, hooks } = ctx;
291
+ const { audience } = writer;
292
+ const formatOptions = {
293
+ audience,
294
+ verbose: logLevel$3 >= logLevel.verbose,
295
+ width: Math.min(process.stdout.columns || 80, 100) - (audience === "human" ? 4 : 2)
296
+ };
297
+ const state = {
298
+ /** Problems held back so the human view can print them grouped by plugin once the build ends. */
299
+ problems: [],
300
+ /** The finished config's diagnostics, for the closing verdict. */
301
+ diagnostics: [],
302
+ /** A local-file input, which a located diagnostic points into; `load` parses it once, on the first such diagnostic. */
303
+ spec: void 0
304
+ };
305
+ /** Format options for one problem, with its line and column in a local spec when its pointer resolves there. */
306
+ async function optionsFor(problem) {
307
+ const { location } = problem;
308
+ const { spec } = state;
309
+ if (!location || !spec) return formatOptions;
310
+ const source = await spec.source;
311
+ const position = source && locateInSpec({
312
+ adapter: spec.adapter,
313
+ source,
314
+ location
315
+ });
316
+ if (!position) return formatOptions;
317
+ return {
318
+ ...formatOptions,
319
+ position: {
320
+ file: relative(process.cwd(), spec.path),
321
+ ...position
322
+ }
323
+ };
324
+ }
325
+ /**
326
+ * Prints problems grouped under their plugin's name, each with the code aligned at the right.
327
+ */
328
+ async function writeProblems(problems) {
329
+ if (!problems.length) return;
330
+ const formatted = await Promise.all(problems.map(async (problem) => Diagnostics.format(problem, await optionsFor(problem))));
331
+ if (!human) {
332
+ for (const lines of formatted) writer.diagnostic(lines);
333
+ return;
334
+ }
335
+ const lines = [...Map.groupBy(problems.map((problem, index) => ({
336
+ plugin: problem.plugin ?? "",
337
+ lines: formatted[index]
338
+ })), ({ plugin }) => plugin)].flatMap(([plugin, group]) => [...plugin ? [` ${styleText("bold", plugin)}`, ""] : [], ...group.flatMap((entry) => [...entry.lines.map((entryLine) => ` ${entryLine}`), ""])]);
339
+ writer.diagnostic(lines.slice(0, -1));
340
+ }
341
+ hooks.hook("kubb:warn", ({ message, info }) => {
342
+ if (logLevel$3 < logLevel.warn) return;
343
+ writer.warn(line(styleText("yellow", "⚠"), message, logLevel$3 >= logLevel.info ? info : void 0));
344
+ });
345
+ hooks.hook("kubb:error", ({ error }) => {
346
+ const phase = phases.phase;
347
+ if (phase) phase.failed = true;
348
+ const message = error.name === "Error" || Diagnostics.isError(error) ? error.message : `${error.name}: ${error.message}`;
349
+ const help = Diagnostics.isError(error) ? error.diagnostic.help : void 0;
350
+ const [headline = "", ...details] = Diagnostics.format({
351
+ code: Diagnostics.code.unknown,
352
+ severity: "error",
353
+ message,
354
+ help,
355
+ cause: error
356
+ }, formatOptions);
357
+ if (!human) {
358
+ spinner.stop();
359
+ progress.stop();
360
+ writer.diagnostic([headline, ...details]);
361
+ return;
362
+ }
363
+ if (spinner.active) spinner.stop(message, "failed");
364
+ else writer.error(text(message));
365
+ if (details.length) writer.block(details.map(text));
366
+ });
367
+ hooks.hook("kubb:diagnostic", async ({ diagnostic }) => {
368
+ if (silent && diagnostic.severity !== "error") return;
369
+ if (ctx.session && !Diagnostics.isUpdate(diagnostic)) return;
370
+ spinner.stop();
371
+ progress.stop();
372
+ if (Diagnostics.isUpdate(diagnostic)) {
373
+ writer.update([`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\``, "Run `npm install -g @kubb/cli` to update"], "Update available for `Kubb`");
374
+ return;
375
+ }
376
+ if (human && ctx.groupOpen) {
377
+ state.problems.push(diagnostic);
378
+ return;
379
+ }
380
+ await writeProblems([diagnostic]);
381
+ });
382
+ return {
383
+ state,
384
+ /** Points located diagnostics into the config's input when it is a local file. */
385
+ setSpec(config) {
386
+ const input = getInputPath(config);
387
+ if (input && config.adapter?.locate && getInputKind(input) === "file") {
388
+ const path = resolve(config.root, input);
389
+ let source;
390
+ state.spec = {
391
+ path,
392
+ adapter: config.adapter,
393
+ get source() {
394
+ return source ??= read(path).catch(() => void 0);
395
+ }
396
+ };
397
+ }
398
+ },
399
+ /** Prints the problems held back so far. */
400
+ flush() {
401
+ return writeProblems(state.problems.splice(0));
402
+ },
403
+ /** `✖ Failed 1 error · 1 warning in plugin-ts` once the build ended, or `✗ Generation failed` for a run that stopped early. */
404
+ failedTitle() {
405
+ const { errors, warnings } = Diagnostics.count(state.diagnostics);
406
+ const counts = [errors && pluralize(errors, "error"), warnings && pluralize(warnings, "warning")].filter(Boolean).join(" · ");
407
+ if (!human || !counts) return "✗ Generation failed";
408
+ const plugins = Diagnostics.failedPlugins(state.diagnostics);
409
+ return `✖ Failed ${counts}${plugins.length ? ` in ${plugins.join(", ")}` : ""}`;
410
+ },
411
+ reset() {
412
+ Object.assign(state, {
413
+ problems: [],
414
+ diagnostics: [],
415
+ spec: void 0
416
+ });
417
+ }
418
+ };
419
+ }
420
+ //#endregion
421
+ //#region src/loggers/features/progress.ts
422
+ /** The progress bar over the file writes, and the counts the summary line shows. */
423
+ function createProgress({ writer, silent, text, onRun }, spinner) {
424
+ const state = {
425
+ progress: null,
426
+ totalFiles: 0,
427
+ processedFiles: 0,
428
+ /** Whether this run keeps its files in memory, as a `kubb studio` run without `--allow-write` does. */
429
+ inMemory: false
430
+ };
431
+ function stop(message) {
432
+ const progress = state.progress;
433
+ if (!progress) return;
434
+ state.progress = null;
435
+ progress.stop(message === void 0 ? "" : text(message));
436
+ }
437
+ if (!silent) {
438
+ onRun("kubb:files:processing:start", ({ files }) => {
439
+ spinner.stop();
440
+ state.totalFiles = files.length;
441
+ state.processedFiles = 0;
442
+ state.progress = writer.progress(files.length);
443
+ state.progress.start(text(`${state.inMemory ? "Generating" : "Writing"} ${pluralize(files.length, "file")}`));
444
+ });
445
+ onRun("kubb:files:processing:update", ({ files }) => {
446
+ for (const { file, config } of files) {
447
+ state.processedFiles++;
448
+ state.progress?.advance(`${state.inMemory ? "Generating" : "Writing"} ${relative(config.root, file.path)}`);
449
+ }
450
+ });
451
+ onRun("kubb:files:processing:end", ({ files, unchanged }) => {
452
+ const skipped = unchanged.length ? styleText("dim", ` · ${unchanged.length} unchanged`) : "";
453
+ stop(state.inMemory ? `Generated ${pluralize(state.processedFiles, "file")} in memory` : `Wrote ${pluralize(files.length - unchanged.length, "file")}${skipped}`);
454
+ });
455
+ }
456
+ return {
457
+ state,
458
+ stop,
459
+ reset() {
460
+ stop();
461
+ Object.assign(state, {
462
+ totalFiles: 0,
463
+ processedFiles: 0,
464
+ inMemory: false
465
+ });
466
+ }
467
+ };
468
+ }
469
+ //#endregion
470
+ //#region src/loggers/features/session.ts
471
+ /** One line of a Studio session's job log: the time, the command, who asked for it, and how it ended. */
472
+ function formatJobLine({ command, result, requestedBy }) {
473
+ const by = requestedBy ? `${styleText("dim", `by ${requestedBy}`.padEnd(16))} ` : "";
474
+ return `${styleText("dim", clock())} ${command.padEnd(10)} ${by}${result}`;
475
+ }
476
+ /**
477
+ * A `kubb studio` session. It emits these on the same emitter as its generations, so one logger
478
+ * renders the whole command: the connection, then one job line per command.
479
+ */
480
+ function createSession(ctx, spinner) {
481
+ const { writer, silent, human, logLevel: logLevel$2, text, hooks } = ctx;
482
+ hooks.hook("studio:disconnected", ({ code, reason }) => {
483
+ if (logLevel$2 < logLevel.warn) return;
484
+ spinner.stop();
485
+ if (code === 4002) {
486
+ writer.warn(text("⚠ Another process connected to Kubb Studio as this agent, so this one stopped"));
487
+ return;
488
+ }
489
+ writer.warn(text(`⚠ Kubb Studio ended the session (${reason || `code ${code}`})`));
490
+ });
491
+ hooks.hook("studio:warn", ({ message, permission }) => {
492
+ if (logLevel$2 < logLevel.warn) return;
493
+ const remedy = permission ? `; pass ${toPermissionFlag(permission)} to allow it` : "";
494
+ const paused = spinner.active;
495
+ spinner.stop();
496
+ writer.warn(text(`⚠ ${message}${remedy}`));
497
+ if (paused) spinner.start("Ready for jobs");
498
+ });
499
+ hooks.hook("studio:error", ({ error }) => {
500
+ if (spinner.active) {
501
+ spinner.stop(error.message, "failed");
502
+ return;
503
+ }
504
+ writer.error(text(error.message));
505
+ });
506
+ if (silent) return;
507
+ let job = null;
508
+ hooks.hook("studio:connected", ({ url, versions: { studio, kubb, agent } }) => {
509
+ ctx.session = human;
510
+ spinner.stop();
511
+ const versions = [
512
+ `v${agent}`,
513
+ kubb !== agent ? `runtime v${kubb}` : void 0,
514
+ `Studio v${studio}`
515
+ ].filter(Boolean).join(" · ");
516
+ writer.success(text(`✓ Connected to ${styleText("cyan", url)} ${styleText("dim", `· ${versions}`)}`));
517
+ spinner.start("Ready for jobs");
518
+ });
519
+ hooks.hook("kubb:generation:start", () => {
520
+ if (ctx.session) spinner.message(text("Generating"));
521
+ });
522
+ hooks.hook("kubb:generation:end", ({ diagnostics = [], filesCreated = 0, status, hrStart = process.hrtime() }) => {
523
+ job = {
524
+ fileCount: filesCreated,
525
+ ms: getElapsedMs(hrStart),
526
+ errors: status === "failed" ? Diagnostics.count(diagnostics).errors : 0
527
+ };
528
+ });
529
+ hooks.hook("studio:command", ({ command, info, requestedBy, failed }) => {
530
+ spinner.stop();
531
+ const took = command === "generate" && job ? `${pluralize(job.fileCount, "file")} in ${formatMs(job.ms)} ` : "";
532
+ const result = failed ? `${styleText("red", "✖")} ${job?.errors ? pluralize(job.errors, "error") : "failed"}, see Studio` : `${styleText("green", "✓")} ${took}${styleText("dim", `(${info})`)}`;
533
+ writer.info(formatJobLine({
534
+ command,
535
+ requestedBy,
536
+ result
537
+ }));
538
+ job = null;
539
+ spinner.start("Ready for jobs");
540
+ });
541
+ hooks.hook("studio:reconnecting", ({ delayMs }) => {
542
+ spinner.stop();
543
+ writer.info(text(styleText("dim", `Retrying connection to Kubb Studio in ${formatMs(delayMs)}`)));
544
+ });
545
+ }
546
+ //#endregion
547
+ //#region src/loggers/features/spinner.ts
548
+ /** The step whose end is not known when it starts: the spec parse, the node walk, a phase, or a Studio session waiting for jobs. */
549
+ function createSpinner({ writer, silent, text }) {
550
+ let spinner = null;
551
+ function stop(message, status = "success") {
552
+ const active = spinner;
553
+ if (!active) return;
554
+ spinner = null;
555
+ if (message === void 0) {
556
+ active.clear();
557
+ return;
558
+ }
559
+ if (status === "failed") {
560
+ active.error(text(message));
561
+ return;
562
+ }
563
+ active.stop(text(message));
564
+ }
565
+ return {
566
+ /** Whether a spinner is drawing right now. */
567
+ get active() {
568
+ return spinner !== null;
569
+ },
570
+ start(message) {
571
+ if (silent) return;
572
+ spinner = writer.spinner();
573
+ spinner.start(text(message));
574
+ },
575
+ stop,
576
+ /** Redraws the spinner's line. `false` when no spinner is running, so the caller prints the line itself. */
577
+ message(line) {
578
+ if (!spinner) return false;
579
+ spinner.message(line);
580
+ return true;
581
+ },
582
+ reset: () => stop()
583
+ };
584
+ }
585
+ //#endregion
586
+ //#region src/loggers/features/walk.ts
587
+ /** Milliseconds between redraws of the live generation block, about clack's spinner frame rate. */
588
+ const WALK_REDRAW_MS = 80;
589
+ function createState() {
590
+ return {
591
+ totalPlugins: 0,
592
+ completedPlugins: 0,
593
+ failedPlugins: 0,
594
+ /** `process.hrtime()` snapshot taken at the start of generation, for the elapsed time. */
595
+ hrStart: process.hrtime(),
596
+ /** Plugin results held back while the generation step owns the line. */
597
+ pluginLines: [],
598
+ /** The node walk's live state: the spec's node total, the distinct nodes generated, and each plugin's count and current node. */
599
+ walk: {
600
+ total: 0,
601
+ visited: /* @__PURE__ */ new Set(),
602
+ plugins: /* @__PURE__ */ new Map()
603
+ },
604
+ lastWalkDraw: 0
605
+ };
606
+ }
607
+ /** The node walk the plugins share: the live generation block, and the plugin results printed once the walk ends. */
608
+ function createWalk({ writer, silent, human, text, onRun }, spinner, progress) {
609
+ const state = createState();
610
+ /** The progress summary line the clack logger shows, `null` when there is nothing to display. */
611
+ function buildProgressLine() {
612
+ const parts = [];
613
+ const duration = formatMs(getElapsedMs(state.hrStart));
614
+ const { totalFiles, processedFiles } = progress.state;
615
+ if (state.totalPlugins > 0) {
616
+ const failed = state.failedPlugins > 0 ? ` ${styleText("red", `(${state.failedPlugins} failed)`)}` : "";
617
+ parts.push(`Plugins ${styleText("green", state.completedPlugins.toString())}/${state.totalPlugins}${failed}`);
618
+ }
619
+ if (totalFiles > 0) parts.push(`Files ${styleText("green", processedFiles.toString())}/${totalFiles}`);
620
+ if (parts.length === 0) return null;
621
+ parts.push(`${styleText("green", duration)} elapsed`);
622
+ return parts.join(styleText("dim", " | "));
623
+ }
624
+ if (!silent) {
625
+ onRun("kubb:build:start", ({ nodeCounts: { schemas, operations } }) => {
626
+ state.walk.total = schemas + operations;
627
+ const details = `${pluralize(schemas, "schema")} · ${pluralize(operations, "operation")} · ${formatMs(getElapsedMs(state.hrStart))}`;
628
+ spinner.stop(`Parsed spec ${styleText("dim", details)}`);
629
+ spinner.start("Generating");
630
+ });
631
+ if (human) {
632
+ const onNode = (key, name, plugin) => {
633
+ const { total, visited, plugins } = state.walk;
634
+ visited.add(key);
635
+ plugins.set(plugin, {
636
+ count: (plugins.get(plugin)?.count ?? 0) + 1,
637
+ current: name
638
+ });
639
+ const now = Date.now();
640
+ if (now - state.lastWalkDraw < WALK_REDRAW_MS) return;
641
+ state.lastWalkDraw = now;
642
+ const elapsed = styleText("dim", formatMs(getElapsedMs(state.hrStart)));
643
+ const head = total ? `Generating ${formatBar(visited.size, total)} ${visited.size}/${total} nodes ${elapsed}` : `Generating ${pluralize(visited.size, "node")} ${elapsed}`;
644
+ const width = Math.max(...[...plugins.keys()].map((pluginName) => pluginName.length));
645
+ const rows = [...plugins].map(([pluginName, row]) => `${styleText("dim", "│")} ${pluginName.padEnd(width)} ${styleText("dim", String(row.count).padStart(4))} ${styleText("cyan", row.current)}`);
646
+ spinner.message(text([head, ...rows].join("\n")));
647
+ };
648
+ onRun("kubb:generate:schema", (node, { plugin }) => onNode(`schema:${node.name}`, node.name ?? "schema", plugin.name));
649
+ onRun("kubb:generate:operation", (node, { plugin }) => onNode(`operation:${node.operationId}`, node.operationId, plugin.name));
650
+ }
651
+ onRun("kubb:plugin:end", ({ plugin, duration, success }) => {
652
+ state[success ? "completedPlugins" : "failedPlugins"]++;
653
+ state.pluginLines.push(text(`${plugin.name} ${success ? "completed" : "failed"} in ${formatMsWithColor(duration)}`));
654
+ });
655
+ onRun("kubb:plugins:end", () => {
656
+ const failed = state.failedPlugins > 0;
657
+ spinner.stop(buildProgressLine() ?? void 0, failed ? "failed" : "success");
658
+ if (state.pluginLines.length) {
659
+ writer.block(state.pluginLines);
660
+ state.pluginLines = [];
661
+ }
662
+ });
663
+ }
664
+ return {
665
+ state,
666
+ reset() {
667
+ Object.assign(state, createState());
668
+ }
669
+ };
670
+ }
671
+ //#endregion
672
+ //#region src/loggers/features/index.ts
673
+ /**
674
+ * Wires a {@link LoggerWriter} onto the run's hooks. Each feature registers the hooks for one
675
+ * concern, so every writer reports the same run in the same order: one group per config, a step
676
+ * over the plugin work, a progress bar for the writes, one step per output phase, then the summary.
677
+ * It hands back `renderSummary`, which writes the `cli` reporter's summary inside the group opened
678
+ * for that config and closes the group.
679
+ */
680
+ function installLogger({ hooks, writer, logLevel }) {
681
+ const ctx = createLoggerContext({
682
+ hooks,
683
+ writer,
684
+ logLevel
685
+ });
686
+ const spinner = createSpinner(ctx);
687
+ const progress = createProgress(ctx, spinner);
688
+ const walk = createWalk(ctx, spinner, progress);
689
+ const phases = createPhases(ctx, spinner);
690
+ const problems = createProblems(ctx, spinner, progress, phases);
691
+ createSession(ctx, spinner);
692
+ return createGeneration(ctx, {
693
+ spinner,
694
+ progress,
695
+ walk,
696
+ phases,
697
+ problems
698
+ });
699
+ }
700
+ //#endregion
701
+ //#region src/loggers/reporters.ts
702
+ /** Installs the live logger and wires the selected reporters. `cli` renders its summary through the logger, and is skipped when `json` owns stdout. */
703
+ function setupReporters(context, { logLevel: logLevel$1, reporters, writer: writer$1 = writer(), dryRun = false }) {
704
+ const hasJson = reporters.some((reporter) => reporter.name === "json");
705
+ const selected = reporters.flatMap((reporter) => {
706
+ if (reporter.name !== "cli") return [reporter];
707
+ if (hasJson) return [];
708
+ return [createCliReporter({ render: installLogger({
709
+ hooks: context,
710
+ writer: writer$1,
711
+ logLevel: logLevel$1
712
+ }).renderSummary })];
713
+ });
714
+ const ctx = { logLevel: logLevel$1 };
715
+ const pluginFiles = selected.some((reporter) => reporter.needsPluginFiles) ? /* @__PURE__ */ new Map() : void 0;
716
+ const seen = /* @__PURE__ */ new Set();
717
+ if (pluginFiles) context.hook("kubb:plugin:end", ({ plugin, files }) => {
718
+ for (const file of files) {
719
+ if (seen.has(file.path)) continue;
720
+ seen.add(file.path);
721
+ const paths = pluginFiles.get(plugin.name) ?? [];
722
+ paths.push(file.path);
723
+ pluginFiles.set(plugin.name, paths);
724
+ }
725
+ });
726
+ let filesUnchanged = 0;
727
+ let previous;
728
+ let paths = [];
729
+ let outputPath = "";
730
+ const trackChanges = selected.some((reporter) => reporter.name === "cli") && logLevel$1 > logLevel.silent;
731
+ context.hook("kubb:generation:start", async ({ config }) => {
732
+ pluginFiles?.clear();
733
+ seen.clear();
734
+ filesUnchanged = 0;
735
+ paths = [];
736
+ outputPath = resolve(config.root, config.output.path);
737
+ previous = trackChanges && (dryRun || config.storage?.name === "fs") ? await readOutputFiles(outputPath) : void 0;
738
+ });
739
+ context.hook("kubb:files:processing:start", async ({ files }) => {
740
+ if (!previous) return;
741
+ for (const file of files) {
742
+ if (previous.has(file.path) || isPathInside(outputPath, file.path)) continue;
743
+ const source = await readFile(file.path, "utf8").catch(() => null);
744
+ if (source !== null) previous.set(file.path, source);
745
+ }
746
+ });
747
+ context.hook("kubb:files:processing:end", ({ files, unchanged }) => {
748
+ filesUnchanged = unchanged.length;
749
+ paths = files.map((file) => file.path);
750
+ });
751
+ context.hook("kubb:generation:end", async ({ config, storage, diagnostics = [], filesCreated = 0, status = "success", hrStart = process.hrtime() }) => {
752
+ const result = {
753
+ config,
754
+ diagnostics,
755
+ filesCreated,
756
+ filesUnchanged,
757
+ dryRun,
758
+ status,
759
+ hrStart
760
+ };
761
+ const cliResult = { ...result };
762
+ if (previous && status !== "failed") {
763
+ const manifest = dryRun ? await createOutputManifest({
764
+ storage,
765
+ cache: cacheStorage({ root: config.root })
766
+ }) : void 0;
767
+ const diff = await diffOutput({
768
+ storage,
769
+ paths,
770
+ clean: Boolean(config.output.clean),
771
+ manifest,
772
+ previousFiles: previous
773
+ });
774
+ const changes = {
775
+ added: 0,
776
+ modified: 0,
777
+ deleted: 0,
778
+ linesAdded: 0,
779
+ linesRemoved: 0
780
+ };
781
+ const counts = {
782
+ A: "added",
783
+ M: "modified",
784
+ D: "deleted"
785
+ };
786
+ for (const file of diff.files) {
787
+ changes[counts[file.status]]++;
788
+ changes.linesAdded += file.added ?? 0;
789
+ changes.linesRemoved += file.removed ?? 0;
790
+ }
791
+ cliResult.filesCreated = changes.added + changes.modified + diff.unchanged;
792
+ cliResult.filesUnchanged = diff.unchanged;
793
+ cliResult.fileChanges = changes;
794
+ }
795
+ for (const reporter of selected) {
796
+ const grouped = reporter.needsPluginFiles && pluginFiles?.size ? [...pluginFiles].map(([plugin, files]) => ({
797
+ plugin,
798
+ files: [...files]
799
+ })) : void 0;
800
+ await reporter.report({
801
+ ...reporter.name === "cli" ? cliResult : result,
802
+ pluginFiles: grouped
803
+ }, ctx);
804
+ }
805
+ });
806
+ context.hook("kubb:lifecycle:end", async () => {
807
+ for (const reporter of selected) await reporter.drain?.(ctx);
808
+ });
809
+ }
810
+ /** Picks the reporters whose `name` is in `names`, in the given order (the CLI maps `--reporter` to this); duplicates and unknown names are skipped. */
811
+ function selectReporters(reporters, names) {
812
+ return [...new Set(names)].flatMap((name) => reporters.find((reporter) => reporter.name === name) ?? []);
813
+ }
814
+ //#endregion
815
+ export { setupReporters as n, selectReporters as t };
816
+
817
+ //# sourceMappingURL=reporters-Dl8lrJZG.js.map