@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.
- package/dist/{Telemetry-CMXOVUKB.js → Telemetry-RBcAjNxj.js} +18 -43
- package/dist/Telemetry-RBcAjNxj.js.map +1 -0
- package/dist/{Telemetry-DDHXBdEz.cjs → Telemetry-bGdFBaGt.cjs} +27 -89
- package/dist/Telemetry-bGdFBaGt.cjs.map +1 -0
- package/dist/agent-BPIqnEl2.js +59 -0
- package/dist/agent-BPIqnEl2.js.map +1 -0
- package/dist/agent-DElUuYuy.cjs +120 -0
- package/dist/agent-DElUuYuy.cjs.map +1 -0
- package/dist/{background-DxlRE9O5.js → background-0PMj56FJ.js} +6 -5
- package/dist/background-0PMj56FJ.js.map +1 -0
- package/dist/background-BqS01QDb.js +2 -0
- package/dist/{background-Bbcc2TdN.cjs → background-R0T_7Szu.cjs} +8 -7
- package/dist/background-R0T_7Szu.cjs.map +1 -0
- package/dist/commands-BAgGrcvc.js +65 -0
- package/dist/commands-BAgGrcvc.js.map +1 -0
- package/dist/commands-Si-RFAS6.cjs +71 -0
- package/dist/commands-Si-RFAS6.cjs.map +1 -0
- package/dist/config-8Xl4yZmw.js +146 -0
- package/dist/config-8Xl4yZmw.js.map +1 -0
- package/dist/config-ByfJSj3q.cjs +164 -0
- package/dist/config-ByfJSj3q.cjs.map +1 -0
- package/dist/constants-CdxpgX3_.cjs.map +1 -1
- package/dist/constants-DHRspBTo.js.map +1 -1
- package/dist/{errors-C-wEou02.cjs → errors-CMbMAwyW.cjs} +2 -2
- package/dist/{errors-C-wEou02.cjs.map → errors-CMbMAwyW.cjs.map} +1 -1
- package/dist/index.cjs +431 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +429 -20
- package/dist/index.js.map +1 -1
- package/dist/output-B6Ondw0g.js +776 -0
- package/dist/output-B6Ondw0g.js.map +1 -0
- package/dist/output-i3s5c4Xu.cjs +878 -0
- package/dist/output-i3s5c4Xu.cjs.map +1 -0
- package/dist/run-3I2NPeXG.cjs +31 -0
- package/dist/run-3I2NPeXG.cjs.map +1 -0
- package/dist/{run-C-jBKNbI.cjs → run-BkLJ5mXn.cjs} +22 -43
- package/dist/run-BkLJ5mXn.cjs.map +1 -0
- package/dist/run-CZlkqkDc.js +30 -0
- package/dist/run-CZlkqkDc.js.map +1 -0
- package/dist/run-CmNDNEjR.js +346 -0
- package/dist/run-CmNDNEjR.js.map +1 -0
- package/dist/{run-BdTwxLCW.js → run-D5AeacqK.js} +9 -23
- package/dist/run-D5AeacqK.js.map +1 -0
- package/dist/{run-y6LRyWdS.cjs → run-D8Wz_17X.cjs} +41 -60
- package/dist/run-D8Wz_17X.cjs.map +1 -0
- package/dist/{run-Dn8HVjnM.js → run-DQoKTiGk.js} +23 -44
- package/dist/run-DQoKTiGk.js.map +1 -0
- package/dist/{run-BDaZPU-1.js → run-Dgph0WYk.js} +25 -49
- package/dist/run-Dgph0WYk.js.map +1 -0
- package/dist/run-t8nm7zcT.cjs +348 -0
- package/dist/run-t8nm7zcT.cjs.map +1 -0
- package/dist/{run-CZz_GbE4.cjs → run-yvuot2N9.cjs} +12 -26
- package/dist/run-yvuot2N9.cjs.map +1 -0
- package/dist/{snapshot-B5mc4gpC.js → snapshot-CkHXR0Nl.js} +5 -5
- package/dist/snapshot-CkHXR0Nl.js.map +1 -0
- package/dist/{snapshot-DnCxZfmM.cjs → snapshot-D4nKbwkw.cjs} +9 -9
- package/dist/snapshot-D4nKbwkw.cjs.map +1 -0
- package/dist/studioWorker.js +2 -2
- package/package.json +7 -7
- package/dist/Telemetry-CMXOVUKB.js.map +0 -1
- package/dist/Telemetry-DDHXBdEz.cjs.map +0 -1
- package/dist/agent-DiB4CRre.cjs +0 -38
- package/dist/agent-DiB4CRre.cjs.map +0 -1
- package/dist/agent-yA5vrXJB.js +0 -26
- package/dist/agent-yA5vrXJB.js.map +0 -1
- package/dist/background-Bbcc2TdN.cjs.map +0 -1
- package/dist/background-CFNncNYT.js +0 -2
- package/dist/background-DxlRE9O5.js.map +0 -1
- package/dist/connect-C9tTngda.js +0 -11
- package/dist/connect-C9tTngda.js.map +0 -1
- package/dist/connect-CJ7BhOcs.cjs +0 -11
- package/dist/connect-CJ7BhOcs.cjs.map +0 -1
- package/dist/generate-BUqZ_7GB.cjs +0 -92
- package/dist/generate-BUqZ_7GB.cjs.map +0 -1
- package/dist/generate-q2m01zRd.js +0 -92
- package/dist/generate-q2m01zRd.js.map +0 -1
- package/dist/gunshiDryRun-DdKJuVn1.js +0 -2
- package/dist/init-Ckpwkfvz.cjs +0 -58
- package/dist/init-Ckpwkfvz.cjs.map +0 -1
- package/dist/init-DukE9Uw4.js +0 -58
- package/dist/init-DukE9Uw4.js.map +0 -1
- package/dist/login-Bc19oiFE.cjs +0 -11
- package/dist/login-Bc19oiFE.cjs.map +0 -1
- package/dist/login-Do3nns0B.js +0 -11
- package/dist/login-Do3nns0B.js.map +0 -1
- package/dist/logout-BvHFvCeI.cjs +0 -16
- package/dist/logout-BvHFvCeI.cjs.map +0 -1
- package/dist/logout-COxoIMum.js +0 -16
- package/dist/logout-COxoIMum.js.map +0 -1
- package/dist/mcp-B-GxGgVr.js +0 -19
- package/dist/mcp-B-GxGgVr.js.map +0 -1
- package/dist/mcp-B3B-rE7W.cjs +0 -18
- package/dist/mcp-B3B-rE7W.cjs.map +0 -1
- package/dist/output-CVLosNZq.cjs +0 -411
- package/dist/output-CVLosNZq.cjs.map +0 -1
- package/dist/output-FDo-db-w.js +0 -321
- package/dist/output-FDo-db-w.js.map +0 -1
- package/dist/package-Ci2fcoOV.js +0 -6
- package/dist/package-Ci2fcoOV.js.map +0 -1
- package/dist/package-Do9rJ7D3.cjs +0 -11
- package/dist/package-Do9rJ7D3.cjs.map +0 -1
- package/dist/rolldown-runtime-qbf5tadS.cjs +0 -38
- package/dist/run-BDaZPU-1.js.map +0 -1
- package/dist/run-BdTwxLCW.js.map +0 -1
- package/dist/run-C-jBKNbI.cjs.map +0 -1
- package/dist/run-CAcQlhub.js +0 -295
- package/dist/run-CAcQlhub.js.map +0 -1
- package/dist/run-CSoFhDyK.cjs +0 -297
- package/dist/run-CSoFhDyK.cjs.map +0 -1
- package/dist/run-CZz_GbE4.cjs.map +0 -1
- package/dist/run-CaEbo-te.js +0 -40
- package/dist/run-CaEbo-te.js.map +0 -1
- package/dist/run-CihzLZmR.cjs +0 -41
- package/dist/run-CihzLZmR.cjs.map +0 -1
- package/dist/run-Dn8HVjnM.js.map +0 -1
- package/dist/run-y6LRyWdS.cjs.map +0 -1
- package/dist/shared-Cl-OLpNh.cjs +0 -81
- package/dist/shared-Cl-OLpNh.cjs.map +0 -1
- package/dist/shared-DaHA7QFQ.js +0 -58
- package/dist/shared-DaHA7QFQ.js.map +0 -1
- package/dist/snapshot-B5mc4gpC.js.map +0 -1
- package/dist/snapshot-DnCxZfmM.cjs.map +0 -1
- package/dist/start-B-GXO8tS.cjs +0 -12
- package/dist/start-B-GXO8tS.cjs.map +0 -1
- package/dist/start-Dbfx_8zG.js +0 -12
- package/dist/start-Dbfx_8zG.js.map +0 -1
- package/dist/status-DDajNc8w.js +0 -17
- package/dist/status-DDajNc8w.js.map +0 -1
- package/dist/status-DusS7DfO.cjs +0 -17
- package/dist/status-DusS7DfO.cjs.map +0 -1
- package/dist/stop-C5t7Yx0t.js +0 -11
- package/dist/stop-C5t7Yx0t.js.map +0 -1
- package/dist/stop-zmM3A0Re.cjs +0 -11
- package/dist/stop-zmM3A0Re.cjs.map +0 -1
- package/dist/studio--IOOl_VB.js +0 -174
- package/dist/studio--IOOl_VB.js.map +0 -1
- package/dist/studio-CJ2XhtRd.cjs +0 -174
- package/dist/studio-CJ2XhtRd.cjs.map +0 -1
- package/dist/utils-C3HZOWL_.js +0 -1107
- package/dist/utils-C3HZOWL_.js.map +0 -1
- package/dist/utils-ClRbJfxu.cjs +0 -1210
- package/dist/utils-ClRbJfxu.cjs.map +0 -1
- package/dist/validate-BcB0tFQ6.cjs +0 -19
- package/dist/validate-BcB0tFQ6.cjs.map +0 -1
- package/dist/validate-QpkCbIFJ.js +0 -20
- package/dist/validate-QpkCbIFJ.js.map +0 -1
package/dist/utils-C3HZOWL_.js
DELETED
|
@@ -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
|