@kubb/cli 5.5.6 → 5.6.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.
Files changed (146) hide show
  1. package/dist/{Telemetry-CMXOVUKB.js → Telemetry-RBcAjNxj.js} +18 -43
  2. package/dist/Telemetry-RBcAjNxj.js.map +1 -0
  3. package/dist/{Telemetry-DDHXBdEz.cjs → Telemetry-bGdFBaGt.cjs} +27 -89
  4. package/dist/Telemetry-bGdFBaGt.cjs.map +1 -0
  5. package/dist/agent-BPIqnEl2.js +59 -0
  6. package/dist/agent-BPIqnEl2.js.map +1 -0
  7. package/dist/agent-DElUuYuy.cjs +120 -0
  8. package/dist/agent-DElUuYuy.cjs.map +1 -0
  9. package/dist/{background-DxlRE9O5.js → background-0PMj56FJ.js} +6 -5
  10. package/dist/background-0PMj56FJ.js.map +1 -0
  11. package/dist/background-BqS01QDb.js +2 -0
  12. package/dist/{background-Bbcc2TdN.cjs → background-R0T_7Szu.cjs} +8 -7
  13. package/dist/background-R0T_7Szu.cjs.map +1 -0
  14. package/dist/commands-BAgGrcvc.js +65 -0
  15. package/dist/commands-BAgGrcvc.js.map +1 -0
  16. package/dist/commands-Si-RFAS6.cjs +71 -0
  17. package/dist/commands-Si-RFAS6.cjs.map +1 -0
  18. package/dist/config-8Xl4yZmw.js +146 -0
  19. package/dist/config-8Xl4yZmw.js.map +1 -0
  20. package/dist/config-ByfJSj3q.cjs +164 -0
  21. package/dist/config-ByfJSj3q.cjs.map +1 -0
  22. package/dist/constants-CdxpgX3_.cjs.map +1 -1
  23. package/dist/constants-DHRspBTo.js.map +1 -1
  24. package/dist/{errors-C-wEou02.cjs → errors-CMbMAwyW.cjs} +2 -2
  25. package/dist/{errors-C-wEou02.cjs.map → errors-CMbMAwyW.cjs.map} +1 -1
  26. package/dist/index.cjs +431 -22
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.js +429 -20
  29. package/dist/index.js.map +1 -1
  30. package/dist/output-B6Ondw0g.js +776 -0
  31. package/dist/output-B6Ondw0g.js.map +1 -0
  32. package/dist/output-i3s5c4Xu.cjs +878 -0
  33. package/dist/output-i3s5c4Xu.cjs.map +1 -0
  34. package/dist/run-3I2NPeXG.cjs +31 -0
  35. package/dist/run-3I2NPeXG.cjs.map +1 -0
  36. package/dist/{run-C-jBKNbI.cjs → run-BkLJ5mXn.cjs} +22 -43
  37. package/dist/run-BkLJ5mXn.cjs.map +1 -0
  38. package/dist/run-CZlkqkDc.js +30 -0
  39. package/dist/run-CZlkqkDc.js.map +1 -0
  40. package/dist/run-CmNDNEjR.js +346 -0
  41. package/dist/run-CmNDNEjR.js.map +1 -0
  42. package/dist/{run-BdTwxLCW.js → run-D5AeacqK.js} +9 -23
  43. package/dist/run-D5AeacqK.js.map +1 -0
  44. package/dist/{run-y6LRyWdS.cjs → run-D8Wz_17X.cjs} +41 -60
  45. package/dist/run-D8Wz_17X.cjs.map +1 -0
  46. package/dist/{run-Dn8HVjnM.js → run-DQoKTiGk.js} +23 -44
  47. package/dist/run-DQoKTiGk.js.map +1 -0
  48. package/dist/{run-BDaZPU-1.js → run-Dgph0WYk.js} +25 -49
  49. package/dist/run-Dgph0WYk.js.map +1 -0
  50. package/dist/run-t8nm7zcT.cjs +348 -0
  51. package/dist/run-t8nm7zcT.cjs.map +1 -0
  52. package/dist/{run-CZz_GbE4.cjs → run-yvuot2N9.cjs} +12 -26
  53. package/dist/run-yvuot2N9.cjs.map +1 -0
  54. package/dist/{snapshot-B5mc4gpC.js → snapshot-CkHXR0Nl.js} +5 -5
  55. package/dist/snapshot-CkHXR0Nl.js.map +1 -0
  56. package/dist/{snapshot-DnCxZfmM.cjs → snapshot-D4nKbwkw.cjs} +9 -9
  57. package/dist/snapshot-D4nKbwkw.cjs.map +1 -0
  58. package/dist/studioWorker.js +2 -2
  59. package/package.json +7 -7
  60. package/dist/Telemetry-CMXOVUKB.js.map +0 -1
  61. package/dist/Telemetry-DDHXBdEz.cjs.map +0 -1
  62. package/dist/agent-DiB4CRre.cjs +0 -38
  63. package/dist/agent-DiB4CRre.cjs.map +0 -1
  64. package/dist/agent-yA5vrXJB.js +0 -26
  65. package/dist/agent-yA5vrXJB.js.map +0 -1
  66. package/dist/background-Bbcc2TdN.cjs.map +0 -1
  67. package/dist/background-CFNncNYT.js +0 -2
  68. package/dist/background-DxlRE9O5.js.map +0 -1
  69. package/dist/connect-C9tTngda.js +0 -11
  70. package/dist/connect-C9tTngda.js.map +0 -1
  71. package/dist/connect-CJ7BhOcs.cjs +0 -11
  72. package/dist/connect-CJ7BhOcs.cjs.map +0 -1
  73. package/dist/generate-BUqZ_7GB.cjs +0 -92
  74. package/dist/generate-BUqZ_7GB.cjs.map +0 -1
  75. package/dist/generate-q2m01zRd.js +0 -92
  76. package/dist/generate-q2m01zRd.js.map +0 -1
  77. package/dist/gunshiDryRun-DdKJuVn1.js +0 -2
  78. package/dist/init-Ckpwkfvz.cjs +0 -58
  79. package/dist/init-Ckpwkfvz.cjs.map +0 -1
  80. package/dist/init-DukE9Uw4.js +0 -58
  81. package/dist/init-DukE9Uw4.js.map +0 -1
  82. package/dist/login-Bc19oiFE.cjs +0 -11
  83. package/dist/login-Bc19oiFE.cjs.map +0 -1
  84. package/dist/login-Do3nns0B.js +0 -11
  85. package/dist/login-Do3nns0B.js.map +0 -1
  86. package/dist/logout-BvHFvCeI.cjs +0 -16
  87. package/dist/logout-BvHFvCeI.cjs.map +0 -1
  88. package/dist/logout-COxoIMum.js +0 -16
  89. package/dist/logout-COxoIMum.js.map +0 -1
  90. package/dist/mcp-B-GxGgVr.js +0 -19
  91. package/dist/mcp-B-GxGgVr.js.map +0 -1
  92. package/dist/mcp-B3B-rE7W.cjs +0 -18
  93. package/dist/mcp-B3B-rE7W.cjs.map +0 -1
  94. package/dist/output-CVLosNZq.cjs +0 -411
  95. package/dist/output-CVLosNZq.cjs.map +0 -1
  96. package/dist/output-FDo-db-w.js +0 -321
  97. package/dist/output-FDo-db-w.js.map +0 -1
  98. package/dist/package-Ci2fcoOV.js +0 -6
  99. package/dist/package-Ci2fcoOV.js.map +0 -1
  100. package/dist/package-Do9rJ7D3.cjs +0 -11
  101. package/dist/package-Do9rJ7D3.cjs.map +0 -1
  102. package/dist/rolldown-runtime-qbf5tadS.cjs +0 -38
  103. package/dist/run-BDaZPU-1.js.map +0 -1
  104. package/dist/run-BdTwxLCW.js.map +0 -1
  105. package/dist/run-C-jBKNbI.cjs.map +0 -1
  106. package/dist/run-CAcQlhub.js +0 -295
  107. package/dist/run-CAcQlhub.js.map +0 -1
  108. package/dist/run-CSoFhDyK.cjs +0 -297
  109. package/dist/run-CSoFhDyK.cjs.map +0 -1
  110. package/dist/run-CZz_GbE4.cjs.map +0 -1
  111. package/dist/run-CaEbo-te.js +0 -40
  112. package/dist/run-CaEbo-te.js.map +0 -1
  113. package/dist/run-CihzLZmR.cjs +0 -41
  114. package/dist/run-CihzLZmR.cjs.map +0 -1
  115. package/dist/run-Dn8HVjnM.js.map +0 -1
  116. package/dist/run-y6LRyWdS.cjs.map +0 -1
  117. package/dist/shared-Cl-OLpNh.cjs +0 -81
  118. package/dist/shared-Cl-OLpNh.cjs.map +0 -1
  119. package/dist/shared-DaHA7QFQ.js +0 -58
  120. package/dist/shared-DaHA7QFQ.js.map +0 -1
  121. package/dist/snapshot-B5mc4gpC.js.map +0 -1
  122. package/dist/snapshot-DnCxZfmM.cjs.map +0 -1
  123. package/dist/start-B-GXO8tS.cjs +0 -12
  124. package/dist/start-B-GXO8tS.cjs.map +0 -1
  125. package/dist/start-Dbfx_8zG.js +0 -12
  126. package/dist/start-Dbfx_8zG.js.map +0 -1
  127. package/dist/status-DDajNc8w.js +0 -17
  128. package/dist/status-DDajNc8w.js.map +0 -1
  129. package/dist/status-DusS7DfO.cjs +0 -17
  130. package/dist/status-DusS7DfO.cjs.map +0 -1
  131. package/dist/stop-C5t7Yx0t.js +0 -11
  132. package/dist/stop-C5t7Yx0t.js.map +0 -1
  133. package/dist/stop-zmM3A0Re.cjs +0 -11
  134. package/dist/stop-zmM3A0Re.cjs.map +0 -1
  135. package/dist/studio--IOOl_VB.js +0 -174
  136. package/dist/studio--IOOl_VB.js.map +0 -1
  137. package/dist/studio-CJ2XhtRd.cjs +0 -174
  138. package/dist/studio-CJ2XhtRd.cjs.map +0 -1
  139. package/dist/utils-C3HZOWL_.js +0 -1107
  140. package/dist/utils-C3HZOWL_.js.map +0 -1
  141. package/dist/utils-ClRbJfxu.cjs +0 -1210
  142. package/dist/utils-ClRbJfxu.cjs.map +0 -1
  143. package/dist/validate-BcB0tFQ6.cjs +0 -19
  144. package/dist/validate-BcB0tFQ6.cjs.map +0 -1
  145. package/dist/validate-QpkCbIFJ.js +0 -20
  146. package/dist/validate-QpkCbIFJ.js.map +0 -1
@@ -1,1107 +0,0 @@
1
- import "./rolldown-runtime-CRm0XQPb.js";
2
- import { t as toError } from "./errors-BQJzaUwP.js";
3
- import { t as getAgentName } from "./agent-yA5vrXJB.js";
4
- import { c as URL_WATCHER_TIMEOUT_MS, l as WATCHER_IGNORED_PATHS, s as URL_WATCHER_INTERVAL_MS } from "./Telemetry-CMXOVUKB.js";
5
- import { f as canUseTTY, h as getElapsedMs, m as formatMs, p as formatMsWithColor } from "./output-FDo-db-w.js";
6
- import { styleText } from "node:util";
7
- import { randomUUID } from "node:crypto";
8
- import process from "node:process";
9
- import { spawn } from "node:child_process";
10
- import { basename, dirname, relative, resolve } from "node:path";
11
- import * as clack from "@clack/prompts";
12
- import { createJiti } from "jiti";
13
- import { Diagnostics, createCliReporter, logLevel } from "@kubb/core";
14
- import { NonZeroExitError, x } from "tinyexec";
15
- import { loadConfig } from "unconfig";
16
- import { isGreaterThan, isValid, truncate } from "verkit";
17
- //#region ../../internals/utils/src/tools.ts
18
- /**
19
- * CLI command descriptors for each supported code formatter.
20
- */
21
- const formatters = {
22
- prettier: {
23
- command: "prettier",
24
- args: (outputPath) => [
25
- "--ignore-unknown",
26
- "--write",
27
- outputPath
28
- ],
29
- errorMessage: "Prettier not found"
30
- },
31
- biome: {
32
- command: "biome",
33
- args: (outputPath) => [
34
- "format",
35
- "--write",
36
- outputPath
37
- ],
38
- errorMessage: "Biome not found"
39
- },
40
- oxfmt: {
41
- command: "oxfmt",
42
- args: (outputPath) => [outputPath],
43
- errorMessage: "Oxfmt not found"
44
- }
45
- };
46
- /**
47
- * CLI command descriptors for each supported linter.
48
- */
49
- const linters = {
50
- eslint: {
51
- command: "eslint",
52
- args: (outputPath) => [outputPath, "--fix"],
53
- errorMessage: "Eslint not found"
54
- },
55
- biome: {
56
- command: "biome",
57
- args: (outputPath) => [
58
- "lint",
59
- "--fix",
60
- outputPath
61
- ],
62
- errorMessage: "Biome not found"
63
- },
64
- oxlint: {
65
- command: "oxlint",
66
- args: (outputPath) => [
67
- "--fix",
68
- "--no-ignore",
69
- outputPath
70
- ],
71
- errorMessage: "Oxlint not found"
72
- }
73
- };
74
- /**
75
- * Preference order for `format: 'auto'`, most-preferred first. Spelled out rather than taken from
76
- * the table's key order, which is arbitrary and would silently change what `auto` picks.
77
- */
78
- const FORMATTER_PREFERENCE = [
79
- "oxfmt",
80
- "biome",
81
- "prettier"
82
- ];
83
- /**
84
- * Preference order for `lint: 'auto'`, most-preferred first.
85
- */
86
- const LINTER_PREFERENCE = [
87
- "oxlint",
88
- "biome",
89
- "eslint"
90
- ];
91
- /**
92
- * Whether `name` is on PATH and answers `--version` with a zero exit.
93
- */
94
- function isToolAvailable(name) {
95
- return new Promise((resolve) => {
96
- const child = spawn(name, ["--version"], { stdio: "ignore" });
97
- child.on("close", (code) => resolve(code === 0));
98
- child.on("error", () => resolve(false));
99
- });
100
- }
101
- /**
102
- * Returns the first installed executable from `candidates`, or `null` when none are found.
103
- *
104
- * Not memoized: a long-running host that probes repeatedly should cache the result itself, and a
105
- * `--watch` build should keep noticing a tool installed mid-session.
106
- */
107
- async function detectTool(candidates) {
108
- for (const candidate of candidates) if (await isToolAvailable(candidate)) return candidate;
109
- return null;
110
- }
111
- /**
112
- * Tokenizes a shell command string, respecting single and double quotes.
113
- *
114
- * @example
115
- * ```ts
116
- * tokenize('git commit -m "initial commit"')
117
- * // → ['git', 'commit', '-m', 'initial commit']
118
- * ```
119
- */
120
- function tokenize(command) {
121
- return (command.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g) ?? []).map((token) => token.replace(/^["']|["']$/g, ""));
122
- }
123
- //#endregion
124
- //#region ../../internals/shared/src/loader.ts
125
- /**
126
- * jiti options for loading Kubb config modules: the automatic JSX runtime pointed at
127
- * `@kubb/renderer-jsx`, and `moduleCache` off so a re-load re-evaluates the file.
128
- */
129
- const JITI_OPTIONS = {
130
- jsx: {
131
- runtime: "automatic",
132
- importSource: "@kubb/renderer-jsx"
133
- },
134
- moduleCache: false
135
- };
136
- /**
137
- * Creates a jiti-based loader for Kubb's TypeScript and JavaScript config modules.
138
- *
139
- * jiti transpiles TypeScript and the `@kubb/renderer-jsx` JSX runtime on the fly.
140
- *
141
- * @example
142
- * ```ts
143
- * const config = await createModuleLoader().load('/abs/kubb.config.ts', { default: true })
144
- * ```
145
- */
146
- function createModuleLoader() {
147
- const jiti = createJiti(import.meta.url, JITI_OPTIONS);
148
- return { load(filePath, options) {
149
- return options?.default ? jiti.import(filePath, { default: true }) : jiti.import(filePath);
150
- } };
151
- }
152
- /**
153
- * TTY logger for local development, with animated steps and progress bars.
154
- */
155
- const clackLogger = {
156
- name: "clack",
157
- install: createLogger({
158
- group: (title) => clack.intro(title),
159
- groupEnd: (text) => clack.outro(text),
160
- step: (text) => clack.log.step(text, { spacing: 0 }),
161
- info: (text) => clack.log.info(text),
162
- success: (text) => clack.log.success(text),
163
- warn: (text) => clack.log.warn(text),
164
- error: (text) => clack.log.error(text),
165
- block: (lines) => clack.log.message(lines, { spacing: 0 }),
166
- raw: (lines) => console.log(styleText("dim", lines.join("\n"))),
167
- diagnostic: (lines) => clack.log.message(lines, {
168
- symbol: "",
169
- secondarySymbol: ""
170
- }),
171
- update: (lines, title) => clack.box(lines.join("\n"), title, {
172
- width: "auto",
173
- formatBorder: (border) => styleText("yellow", border),
174
- rounded: true,
175
- withGuide: false,
176
- contentAlign: "center",
177
- titleAlign: "center"
178
- }),
179
- spinner() {
180
- const spinner = clack.spinner();
181
- return {
182
- start: (text) => spinner.start(text),
183
- message: (text) => spinner.message(text),
184
- clear: () => spinner.clear(),
185
- stop: (text) => spinner.stop(text),
186
- error: (text) => spinner.error(text)
187
- };
188
- },
189
- progress(max) {
190
- const bar = clack.progress({
191
- style: "block",
192
- max,
193
- size: 30
194
- });
195
- return {
196
- start: (text) => bar.start(text),
197
- advance: (text) => bar.advance(1, text),
198
- stop: (text) => bar.stop(text)
199
- };
200
- }
201
- })
202
- };
203
- //#endregion
204
- //#region src/loggers/utils.ts
205
- /**
206
- * Display path for a config's input: the string form, or its `path` field when the input is an
207
- * object. Loggers show it alongside `Generation started`.
208
- */
209
- function getInputPath(config) {
210
- const { input } = config;
211
- if (typeof input === "string") return input;
212
- return typeof input?.path === "string" ? input.path : void 0;
213
- }
214
- /**
215
- * Counts a noun, so a message never reads `1 files`.
216
- *
217
- * @example
218
- * `pluralize(1, 'config')` returns `'1 config'`
219
- */
220
- function pluralize(count, noun) {
221
- return `${count} ${count === 1 ? noun : `${noun}s`}`;
222
- }
223
- /**
224
- * Optionally prefix a message with a [HH:MM:SS] timestamp when logLevel >= verbose.
225
- * Shared across all logger adapters to avoid duplication.
226
- */
227
- function formatMessage(message, logLevel$2) {
228
- if (logLevel$2 >= logLevel.verbose) {
229
- const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
230
- hour12: false,
231
- hour: "2-digit",
232
- minute: "2-digit",
233
- second: "2-digit"
234
- });
235
- return `${styleText("dim", `[${timestamp}]`)} ${message}`;
236
- }
237
- return message;
238
- }
239
- /**
240
- * Renders the versions from a `studio:connected` event as one parenthetical. The runtime is listed
241
- * only when it differs from the host, and Studio's only when it sent one.
242
- */
243
- function formatVersions({ studio, kubb, agent }) {
244
- return [
245
- `v${agent}`,
246
- kubb !== agent ? `runtime v${kubb}` : void 0,
247
- studio ? `Studio v${studio}` : void 0
248
- ].filter(Boolean).join(", ");
249
- }
250
- /**
251
- * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.
252
- */
253
- function toCause(error) {
254
- return error.cause instanceof Error ? error.cause : void 0;
255
- }
256
- /**
257
- * First stack frames for verbose error output, including an optional `cause` chain.
258
- */
259
- function formatErrorFrames(error) {
260
- if (!error.stack) return null;
261
- const frames = error.stack.split("\n").slice(1, 4).map((frame) => frame.trim());
262
- const caused = toCause(error);
263
- if (!caused?.stack) return { frames };
264
- return {
265
- frames,
266
- cause: {
267
- header: `└─ caused by ${caused.message}`,
268
- frames: caused.stack.split("\n").slice(1, 4).map((frame) => frame.trim())
269
- }
270
- };
271
- }
272
- /**
273
- * Build the progress summary line shown by the clack logger.
274
- * Returns null when there is nothing to display.
275
- */
276
- function buildProgressLine(state) {
277
- const parts = [];
278
- const duration = formatMs(getElapsedMs(state.hrStart));
279
- if (state.totalPlugins > 0) {
280
- const pluginStr = state.failedPlugins > 0 ? `Plugins ${styleText("green", state.completedPlugins.toString())}/${state.totalPlugins} ${styleText("red", `(${state.failedPlugins} failed)`)}` : `Plugins ${styleText("green", state.completedPlugins.toString())}/${state.totalPlugins}`;
281
- parts.push(pluginStr);
282
- }
283
- if (state.totalFiles > 0) parts.push(`Files ${styleText("green", state.processedFiles.toString())}/${state.totalFiles}`);
284
- if (parts.length === 0) return null;
285
- parts.push(`${styleText("green", duration)} elapsed`);
286
- return parts.join(styleText("dim", " | "));
287
- }
288
- /**
289
- * Creates the per-run progress counters used by the clack logger.
290
- */
291
- function createProgressCounters() {
292
- return {
293
- totalPlugins: 0,
294
- completedPlugins: 0,
295
- failedPlugins: 0,
296
- totalFiles: 0,
297
- processedFiles: 0,
298
- hrStart: process.hrtime()
299
- };
300
- }
301
- /**
302
- * Resets the progress counters in place at the start/end of a generation run.
303
- */
304
- function resetProgressCounters(state) {
305
- state.totalPlugins = 0;
306
- state.completedPlugins = 0;
307
- state.failedPlugins = 0;
308
- state.totalFiles = 0;
309
- state.processedFiles = 0;
310
- state.hrStart = process.hrtime();
311
- }
312
- /**
313
- * Records a finished plugin against the progress counters.
314
- */
315
- function recordPluginResult(state, success) {
316
- if (success) state.completedPlugins++;
317
- else state.failedPlugins++;
318
- }
319
- /**
320
- * Join a command and its optional args into a single display string.
321
- *
322
- * @example With args
323
- * `formatCommandWithArgs('prettier', ['--write', '.'])` returns `'prettier --write .'`
324
- */
325
- function formatCommandWithArgs(command, args) {
326
- return args?.length ? `${command} ${args.join(" ")}` : command;
327
- }
328
- /**
329
- * Bridges a {@link Reporter} onto the run's hook emitter: calls `report` with each config's
330
- * {@link GenerationResult} on `kubb:generation:end`. The reporter never touches the emitter.
331
- */
332
- function installReporter(context, reporter, ctx) {
333
- const pluginFiles = reporter.needsPluginFiles ? /* @__PURE__ */ new Map() : void 0;
334
- if (pluginFiles) context.hook("kubb:plugin:end", ({ config, plugin, files }) => {
335
- const filesByPlugin = pluginFiles.get(config) ?? /* @__PURE__ */ new Map();
336
- const seen = new Set([...filesByPlugin.values()].flatMap((paths) => [...paths]));
337
- const added = files.filter((file) => !seen.has(file.path)).map((file) => file.path);
338
- if (added.length) filesByPlugin.set(plugin.name, /* @__PURE__ */ new Set([...filesByPlugin.get(plugin.name) ?? [], ...added]));
339
- pluginFiles.set(config, filesByPlugin);
340
- });
341
- context.hook("kubb:generation:end", async ({ config, diagnostics = [], filesCreated = 0, status = "success", hrStart = process.hrtime() }) => {
342
- const files = pluginFiles?.get(config);
343
- const grouped = files && [...files].map(([plugin, paths]) => ({
344
- plugin,
345
- files: [...paths]
346
- }));
347
- await reporter.report({
348
- config,
349
- diagnostics,
350
- filesCreated,
351
- status,
352
- hrStart,
353
- pluginFiles: grouped
354
- }, ctx);
355
- pluginFiles?.delete(config);
356
- });
357
- if (reporter.drain) context.hook("kubb:lifecycle:end", () => reporter.drain?.(ctx));
358
- }
359
- /**
360
- * Installs the live logger (the TUI view) and the given reporters (the output). The reporters are
361
- * already selected by the caller (the CLI maps `--reporter` to names via `selectReporters`). This
362
- * only wires them. Loggers receive hook subprocess output through `kubb:hook:line` and the
363
- * `stdout`/`stderr` on `kubb:hook:end`, so nothing is returned here.
364
- *
365
- * Loggers and reporters are independent, except for `cli`: it both installs the live logger view
366
- * here and registers a reporter that renders the per-config summary through that logger, so the
367
- * summary lands inside the group the logger opened. The `json` reporter owns stdout, so the whole
368
- * `cli` reporter (live logger and summary) is skipped whenever `json` is among the reporters, even
369
- * if `cli` is also listed.
370
- */
371
- async function setupReporters(context, { logLevel, reporters, logger: forcedLogger }) {
372
- let cliHandle;
373
- const hasJson = reporters.some((reporter) => reporter.name === "json");
374
- const ctx = { logLevel };
375
- for (const reporter of reporters) {
376
- if (reporter.name !== "cli") {
377
- installReporter(context, reporter, ctx);
378
- continue;
379
- }
380
- if (hasJson) continue;
381
- const handle = await (forcedLogger ?? (canUseTTY() && !getAgentName() ? clackLogger : plainLogger)).install(context, { logLevel }) ?? void 0;
382
- cliHandle = handle;
383
- installReporter(context, createCliReporter({ render: handle?.renderSummary }), ctx);
384
- }
385
- return cliHandle;
386
- }
387
- /**
388
- * Picks the reporters whose `name` matches one of `names`, in the order the names are given.
389
- * The config carries every available reporter, and the host selects which to activate by name
390
- * (the CLI maps `--reporter` to this). Duplicate names and names without a matching reporter are
391
- * skipped.
392
- */
393
- function selectReporters(reporters, names) {
394
- const seen = /* @__PURE__ */ new Set();
395
- const selected = [];
396
- for (const name of names) {
397
- if (seen.has(name)) continue;
398
- seen.add(name);
399
- const reporter = reporters.find((candidate) => candidate.name === name);
400
- if (reporter) selected.push(reporter);
401
- }
402
- return selected;
403
- }
404
- //#endregion
405
- //#region src/loggers/createLogger.ts
406
- /**
407
- * The three output phases, in the order the generate runner runs them.
408
- */
409
- const PHASES = {
410
- format: {
411
- running: "Formatting",
412
- success: "Formatted",
413
- failure: "Formatting failed"
414
- },
415
- lint: {
416
- running: "Linting",
417
- success: "Linted",
418
- failure: "Linting failed"
419
- },
420
- hooks: {
421
- running: "Running post-generate hooks",
422
- success: "Post-generate hooks completed",
423
- failure: "Post-generate hooks failed",
424
- showHookResults: true
425
- }
426
- };
427
- /**
428
- * Drops the blank lines a subprocess leaves at either end of its output, so its block does not open
429
- * or close on an empty row.
430
- */
431
- function trimBlankEdges(lines) {
432
- const start = lines.findIndex((line) => line.trim());
433
- if (start === -1) return [];
434
- return lines.slice(start, lines.findLastIndex((line) => line.trim()) + 1);
435
- }
436
- /**
437
- * Wires a {@link LoggerWriter} onto the run's hooks. This owns what a run says and in what order:
438
- * one group per config, a step over the plugin work, a progress bar for the writes, one step per
439
- * output phase, then the summary and the group's closing result. The writer only decides how each
440
- * line looks, so every logger reports the same run the same way.
441
- *
442
- * @example
443
- * ```ts
444
- * export const plainLogger = { name: 'plain', install: (context, options) => createLogger(writer)(context, options) }
445
- * ```
446
- */
447
- function createLogger(writer) {
448
- return function install(context, options) {
449
- const logLevel$1 = options?.logLevel ?? logLevel.info;
450
- const silent = logLevel$1 <= logLevel.silent;
451
- const state = {
452
- ...createProgressCounters(),
453
- spinner: null,
454
- progress: null,
455
- hooks: /* @__PURE__ */ new Map(),
456
- /**
457
- * Plugin results held back while the generation step owns the line.
458
- */
459
- pluginLines: [],
460
- phase: null,
461
- groupOpen: false
462
- };
463
- function text(message) {
464
- return formatMessage(message, logLevel$1);
465
- }
466
- function startSpinner(message) {
467
- if (silent) return;
468
- state.spinner = writer.spinner();
469
- state.spinner.start(text(message));
470
- }
471
- function stopSpinner(message, status = "success") {
472
- const spinner = state.spinner;
473
- if (!spinner) return;
474
- state.spinner = null;
475
- if (message === void 0) {
476
- spinner.clear();
477
- return;
478
- }
479
- if (status === "failed") {
480
- spinner.error(text(message));
481
- return;
482
- }
483
- spinner.stop(text(message));
484
- }
485
- function stopProgress(message) {
486
- const progress = state.progress;
487
- if (!progress) return;
488
- state.progress = null;
489
- progress.stop(message === void 0 ? "" : text(message));
490
- }
491
- function startPhase({ running, success, failure, showHookResults = false }) {
492
- state.phase = {
493
- successLabel: success,
494
- failureLabel: failure,
495
- showHookResults,
496
- failed: false,
497
- hrStart: process.hrtime(),
498
- lines: [],
499
- output: []
500
- };
501
- startSpinner(running);
502
- }
503
- function endPhase() {
504
- const phase = state.phase;
505
- state.phase = null;
506
- if (!phase || silent) return;
507
- const duration = formatMsWithColor(getElapsedMs(phase.hrStart));
508
- stopSpinner(phase.failed ? `${phase.failureLabel} after ${duration}` : `${phase.successLabel} in ${duration}`, phase.failed ? "failed" : "success");
509
- if (phase.lines.length) writer.block(phase.lines);
510
- writeOutput(phase.output);
511
- }
512
- function writeOutput(lines) {
513
- const output = trimBlankEdges(lines);
514
- if (output.length) writer.raw(output);
515
- }
516
- function closeGroup(status) {
517
- if (!state.groupOpen) return;
518
- state.groupOpen = false;
519
- stopSpinner();
520
- stopProgress();
521
- writer.groupEnd(status === "failed" ? styleText("red", "✗ Generation failed") : styleText("green", "✓ Generation succeeded"));
522
- }
523
- function reset() {
524
- stopSpinner();
525
- stopProgress();
526
- resetProgressCounters(state);
527
- state.hooks.clear();
528
- state.pluginLines = [];
529
- state.phase = null;
530
- state.groupOpen = false;
531
- }
532
- context.hook("kubb:info", ({ message, info }) => {
533
- if (silent) return;
534
- const line = text([
535
- styleText("blue", "ℹ"),
536
- message,
537
- info ? styleText("dim", info) : void 0
538
- ].filter(Boolean).join(" "));
539
- if (state.phase) {
540
- state.phase.lines.push(line);
541
- return;
542
- }
543
- if (state.spinner) {
544
- state.spinner.message(line);
545
- return;
546
- }
547
- writer.info(line);
548
- });
549
- context.hook("kubb:success", ({ message, info }) => {
550
- if (silent) return;
551
- const line = text([
552
- styleText("green", "✓"),
553
- message,
554
- logLevel$1 >= logLevel.info && info ? styleText("dim", info) : void 0
555
- ].filter(Boolean).join(" "));
556
- if (state.spinner) {
557
- state.spinner.message(line);
558
- return;
559
- }
560
- writer.success(line);
561
- });
562
- context.hook("kubb:warn", ({ message, info }) => {
563
- if (logLevel$1 < logLevel.warn) return;
564
- writer.warn(text([
565
- styleText("yellow", "⚠"),
566
- message,
567
- logLevel$1 >= logLevel.info && info ? styleText("dim", info) : void 0
568
- ].filter(Boolean).join(" ")));
569
- });
570
- context.hook("kubb:error", ({ error }) => {
571
- if (state.phase) state.phase.failed = true;
572
- if (state.spinner) stopSpinner(error.message, "failed");
573
- else writer.error(text(error.message));
574
- const frames = logLevel$1 >= logLevel.verbose ? formatErrorFrames(error) : null;
575
- if (!frames) return;
576
- writer.block(frames.frames.map((frame) => text(styleText("dim", frame))));
577
- if (frames.cause) writer.block([styleText("dim", frames.cause.header), ...frames.cause.frames.map((frame) => text(` ${styleText("dim", frame)}`))]);
578
- });
579
- context.hook("kubb:diagnostic", ({ diagnostic }) => {
580
- if (silent && diagnostic.severity !== "error") return;
581
- stopSpinner();
582
- stopProgress();
583
- if (Diagnostics.isUpdate(diagnostic)) {
584
- writer.update([`\`v${diagnostic.currentVersion}\` → \`v${diagnostic.latestVersion}\``, "Run `npm install -g @kubb/cli` to update"], "Update available for `Kubb`");
585
- return;
586
- }
587
- const { headline, details } = Diagnostics.format(diagnostic);
588
- writer.diagnostic([headline, ...details]);
589
- });
590
- context.hook("studio:connecting", ({ url }) => {
591
- startSpinner(`Connecting to ${styleText("cyan", url)}`);
592
- });
593
- context.hook("studio:connected", ({ url, versions }) => {
594
- if (silent) return;
595
- const line = `Connected to ${styleText("cyan", url)} ${styleText("dim", `(${formatVersions(versions)})`)}`;
596
- if (state.spinner) {
597
- stopSpinner(line);
598
- return;
599
- }
600
- writer.success(text(`✓ ${line}`));
601
- });
602
- context.hook("studio:ready", () => {
603
- if (silent) return;
604
- startSpinner("✓ Ready to receive jobs");
605
- });
606
- context.hook("studio:disconnected", ({ reason }) => {
607
- if (logLevel$1 < logLevel.warn) return;
608
- stopSpinner();
609
- writer.warn(text(`⚠ Kubb Studio ended the session (${reason})`));
610
- });
611
- context.hook("studio:command:start", ({ command }) => {
612
- if (silent) return;
613
- stopSpinner();
614
- writer.info(text(`Kubb Studio asked to ${styleText("bold", command)}`));
615
- });
616
- context.hook("studio:command:end", ({ command, info }) => {
617
- if (silent) return;
618
- writer.success(text(`✓ Finished ${command}${info ? ` ${styleText("dim", `(${info})`)}` : ""}`));
619
- startSpinner("✓ Ready to receive jobs");
620
- });
621
- context.hook("studio:reconnecting", ({ delayMs }) => {
622
- if (silent) return;
623
- stopSpinner();
624
- writer.info(text(styleText("dim", `Retrying connection to Kubb Studio in ${formatMs(delayMs)}`)));
625
- });
626
- context.hook("studio:warn", ({ message, permission }) => {
627
- if (logLevel$1 < logLevel.warn) return;
628
- const remedy = permission ? `; pass --${permission.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)} to allow it` : "";
629
- writer.warn(text(`⚠ ${message}${remedy}`));
630
- });
631
- context.hook("studio:error", ({ error }) => {
632
- if (state.spinner) {
633
- stopSpinner(error.message, "failed");
634
- return;
635
- }
636
- writer.error(text(error.message));
637
- });
638
- context.hook("kubb:lifecycle:start", () => {
639
- reset();
640
- });
641
- context.hook("kubb:generation:start", ({ config }) => {
642
- closeGroup("failed");
643
- reset();
644
- state.totalPlugins = config.plugins?.length ?? 0;
645
- if (silent) return;
646
- const inputPath = getInputPath(config);
647
- writer.group(text([styleText("bold", config.name ?? "kubb"), inputPath ? styleText("dim", inputPath) : void 0].filter(Boolean).join(" ")));
648
- state.groupOpen = true;
649
- startSpinner("Generating");
650
- });
651
- context.hook("kubb:plugin:start", ({ plugin }) => {
652
- if (silent) return;
653
- state.spinner?.message(text(`Generating ${styleText("dim", plugin.name)}`));
654
- });
655
- context.hook("kubb:plugin:end", ({ plugin, duration, success }) => {
656
- if (silent) return;
657
- recordPluginResult(state, success);
658
- state.pluginLines.push(text(`${plugin.name} ${success ? "completed" : "failed"} in ${formatMsWithColor(duration)}`));
659
- });
660
- context.hook("kubb:plugins:end", () => {
661
- if (silent) return;
662
- const failed = state.failedPlugins > 0;
663
- stopSpinner(buildProgressLine(state) ?? "No plugins ran", failed ? "failed" : "success");
664
- if (state.pluginLines.length) {
665
- writer.block(state.pluginLines);
666
- state.pluginLines = [];
667
- }
668
- });
669
- context.hook("kubb:files:processing:start", ({ files }) => {
670
- if (silent) return;
671
- stopSpinner();
672
- state.totalFiles = files.length;
673
- state.processedFiles = 0;
674
- state.progress = writer.progress(files.length);
675
- state.progress.start(text(`Writing ${pluralize(files.length, "file")}`));
676
- });
677
- context.hook("kubb:files:processing:update", ({ files }) => {
678
- if (silent) return;
679
- for (const { file, config } of files) {
680
- state.processedFiles++;
681
- state.progress?.advance(`Writing ${relative(config.root, file.path)}`);
682
- }
683
- });
684
- context.hook("kubb:files:processing:end", () => {
685
- if (silent) return;
686
- stopProgress(`Wrote ${pluralize(state.processedFiles, "file")}`);
687
- });
688
- context.hook("kubb:format:start", () => startPhase(PHASES.format));
689
- context.hook("kubb:format:end", endPhase);
690
- context.hook("kubb:lint:start", () => startPhase(PHASES.lint));
691
- context.hook("kubb:lint:end", endPhase);
692
- context.hook("kubb:hooks:start", () => startPhase(PHASES.hooks));
693
- context.hook("kubb:hooks:end", endPhase);
694
- context.hook("kubb:hook:start", ({ id }) => {
695
- if (silent || !id) return;
696
- state.hooks.set(id, {
697
- hrStart: process.hrtime(),
698
- lines: []
699
- });
700
- });
701
- if (!silent) context.hook("kubb:hook:line", ({ id, line }) => {
702
- const active = state.hooks.get(id);
703
- if (!active) return;
704
- if (state.phase) {
705
- active.lines.push(line);
706
- return;
707
- }
708
- writer.raw([line]);
709
- });
710
- context.hook("kubb:hook:end", ({ id, command, name, args, success, error, stdout, stderr }) => {
711
- if (!id) return;
712
- if (!success && state.phase) state.phase.failed = true;
713
- if (silent) {
714
- if (!success) {
715
- if (stdout) console.log(stdout);
716
- if (stderr) console.error(stderr);
717
- }
718
- return;
719
- }
720
- const active = state.hooks.get(id);
721
- if (!active) return;
722
- state.hooks.delete(id);
723
- const label = styleText("dim", name ?? formatCommandWithArgs(command, args));
724
- const reason = error?.message ? ` (${error.message})` : "";
725
- const result = success ? `${styleText("green", "✓")} ${label} in ${formatMsWithColor(getElapsedMs(active.hrStart))}` : `${styleText("red", "✗")} ${label} failed${reason}`;
726
- const phase = state.phase;
727
- if (phase) {
728
- if (phase.showHookResults || !success) phase.lines.push(result);
729
- phase.output.push(...active.lines);
730
- return;
731
- }
732
- stopSpinner();
733
- writer.step(text(result));
734
- writeOutput(active.lines);
735
- });
736
- context.hook("kubb:generation:end", () => {
737
- stopSpinner();
738
- stopProgress();
739
- });
740
- context.hook("kubb:lifecycle:end", () => {
741
- closeGroup("failed");
742
- reset();
743
- });
744
- return { renderSummary(lines, { status }) {
745
- if (silent || !state.groupOpen) return;
746
- closeGroup(status);
747
- writer.diagnostic([...lines]);
748
- } };
749
- };
750
- }
751
- //#endregion
752
- //#region src/loggers/plainLogger.ts
753
- /**
754
- * Prefixes that stand in for the symbols clack draws, so plain output reads the same way.
755
- */
756
- const SYMBOLS = {
757
- info: "ℹ",
758
- warn: "⚠",
759
- error: "✗",
760
- step: "◇"
761
- };
762
- /** Draws with one `write` call per line. Nothing animates, so a step prints at start and end. */
763
- function createWriter(write) {
764
- return {
765
- group: write,
766
- groupEnd: write,
767
- step: (text) => write(`${SYMBOLS.step} ${text}`),
768
- info: write,
769
- success: write,
770
- warn: write,
771
- error: (text) => write(`${SYMBOLS.error} ${text}`),
772
- block: (lines) => write(lines.join("\n")),
773
- raw: (lines) => write(lines.join("\n")),
774
- diagnostic: (lines) => write(lines.join("\n")),
775
- update: (lines, title) => write([title, ...lines].join("\n")),
776
- spinner: () => ({
777
- start: write,
778
- message: write,
779
- clear: () => {},
780
- stop: (text) => text && write(text),
781
- error: (text) => write(`${SYMBOLS.error} ${text}`)
782
- }),
783
- progress: () => ({
784
- start: write,
785
- advance: write,
786
- stop: (text) => text && write(text)
787
- })
788
- };
789
- }
790
- /** The plain logger writing through `write`, e.g. `console.error` so stdout carries only JSON. */
791
- function createPlainLogger(write) {
792
- return {
793
- name: "plain",
794
- install: createLogger(createWriter(write))
795
- };
796
- }
797
- /**
798
- * Console adapter for non-TTY environments, and for an AI coding agent reading the output. Reports
799
- * the same run in the same order as `clackLogger`, without the animation.
800
- */
801
- const plainLogger = createPlainLogger((line) => console.log(line));
802
- //#endregion
803
- //#region src/runners/generate/utils.ts
804
- const loader = createModuleLoader();
805
- const tsLoader = (configFile) => loader.load(configFile, { default: true });
806
- const MODULE_NAME = "kubb";
807
- const SEARCH_FILES = [
808
- "",
809
- ".config/",
810
- "configs/"
811
- ].flatMap((prefix) => [`${prefix}.${MODULE_NAME}rc`, `${prefix}${MODULE_NAME}.config`]);
812
- const SEARCH_EXTENSIONS = [
813
- "ts",
814
- "mts",
815
- "cts",
816
- "js",
817
- "mjs",
818
- "cjs"
819
- ];
820
- /**
821
- * Discovers the Kubb config and resolves it into a normalized array of configs.
822
- * Every config in the result is guaranteed to have a `plugins` array.
823
- */
824
- async function getConfigs({ configPath, input, watch, logLevel }) {
825
- const abs = configPath ? resolve(configPath) : void 0;
826
- const sources = abs ? [{
827
- files: [basename(abs)],
828
- extensions: [],
829
- parser: tsLoader
830
- }] : [{
831
- files: SEARCH_FILES,
832
- extensions: SEARCH_EXTENSIONS,
833
- parser: tsLoader
834
- }];
835
- let result;
836
- try {
837
- result = await loadConfig({
838
- cwd: abs ? dirname(abs) : process.cwd(),
839
- sources,
840
- merge: false
841
- });
842
- } catch (error) {
843
- throw new Error("Config failed loading", { cause: error });
844
- }
845
- const [filepath] = result.sources;
846
- if (!result.config || !filepath) throw new Error("Config not defined, create a kubb.config.js or pass through your config with the option --config");
847
- const config = result.config;
848
- const resolved = await (typeof config === "function" ? config({
849
- config: configPath,
850
- input,
851
- watch,
852
- logLevel
853
- }) : config);
854
- return {
855
- configPath: filepath,
856
- configs: (Array.isArray(resolved) ? resolved : [resolved]).map((item) => {
857
- return {
858
- ...item,
859
- plugins: item.plugins ?? []
860
- };
861
- })
862
- };
863
- }
864
- /**
865
- * Returns `true` when `latest` is a newer semver version than `current`. Compares each numeric
866
- * part, so `5.10.0` beats `5.9.0` where a plain string comparison would not. Prerelease
867
- * suffixes are ignored, and a malformed version never reports an update.
868
- *
869
- * @example Double-digit minor
870
- * `isNewerVersion('5.9.0', '5.10.0') // true`
871
- *
872
- * @example String comparison would get this wrong the other way
873
- * `isNewerVersion('5.10.0', '5.9.0') // false`
874
- */
875
- function isNewerVersion(current, latest) {
876
- if (!isValid(current) || !isValid(latest)) return false;
877
- return isGreaterThan(truncate(latest, "patch"), truncate(current, "patch"));
878
- }
879
- /**
880
- * Runs the `output.postGenerate` commands of a Kubb config in sequence and returns each command's
881
- * outcome, so the caller can turn failures into diagnostics.
882
- */
883
- async function runPostGenerate({ commands, hooks }) {
884
- const results = [];
885
- for (const entry of commands) {
886
- const { command, name } = typeof entry === "string" ? {
887
- command: entry,
888
- name: void 0
889
- } : entry;
890
- const [cmd, ...args] = tokenize(command);
891
- if (!cmd) continue;
892
- const hookId = randomUUID();
893
- const commandWithArgs = [cmd, ...args].join(" ");
894
- await hooks.callHook("kubb:hook:start", {
895
- id: hookId,
896
- command: cmd,
897
- name,
898
- args
899
- });
900
- results.push(await runHook({
901
- id: hookId,
902
- command: cmd,
903
- name,
904
- args,
905
- commandWithArgs,
906
- hooks
907
- }));
908
- }
909
- return results;
910
- }
911
- /**
912
- * Spawns a hook command and returns its outcome, mirroring it through `kubb:hook:end` for the
913
- * loggers. A non-zero exit returns `success: false` rather than throwing, so the caller can turn
914
- * it into a diagnostic. Other spawn errors do the same. Output is streamed through `kubb:hook:line`
915
- * only while a listener is attached.
916
- */
917
- async function runHook({ id, command, name, args, commandWithArgs, hooks }) {
918
- const emitEnd = async (result) => {
919
- await hooks.callHook("kubb:hook:end", {
920
- command,
921
- name,
922
- args,
923
- id,
924
- ...result
925
- });
926
- return result;
927
- };
928
- const stream = hooks.listenerCount("kubb:hook:line") > 0;
929
- try {
930
- const proc = x(command, [...args ?? []], {
931
- nodeOptions: { detached: process.platform !== "win32" },
932
- throwOnError: true
933
- });
934
- if (stream) for await (const line of proc) await hooks.callHook("kubb:hook:line", {
935
- id,
936
- line
937
- });
938
- await proc;
939
- await hooks.callHook("kubb:success", { message: `${styleText("dim", name ?? commandWithArgs)} successfully executed` });
940
- return emitEnd({
941
- success: true,
942
- error: null
943
- });
944
- } catch (err) {
945
- if (!(err instanceof NonZeroExitError)) return emitEnd({
946
- success: false,
947
- error: toError(err)
948
- });
949
- const stderr = err.output?.stderr ?? "";
950
- const stdout = err.output?.stdout ?? "";
951
- return emitEnd({
952
- success: false,
953
- error: /* @__PURE__ */ new Error(`Hook execute failed: ${commandWithArgs}`),
954
- stdout,
955
- stderr
956
- });
957
- }
958
- }
959
- /**
960
- * Wraps `run` so invocations never overlap: a trigger that lands while a run is in flight
961
- * marks it dirty and runs once more after it finishes, no matter how many triggers arrived.
962
- * Useful for event-driven reruns (a file watcher, a queue drain) where bursts should
963
- * coalesce into a single trailing run.
964
- *
965
- * @example
966
- * ```ts
967
- * const rebuild = createSerialRunner({
968
- * run: () => build(),
969
- * onError: (error) => log.error(error.message),
970
- * })
971
- * watcher.on('change', () => void rebuild())
972
- * ```
973
- */
974
- function createSerialRunner({ run, onError }) {
975
- let running = false;
976
- let dirty = false;
977
- return async () => {
978
- if (running) {
979
- dirty = true;
980
- return;
981
- }
982
- running = true;
983
- do {
984
- dirty = false;
985
- try {
986
- await run();
987
- } catch (error) {
988
- onError(toError(error));
989
- }
990
- } while (dirty);
991
- running = false;
992
- };
993
- }
994
- /**
995
- * Starts a file watcher on the given paths and calls `cb` on any change.
996
- * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)
997
- * are debounced into one build, and builds never overlap: changes during a build queue exactly
998
- * one rebuild.
999
- */
1000
- async function startWatcher(path, cb, log = {
1001
- info: console.log,
1002
- error: console.log
1003
- }) {
1004
- const { watch } = await import("chokidar");
1005
- const watcher = watch(path, {
1006
- ignorePermissionErrors: true,
1007
- ignored: WATCHER_IGNORED_PATHS,
1008
- ignoreInitial: true
1009
- });
1010
- process.once("SIGINT", () => {
1011
- watcher.close();
1012
- });
1013
- process.once("SIGTERM", () => {
1014
- watcher.close();
1015
- });
1016
- const runBuild = createSerialRunner({
1017
- run: () => cb(path),
1018
- onError: () => log.error(styleText("red", "Watcher failed"))
1019
- });
1020
- let debounceTimer = null;
1021
- watcher.on("all", (type, file) => {
1022
- log.info(styleText("yellow", styleText("bold", `Change detected: ${type} ${file}`)));
1023
- if (debounceTimer) clearTimeout(debounceTimer);
1024
- debounceTimer = setTimeout(() => {
1025
- debounceTimer = null;
1026
- runBuild();
1027
- }, 100);
1028
- });
1029
- }
1030
- /**
1031
- * Fetches the body of a remote spec URL, `undefined` when the server does not answer with a
1032
- * readable 2xx body within the timeout. The caller seeds `startUrlWatcher` with the result, so
1033
- * the watcher compares polls against the content the initial build ran on.
1034
- */
1035
- async function fetchUrlBody(url, timeoutMs = URL_WATCHER_TIMEOUT_MS) {
1036
- try {
1037
- const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
1038
- if (!response.ok) return void 0;
1039
- return await response.text();
1040
- } catch {
1041
- return;
1042
- }
1043
- }
1044
- /**
1045
- * Polls a remote spec URL and calls `cb` whenever the response body changes. A remote document
1046
- * emits no filesystem events, so this is the URL counterpart of `startWatcher`.
1047
- *
1048
- * `initialBody` seeds the change detection, so an edit landing between the initial build and the
1049
- * first poll still rebuilds. An unreachable server (the API restarting between saves) is reported
1050
- * once per outage and polling continues; after it recovers, a rebuild happens only when the body
1051
- * actually differs from the last one seen, so a plain restart with an unchanged spec stays quiet.
1052
- * Each request is aborted after `timeoutMs`, so a hung response delays at most one poll.
1053
- *
1054
- * Returns a function that stops polling and aborts the in-flight request, so callers (and tests)
1055
- * can shut the watcher down without signaling the process.
1056
- */
1057
- function startUrlWatcher(url, cb, options = {}) {
1058
- const { log = {
1059
- info: console.log,
1060
- error: console.log
1061
- }, intervalMs = URL_WATCHER_INTERVAL_MS, timeoutMs = URL_WATCHER_TIMEOUT_MS, initialBody } = options;
1062
- let lastBody = initialBody;
1063
- let offline = false;
1064
- let timer = null;
1065
- let controller = null;
1066
- let stopped = false;
1067
- const stop = () => {
1068
- stopped = true;
1069
- if (timer) clearTimeout(timer);
1070
- controller?.abort();
1071
- };
1072
- process.once("SIGINT", stop);
1073
- process.once("SIGTERM", stop);
1074
- const runBuild = createSerialRunner({
1075
- run: () => cb([url]),
1076
- onError: () => log.error(styleText("red", "Watcher failed"))
1077
- });
1078
- const poll = async () => {
1079
- controller = new AbortController();
1080
- try {
1081
- const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(timeoutMs)]);
1082
- const response = await fetch(url, { signal });
1083
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
1084
- const body = await response.text();
1085
- if (offline) {
1086
- offline = false;
1087
- log.info(styleText("yellow", `${url} is reachable again`));
1088
- }
1089
- const changed = lastBody !== void 0 && body !== lastBody;
1090
- if (changed) log.info(styleText("yellow", styleText("bold", `Change detected: ${url}`)));
1091
- if (changed || lastBody === void 0) runBuild();
1092
- lastBody = body;
1093
- } catch {
1094
- if (!offline && !stopped) {
1095
- offline = true;
1096
- log.error(styleText("red", `Cannot reach ${url}, polling until it responds again`));
1097
- }
1098
- }
1099
- if (!stopped) timer = setTimeout(() => void poll(), intervalMs);
1100
- };
1101
- poll();
1102
- return stop;
1103
- }
1104
- //#endregion
1105
- export { linters as _, runPostGenerate as a, createPlainLogger as c, selectReporters as d, setupReporters as f, formatters as g, detectTool as h, runHook as i, plainLogger as l, LINTER_PREFERENCE as m, getConfigs as n, startUrlWatcher as o, FORMATTER_PREFERENCE as p, isNewerVersion as r, startWatcher as s, fetchUrlBody as t, pluralize as u };
1106
-
1107
- //# sourceMappingURL=utils-C3HZOWL_.js.map