@kubb/cli 5.12.8 → 5.13.1
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/README.md +1 -0
- package/dist/{Telemetry-B3ls8iy8.cjs → Telemetry-0Sr_THx9.cjs} +2 -2
- package/dist/{Telemetry-B3ls8iy8.cjs.map → Telemetry-0Sr_THx9.cjs.map} +1 -1
- package/dist/{Telemetry-Cwis9NRC.js → Telemetry-B4hk3xGv.js} +2 -2
- package/dist/{Telemetry-Cwis9NRC.js.map → Telemetry-B4hk3xGv.js.map} +1 -1
- package/dist/{background-BuZJ6isn.cjs → background-6xlebN_Y.cjs} +2 -2
- package/dist/{background-BuZJ6isn.cjs.map → background-6xlebN_Y.cjs.map} +1 -1
- package/dist/{background-nDhk-eNv.js → background-B7oEWLZs.js} +1 -1
- package/dist/{background-BpyuKytZ.js → background-DY4LTodc.js} +2 -2
- package/dist/{background-BpyuKytZ.js.map → background-DY4LTodc.js.map} +1 -1
- package/dist/{config-1tiH3heq.cjs → config-BG_Nr2CZ.cjs} +17 -1
- package/dist/config-BG_Nr2CZ.cjs.map +1 -0
- package/dist/{config-CbjbW2J5.js → config-CNqgDSu3.js} +12 -2
- package/dist/config-CNqgDSu3.js.map +1 -0
- package/dist/config-DWDpFSn1.js +1 -0
- package/dist/credentials-CZK6zPLv.js.map +1 -1
- package/dist/credentials-DDaKqtJn.cjs.map +1 -1
- package/dist/index.cjs +1275 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1276 -98
- package/dist/index.js.map +1 -1
- package/dist/{registry-DxHGGUfy.cjs → registry-9AEIs-Cm.cjs} +3 -3
- package/dist/{registry-DxHGGUfy.cjs.map → registry-9AEIs-Cm.cjs.map} +1 -1
- package/dist/{registry-D8Vxs2q6.js → registry-DLasg7du.js} +3 -3
- package/dist/{registry-D8Vxs2q6.js.map → registry-DLasg7du.js.map} +1 -1
- package/dist/{reporters-DMvezNR1.js → reporters-BfeB9jOt.js} +4 -4
- package/dist/{reporters-DMvezNR1.js.map → reporters-BfeB9jOt.js.map} +1 -1
- package/dist/{reporters-BRt7Cmp0.cjs → reporters-xwOZPZ42.cjs} +4 -4
- package/dist/{reporters-BRt7Cmp0.cjs.map → reporters-xwOZPZ42.cjs.map} +1 -1
- package/dist/{run-8K8KvqtO.js → run--OOU4YSR.js} +5 -5
- package/dist/{run-8K8KvqtO.js.map → run--OOU4YSR.js.map} +1 -1
- package/dist/{run-BPi2eh0e.js → run-BDmTZPT7.js} +7 -7
- package/dist/{run-BPi2eh0e.js.map → run-BDmTZPT7.js.map} +1 -1
- package/dist/{run-BFqPilTx.cjs → run-BTzmKup3.cjs} +2 -2
- package/dist/{run-BFqPilTx.cjs.map → run-BTzmKup3.cjs.map} +1 -1
- package/dist/{run-C61yKdb4.cjs → run-Bgr0F6xv.cjs} +5 -5
- package/dist/{run-C61yKdb4.cjs.map → run-Bgr0F6xv.cjs.map} +1 -1
- package/dist/{run-Be6_6_PJ.cjs → run-C2syKtAg.cjs} +7 -7
- package/dist/{run-Be6_6_PJ.cjs.map → run-C2syKtAg.cjs.map} +1 -1
- package/dist/{run-CfkP7AY1.js → run-C9Q5R-p9.js} +1 -1
- package/dist/{run-BJUijhbO.js → run-DoUk-2Xb.js} +2 -2
- package/dist/{run-BJUijhbO.js.map → run-DoUk-2Xb.js.map} +1 -1
- package/dist/{run-DZgSH5YE.js → run-kRFuEqeA.js} +14 -8
- package/dist/run-kRFuEqeA.js.map +1 -0
- package/dist/{run-Bk-PyLaA.cjs → run-w92b045L.cjs} +14 -8
- package/dist/run-w92b045L.cjs.map +1 -0
- package/dist/{snapshot-BApwIb2T.cjs → snapshot-B4eWToMC.cjs} +6 -6
- package/dist/{snapshot-BApwIb2T.cjs.map → snapshot-B4eWToMC.cjs.map} +1 -1
- package/dist/{snapshot-F8ShCYAC.js → snapshot-O36QFuVw.js} +6 -6
- package/dist/{snapshot-F8ShCYAC.js.map → snapshot-O36QFuVw.js.map} +1 -1
- package/dist/{status-D1O1SZi5.js → status-C5_IHiDc.js} +3 -3
- package/dist/{status-D1O1SZi5.js.map → status-C5_IHiDc.js.map} +1 -1
- package/dist/{status-CNvgGbU7.cjs → status-CteZmcug.cjs} +3 -3
- package/dist/{status-CNvgGbU7.cjs.map → status-CteZmcug.cjs.map} +1 -1
- package/dist/studioWorker.js +2 -2
- package/dist/{summary-D6hq3I9C.js → summary-B1aj8T_V.js} +3 -3
- package/dist/{summary-D6hq3I9C.js.map → summary-B1aj8T_V.js.map} +1 -1
- package/dist/{summary-D2g0ii83.cjs → summary-BuKuPfE_.cjs} +3 -3
- package/dist/{summary-D2g0ii83.cjs.map → summary-BuKuPfE_.cjs.map} +1 -1
- package/dist/{text-BBQbTT22.js → text-CaZG0KF2.js} +2 -2
- package/dist/{text-BBQbTT22.js.map → text-CaZG0KF2.js.map} +1 -1
- package/dist/{text-1NAngHDI.cjs → text-DVPYqR74.cjs} +2 -2
- package/dist/{text-1NAngHDI.cjs.map → text-DVPYqR74.cjs.map} +1 -1
- package/dist/{utils-DNOlQ-6B.js → utils--q06lp55.js} +4 -4
- package/dist/{utils-DNOlQ-6B.js.map → utils--q06lp55.js.map} +1 -1
- package/dist/{utils-N757uO9x.cjs → utils-Ckbz012p.cjs} +9 -3
- package/dist/{utils-N757uO9x.cjs.map → utils-Ckbz012p.cjs.map} +1 -1
- package/package.json +11 -10
- package/dist/config-1tiH3heq.cjs.map +0 -1
- package/dist/config-CbjbW2J5.js.map +0 -1
- package/dist/config-cP7k9YwA.js +0 -1
- package/dist/run-Bk-PyLaA.cjs.map +0 -1
- package/dist/run-DZgSH5YE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
|
|
2
2
|
import { t as getErrorMessage } from "./errors-DmC83ALX.js";
|
|
3
|
-
import { a as getElapsedMs, i as formatMs, r as version, t as isDisabled } from "./Telemetry-
|
|
4
|
-
import { _ as write, a as readOutputFiles, c as createSpinner, g as read, l as withSpinner, m as exists, n as diffFiles } from "./utils
|
|
5
|
-
import { _ as isRichOutput, a as logOutro, c as logBlock, d as logStep, f as logWarn, i as logIntro, l as logError, r as isInteractiveRun, t as pluralize } from "./text-
|
|
6
|
-
import { a as installSpec, c as toRegistryId, f as wantsMascot, i as incompatibility, l as toShortId, n as closestIds, o as isOutdated, r as fetchRegistryIndex, s as registryUrl, t as bumpOf, u as getBanner } from "./registry-
|
|
7
|
-
import { a as
|
|
8
|
-
import { t as logCommandSummary } from "./summary-
|
|
3
|
+
import { a as getElapsedMs, i as formatMs, r as version, t as isDisabled } from "./Telemetry-B4hk3xGv.js";
|
|
4
|
+
import { _ as write, a as readOutputFiles, c as createSpinner, g as read, l as withSpinner, m as exists, n as diffFiles, v as writeRaw } from "./utils--q06lp55.js";
|
|
5
|
+
import { _ as isRichOutput, a as logOutro, c as logBlock, d as logStep, f as logWarn, i as logIntro, l as logError, r as isInteractiveRun, t as pluralize } from "./text-CaZG0KF2.js";
|
|
6
|
+
import { a as installSpec, c as toRegistryId, f as wantsMascot, i as incompatibility, l as toShortId, n as closestIds, o as isOutdated, r as fetchRegistryIndex, s as registryUrl, t as bumpOf, u as getBanner } from "./registry-DLasg7du.js";
|
|
7
|
+
import { a as KUBB_PACKAGE_NAME, i as KUBB_CONFIG_FILENAME, n as findConfigFile, o as initDefaults, r as BUNDLED_PACKAGES, t as getConfigs } from "./config-CNqgDSu3.js";
|
|
8
|
+
import { t as logCommandSummary } from "./summary-B1aj8T_V.js";
|
|
9
9
|
import { styleText } from "node:util";
|
|
10
10
|
import dryrun, { pluginId as dryRunId } from "@gunshi/plugin-dryrun";
|
|
11
11
|
import { cli, define, defineWithTypes } from "gunshi";
|
|
@@ -14,7 +14,46 @@ import { x } from "tinyexec";
|
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import * as clack from "@clack/prompts";
|
|
17
|
-
import { Diagnostics, Hookable, createKubb, readInstalledPackages } from "@kubb/core";
|
|
17
|
+
import { Diagnostics, Hookable, createKubb, isPackageName, readInstalledPackages } from "@kubb/core";
|
|
18
|
+
import { parse } from "yuku-parser";
|
|
19
|
+
//#region ../../internals/utils/src/casing.ts
|
|
20
|
+
/**
|
|
21
|
+
* Shared implementation for camelCase and PascalCase conversion.
|
|
22
|
+
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
23
|
+
* and capitalizes each word according to `pascal`.
|
|
24
|
+
*
|
|
25
|
+
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
26
|
+
*/
|
|
27
|
+
function convert(text, pascal) {
|
|
28
|
+
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
29
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
30
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
31
|
+
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
32
|
+
}
|
|
33
|
+
const cache = /* @__PURE__ */ new Map();
|
|
34
|
+
const cacheLimit = 1e4;
|
|
35
|
+
function toCamelOrPascal(text, pascal) {
|
|
36
|
+
const key = `${pascal ? "P" : "c"}${text}`;
|
|
37
|
+
const cached = cache.get(key);
|
|
38
|
+
if (cached !== void 0) return cached;
|
|
39
|
+
const result = convert(text, pascal);
|
|
40
|
+
if (cache.size >= cacheLimit) cache.clear();
|
|
41
|
+
cache.set(key, result);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Converts `text` to camelCase.
|
|
46
|
+
*
|
|
47
|
+
* @example Word boundaries
|
|
48
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
49
|
+
*
|
|
50
|
+
* @example With a prefix
|
|
51
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
52
|
+
*/
|
|
53
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
54
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
18
57
|
//#region src/commands/shared.ts
|
|
19
58
|
const configArg = { config: {
|
|
20
59
|
type: "string",
|
|
@@ -86,6 +125,12 @@ const studioPermissionArgs = {
|
|
|
86
125
|
default: false,
|
|
87
126
|
toKebab: true
|
|
88
127
|
},
|
|
128
|
+
allowRemove: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
description: "Let Studio remove extensions with kubb remove, which edits kubb.config.ts and uninstalls packages; needs --allow-config-edit too. Asked for once per project when omitted",
|
|
131
|
+
default: false,
|
|
132
|
+
toKebab: true
|
|
133
|
+
},
|
|
89
134
|
allowSaveInput: {
|
|
90
135
|
type: "boolean",
|
|
91
136
|
description: "Let Studio save the spec it sends as the input in kubb.config.ts; needs --allow-config-edit too. Asked for once per project when omitted",
|
|
@@ -128,7 +173,7 @@ function resolveLogLevel({ verbose, silent, logLevel }) {
|
|
|
128
173
|
if (silent) return "silent";
|
|
129
174
|
return logLevel;
|
|
130
175
|
}
|
|
131
|
-
const command$
|
|
176
|
+
const command$14 = defineWithTypes()({
|
|
132
177
|
name: "generate",
|
|
133
178
|
rendering: { header: null },
|
|
134
179
|
description: "Generate types, clients, hooks, and schemas from an OpenAPI specification.",
|
|
@@ -179,7 +224,7 @@ const command$13 = defineWithTypes()({
|
|
|
179
224
|
async run(ctx) {
|
|
180
225
|
const { values } = ctx;
|
|
181
226
|
const logLevel = resolveLogLevel(values);
|
|
182
|
-
const { run } = await import("./run-
|
|
227
|
+
const { run } = await import("./run-BDmTZPT7.js");
|
|
183
228
|
await run({
|
|
184
229
|
input: values.input,
|
|
185
230
|
configPath: values.config,
|
|
@@ -193,7 +238,7 @@ const command$13 = defineWithTypes()({
|
|
|
193
238
|
//#endregion
|
|
194
239
|
//#region src/commands/mcp.ts
|
|
195
240
|
/** The runner is imported when the command runs, so listing `kubb --help` never loads `@kubb/mcp`. */
|
|
196
|
-
const command$
|
|
241
|
+
const command$13 = define({
|
|
197
242
|
name: "mcp",
|
|
198
243
|
description: "Start an MCP server for code generation from an AI assistant.",
|
|
199
244
|
examples: [
|
|
@@ -203,7 +248,7 @@ const command$12 = define({
|
|
|
203
248
|
].join("\n"),
|
|
204
249
|
rendering: { header: null },
|
|
205
250
|
async run() {
|
|
206
|
-
const { run } = await import("./run-
|
|
251
|
+
const { run } = await import("./run-DoUk-2Xb.js");
|
|
207
252
|
await run();
|
|
208
253
|
}
|
|
209
254
|
});
|
|
@@ -218,14 +263,14 @@ function studioCommand({ action, block, ...definition }) {
|
|
|
218
263
|
...definition,
|
|
219
264
|
toKebab: true,
|
|
220
265
|
async run({ values }) {
|
|
221
|
-
const { runStudio } = await import("./run-
|
|
266
|
+
const { runStudio } = await import("./run-C9Q5R-p9.js");
|
|
222
267
|
await runStudio(values, action, { block });
|
|
223
268
|
}
|
|
224
269
|
});
|
|
225
270
|
}
|
|
226
271
|
//#endregion
|
|
227
272
|
//#region src/commands/studio/connect.ts
|
|
228
|
-
const command$
|
|
273
|
+
const command$12 = studioCommand({
|
|
229
274
|
name: "connect",
|
|
230
275
|
description: "Connect this project to Kubb Studio and generate from the browser.",
|
|
231
276
|
examples: [
|
|
@@ -248,31 +293,31 @@ const command$11 = studioCommand({
|
|
|
248
293
|
},
|
|
249
294
|
block: true,
|
|
250
295
|
async action(options) {
|
|
251
|
-
const { connect } = await import("./run-
|
|
296
|
+
const { connect } = await import("./run-C9Q5R-p9.js");
|
|
252
297
|
await connect(options);
|
|
253
298
|
}
|
|
254
299
|
});
|
|
255
300
|
//#endregion
|
|
256
301
|
//#region src/commands/studio/login.ts
|
|
257
|
-
const command$
|
|
302
|
+
const command$11 = studioCommand({
|
|
258
303
|
name: "login",
|
|
259
304
|
description: "Pair this machine with Kubb Studio without connecting.",
|
|
260
305
|
examples: ["kubb studio login", "kubb studio login --url http://localhost:3000"].join("\n"),
|
|
261
|
-
args: command$
|
|
306
|
+
args: command$12.args,
|
|
262
307
|
async action(options) {
|
|
263
|
-
const { login } = await import("./run-
|
|
308
|
+
const { login } = await import("./run-C9Q5R-p9.js");
|
|
264
309
|
await login(options);
|
|
265
310
|
}
|
|
266
311
|
});
|
|
267
312
|
//#endregion
|
|
268
313
|
//#region src/commands/studio/logout.ts
|
|
269
|
-
const command$
|
|
314
|
+
const command$10 = studioCommand({
|
|
270
315
|
name: "logout",
|
|
271
316
|
description: "Forget the stored Kubb Studio token.",
|
|
272
317
|
examples: "kubb studio logout",
|
|
273
318
|
args: {},
|
|
274
319
|
async action() {
|
|
275
|
-
const { stopWorker } = await import("./background-
|
|
320
|
+
const { stopWorker } = await import("./background-B7oEWLZs.js");
|
|
276
321
|
const { clearCredentials } = await import("./credentials-C8tjAhTo.js");
|
|
277
322
|
await stopWorker();
|
|
278
323
|
await clearCredentials();
|
|
@@ -281,7 +326,7 @@ const command$9 = studioCommand({
|
|
|
281
326
|
});
|
|
282
327
|
//#endregion
|
|
283
328
|
//#region src/commands/studio/snapshot.ts
|
|
284
|
-
const command$
|
|
329
|
+
const command$9 = define({
|
|
285
330
|
name: "snapshot",
|
|
286
331
|
description: "Generate and publish a Kubb Studio snapshot from CI.",
|
|
287
332
|
examples: ["kubb studio snapshot", "kubb studio snapshot --json"].join("\n"),
|
|
@@ -315,18 +360,18 @@ const command$8 = define({
|
|
|
315
360
|
...logLevelArg
|
|
316
361
|
},
|
|
317
362
|
async run(ctx) {
|
|
318
|
-
const { runner } = await import("./snapshot-
|
|
363
|
+
const { runner } = await import("./snapshot-O36QFuVw.js");
|
|
319
364
|
await runner(ctx);
|
|
320
365
|
}
|
|
321
366
|
});
|
|
322
367
|
//#endregion
|
|
323
368
|
//#region src/commands/studio/background.ts
|
|
324
|
-
const command$
|
|
369
|
+
const command$8 = studioCommand({
|
|
325
370
|
name: "start",
|
|
326
371
|
description: "Keep this project connected to Kubb Studio in the background.",
|
|
327
|
-
args: command$
|
|
372
|
+
args: command$12.args,
|
|
328
373
|
async action(options) {
|
|
329
|
-
const { startWorker } = await import("./background-
|
|
374
|
+
const { startWorker } = await import("./background-B7oEWLZs.js");
|
|
330
375
|
await startWorker(options);
|
|
331
376
|
}
|
|
332
377
|
});
|
|
@@ -334,22 +379,22 @@ const stopCommand = define({
|
|
|
334
379
|
name: "stop",
|
|
335
380
|
description: "Stop this project's background connection and keep its pairing.",
|
|
336
381
|
async run() {
|
|
337
|
-
const { stopWorker } = await import("./background-
|
|
382
|
+
const { stopWorker } = await import("./background-B7oEWLZs.js");
|
|
338
383
|
await stopWorker();
|
|
339
384
|
console.log("Background connection stopped.");
|
|
340
385
|
}
|
|
341
386
|
});
|
|
342
387
|
//#endregion
|
|
343
388
|
//#region src/commands/studio/status.ts
|
|
344
|
-
const command$
|
|
389
|
+
const command$7 = studioCommand({
|
|
345
390
|
name: "status",
|
|
346
391
|
description: "Show the project worker, Kubb Studio pairing, and saved permissions.",
|
|
347
392
|
examples: "kubb studio status",
|
|
348
393
|
args: { url: studioConnectionArgs.url },
|
|
349
394
|
async action(options) {
|
|
350
|
-
const { getWorkerLogPath, getWorkerStatus } = await import("./background-
|
|
395
|
+
const { getWorkerLogPath, getWorkerStatus } = await import("./background-B7oEWLZs.js");
|
|
351
396
|
const { readCredentials } = await import("./credentials-C8tjAhTo.js");
|
|
352
|
-
const { formatStatus } = await import("./status-
|
|
397
|
+
const { formatStatus } = await import("./status-C5_IHiDc.js");
|
|
353
398
|
const [worker, credentials] = await Promise.all([getWorkerStatus(), readCredentials()]);
|
|
354
399
|
console.log([
|
|
355
400
|
"",
|
|
@@ -366,8 +411,8 @@ const command$6 = studioCommand({
|
|
|
366
411
|
});
|
|
367
412
|
//#endregion
|
|
368
413
|
//#region src/commands/studio/index.ts
|
|
369
|
-
const command$
|
|
370
|
-
...command$
|
|
414
|
+
const command$6 = define({
|
|
415
|
+
...command$12,
|
|
371
416
|
name: "studio",
|
|
372
417
|
description: "Connect this project to Kubb Studio, or manage its pairing and snapshots.",
|
|
373
418
|
examples: [
|
|
@@ -379,13 +424,13 @@ const command$5 = define({
|
|
|
379
424
|
"kubb studio snapshot"
|
|
380
425
|
].join("\n"),
|
|
381
426
|
subCommands: {
|
|
382
|
-
start: command$
|
|
427
|
+
start: command$8,
|
|
383
428
|
stop: stopCommand,
|
|
384
|
-
connect: command$
|
|
385
|
-
login: command$
|
|
386
|
-
logout: command$
|
|
387
|
-
status: command$
|
|
388
|
-
snapshot: command$
|
|
429
|
+
connect: command$12,
|
|
430
|
+
login: command$11,
|
|
431
|
+
logout: command$10,
|
|
432
|
+
status: command$7,
|
|
433
|
+
snapshot: command$9
|
|
389
434
|
}
|
|
390
435
|
});
|
|
391
436
|
/**
|
|
@@ -404,7 +449,7 @@ function routeStudioFlags(args) {
|
|
|
404
449
|
//#endregion
|
|
405
450
|
//#region src/commands/validate.ts
|
|
406
451
|
/** The runner is imported when the command runs, so listing `kubb --help` never loads `@kubb/adapter-oas`. */
|
|
407
|
-
const command$
|
|
452
|
+
const command$5 = define({
|
|
408
453
|
name: "validate",
|
|
409
454
|
description: "Check a specification with the configured adapter before generating.",
|
|
410
455
|
examples: ["kubb validate ./openapi.yaml", "kubb validate https://petstore3.swagger.io/api/v3/openapi.json"].join("\n"),
|
|
@@ -417,7 +462,7 @@ const command$4 = define({
|
|
|
417
462
|
}
|
|
418
463
|
},
|
|
419
464
|
async run({ values }) {
|
|
420
|
-
const { run } = await import("./run
|
|
465
|
+
const { run } = await import("./run--OOU4YSR.js");
|
|
421
466
|
await run({
|
|
422
467
|
input: values.input,
|
|
423
468
|
configPath: values.config
|
|
@@ -474,6 +519,959 @@ export default defineConfig({
|
|
|
474
519
|
`;
|
|
475
520
|
}
|
|
476
521
|
//#endregion
|
|
522
|
+
//#region ../../internals/shared/src/configFile/ast.ts
|
|
523
|
+
/** Node types the config editor reads from the yuku AST. */
|
|
524
|
+
const nodeTypes = {
|
|
525
|
+
arrayExpression: "ArrayExpression",
|
|
526
|
+
arrowFunctionExpression: "ArrowFunctionExpression",
|
|
527
|
+
assignmentExpression: "AssignmentExpression",
|
|
528
|
+
blockStatement: "BlockStatement",
|
|
529
|
+
callExpression: "CallExpression",
|
|
530
|
+
exportDefaultDeclaration: "ExportDefaultDeclaration",
|
|
531
|
+
expressionStatement: "ExpressionStatement",
|
|
532
|
+
functionExpression: "FunctionExpression",
|
|
533
|
+
identifier: "Identifier",
|
|
534
|
+
importDeclaration: "ImportDeclaration",
|
|
535
|
+
importSpecifier: "ImportSpecifier",
|
|
536
|
+
literal: "Literal",
|
|
537
|
+
memberExpression: "MemberExpression",
|
|
538
|
+
objectExpression: "ObjectExpression",
|
|
539
|
+
objectPattern: "ObjectPattern",
|
|
540
|
+
property: "Property",
|
|
541
|
+
returnStatement: "ReturnStatement",
|
|
542
|
+
spreadElement: "SpreadElement",
|
|
543
|
+
tSAsExpression: "TSAsExpression",
|
|
544
|
+
tSSatisfiesExpression: "TSSatisfiesExpression",
|
|
545
|
+
templateLiteral: "TemplateLiteral",
|
|
546
|
+
unaryExpression: "UnaryExpression",
|
|
547
|
+
variableDeclaration: "VariableDeclaration"
|
|
548
|
+
};
|
|
549
|
+
const commentTypes = { line: "Line" };
|
|
550
|
+
const propertyKinds = { init: "init" };
|
|
551
|
+
const unaryOperators = {
|
|
552
|
+
minus: "-",
|
|
553
|
+
plus: "+"
|
|
554
|
+
};
|
|
555
|
+
const diagnosticSeverities = { error: "error" };
|
|
556
|
+
/**
|
|
557
|
+
* Parses `source` as a TypeScript module. Never throws: a syntax error, or a native parser that
|
|
558
|
+
* cannot load on this platform, comes back as a `reason`.
|
|
559
|
+
*/
|
|
560
|
+
function parseSource(source) {
|
|
561
|
+
try {
|
|
562
|
+
const { program, comments, diagnostics } = parse(source, {
|
|
563
|
+
lang: "ts",
|
|
564
|
+
sourceType: "module",
|
|
565
|
+
preserveParens: false
|
|
566
|
+
});
|
|
567
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === diagnosticSeverities.error)) return { reason: "the config file could not be parsed" };
|
|
568
|
+
return {
|
|
569
|
+
program,
|
|
570
|
+
comments
|
|
571
|
+
};
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return { reason: `the config parser could not load on this platform: ${getErrorMessage(error)}` };
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/** Parses `source` as one expression, such as a commented-out `pluginTs({ ... })` call. */
|
|
577
|
+
function parseExpression(source) {
|
|
578
|
+
const parsed = parseSource(source);
|
|
579
|
+
if ("reason" in parsed) return void 0;
|
|
580
|
+
const [statement] = parsed.program.body;
|
|
581
|
+
return parsed.program.body.length === 1 && statement?.type === nodeTypes.expressionStatement ? statement.expression : void 0;
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region ../../internals/shared/src/configFile/types.ts
|
|
585
|
+
const extensionKinds = {
|
|
586
|
+
plugin: "plugin",
|
|
587
|
+
adapter: "adapter",
|
|
588
|
+
parser: "parser"
|
|
589
|
+
};
|
|
590
|
+
const ConfigEditOperation = {
|
|
591
|
+
SET_OPTION: "set-option",
|
|
592
|
+
REMOVE_OPTION: "remove-option",
|
|
593
|
+
ADD_PLUGIN: "add-plugin",
|
|
594
|
+
REMOVE_EXTENSION: "remove-extension",
|
|
595
|
+
DISABLE_PLUGIN: "disable-plugin",
|
|
596
|
+
ENABLE_PLUGIN: "enable-plugin",
|
|
597
|
+
SET_INPUT: "set-input",
|
|
598
|
+
SET_OUTPUT: "set-output",
|
|
599
|
+
REMOVE_OUTPUT: "remove-output"
|
|
600
|
+
};
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region ../../internals/shared/src/configFile/rules.ts
|
|
603
|
+
/** `@kubb/plugin-ts` becomes `plugin-ts`, the `name` Kubb plugin factories use. */
|
|
604
|
+
function toPluginName(packageName) {
|
|
605
|
+
return packageName.split("/").pop() ?? packageName;
|
|
606
|
+
}
|
|
607
|
+
/** `@kubb/plugin-react-query` becomes `pluginReactQuery`, the export a plugin package uses by convention. */
|
|
608
|
+
function toExportName(packageName) {
|
|
609
|
+
return camelCase(toPluginName(packageName));
|
|
610
|
+
}
|
|
611
|
+
const KUBB_PLUGIN_SPECIFIER = /^@kubb\/plugin-[\w.-]+$/;
|
|
612
|
+
/** Only a `@kubb/plugin-*` package or an installed extension of `kind` may be imported, never a path or URL. */
|
|
613
|
+
function isAllowedPlugin({ packageName, extensions, kind = extensionKinds.plugin }) {
|
|
614
|
+
if (!isPackageName(packageName)) return false;
|
|
615
|
+
return kind === extensionKinds.plugin && KUBB_PLUGIN_SPECIFIER.test(packageName) || extensions.get(packageName)?.manifest.kind === kind;
|
|
616
|
+
}
|
|
617
|
+
const UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
618
|
+
"__proto__",
|
|
619
|
+
"constructor",
|
|
620
|
+
"prototype"
|
|
621
|
+
]);
|
|
622
|
+
/** Whether `key` can be an option key without reaching the object's prototype. */
|
|
623
|
+
function isSafeKey(key) {
|
|
624
|
+
return !UNSAFE_KEYS.has(key);
|
|
625
|
+
}
|
|
626
|
+
function isPlainObject(value) {
|
|
627
|
+
if (typeof value !== "object" || value === null) return false;
|
|
628
|
+
const prototype = Object.getPrototypeOf(value);
|
|
629
|
+
return prototype === null || prototype === Object.prototype;
|
|
630
|
+
}
|
|
631
|
+
/** Whether `value` survives a JSON round trip, so an edit never writes a lossy copy into the file. */
|
|
632
|
+
function isOptionValue(value) {
|
|
633
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
634
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
635
|
+
if (Array.isArray(value)) return value.every(isOptionValue);
|
|
636
|
+
if (isPlainObject(value)) return Object.entries(value).every(([key, entry]) => isSafeKey(key) && isOptionValue(entry));
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
/** The root `output` members Studio may set, with the values each takes. */
|
|
640
|
+
const OUTPUT_VALUES = {
|
|
641
|
+
barrel: (value) => value === false || isPlainObject(value) && Object.keys(value).length === 1 && (value.type === "named" || value.type === "all"),
|
|
642
|
+
format: (value) => value === false || [
|
|
643
|
+
"auto",
|
|
644
|
+
"prettier",
|
|
645
|
+
"biome",
|
|
646
|
+
"oxfmt"
|
|
647
|
+
].includes(value),
|
|
648
|
+
lint: (value) => value === false || [
|
|
649
|
+
"auto",
|
|
650
|
+
"eslint",
|
|
651
|
+
"biome",
|
|
652
|
+
"oxlint"
|
|
653
|
+
].includes(value),
|
|
654
|
+
clean: (value) => typeof value === "boolean",
|
|
655
|
+
defaultBanner: (value) => value === false || value === "simple" || value === "full"
|
|
656
|
+
};
|
|
657
|
+
function isOutputKey(key) {
|
|
658
|
+
return Object.hasOwn(OUTPUT_VALUES, key);
|
|
659
|
+
}
|
|
660
|
+
function isOutputValue({ key, value }) {
|
|
661
|
+
return isOutputKey(key) && OUTPUT_VALUES[key](value);
|
|
662
|
+
}
|
|
663
|
+
function pickOutput(output) {
|
|
664
|
+
return Object.fromEntries(Object.entries(output).filter(([key]) => isOutputKey(key)));
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region ../../internals/shared/src/configFile/text.ts
|
|
668
|
+
/**
|
|
669
|
+
* A valid JavaScript identifier, so an import name can only ever print as `import { name } from`,
|
|
670
|
+
* never as source that breaks out of the import statement.
|
|
671
|
+
*/
|
|
672
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/;
|
|
673
|
+
const quotes = {
|
|
674
|
+
single: `'`,
|
|
675
|
+
double: `"`
|
|
676
|
+
};
|
|
677
|
+
/** `source` with the text between `start` and `end` replaced by `text`. */
|
|
678
|
+
function replaceText(source, start, end, text = "") {
|
|
679
|
+
return `${source.slice(0, start)}${text}${source.slice(end)}`;
|
|
680
|
+
}
|
|
681
|
+
/** The 1-based line and 0-based column of `offset` in `source`. */
|
|
682
|
+
function getPosition(source, offset) {
|
|
683
|
+
return {
|
|
684
|
+
line: source.slice(0, offset).split("\n").length,
|
|
685
|
+
column: offset - source.lastIndexOf("\n", offset - 1) - 1
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/** Writes `value` as a single-line literal with the file's quote style. */
|
|
689
|
+
function printValue(value, quote) {
|
|
690
|
+
if (typeof value === "string") {
|
|
691
|
+
const escaped = JSON.stringify(value).slice(1, -1);
|
|
692
|
+
return quote === quotes.double ? `"${escaped}"` : `'${escaped.replaceAll(`\\"`, `"`).replaceAll(`'`, `\\'`)}'`;
|
|
693
|
+
}
|
|
694
|
+
if (value === null || typeof value !== "object") return String(value);
|
|
695
|
+
if (Array.isArray(value)) return `[${value.map((item) => printValue(item, quote)).join(", ")}]`;
|
|
696
|
+
const entries = Object.entries(value);
|
|
697
|
+
return entries.length ? `{ ${entries.map(([key, item]) => `${printKey(key, quote)}: ${printValue(item, quote)}`).join(", ")} }` : "{}";
|
|
698
|
+
}
|
|
699
|
+
/** An object key as source text: bare when it is an identifier, quoted otherwise. */
|
|
700
|
+
function printKey(key, quote) {
|
|
701
|
+
return IDENTIFIER_PATTERN.test(key) ? key : printValue(key, quote);
|
|
702
|
+
}
|
|
703
|
+
/** `value` wrapped in one object per key of `path`: `wrapPath(['enum', 'type'], 'enum')` is `{ enum: { type: 'enum' } }`. */
|
|
704
|
+
function wrapPath(path, value) {
|
|
705
|
+
return path.reduceRight((inner, key) => ({ [key]: inner }), value);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Adds `text` as the last item of an object or array literal, following its layout: on a line of
|
|
709
|
+
* its own with the last item's indentation when the literal spans lines, inline otherwise.
|
|
710
|
+
*/
|
|
711
|
+
function appendItem(source, container, text) {
|
|
712
|
+
const { start, end } = container;
|
|
713
|
+
const last = (container.type === nodeTypes.objectExpression ? container.properties : container.elements).filter((item) => item !== null).at(-1);
|
|
714
|
+
if (!last) {
|
|
715
|
+
const inner = source.slice(start + 1, end - 1);
|
|
716
|
+
if (inner.trim()) return replaceText(source, start + 1, start + 1, /^\s/.test(inner) ? `${text},` : `${text}, `);
|
|
717
|
+
return replaceText(source, start, end, container.type === nodeTypes.objectExpression ? `{ ${text} }` : `[${text}]`);
|
|
718
|
+
}
|
|
719
|
+
const between = source.slice(last.end, end - 1);
|
|
720
|
+
if (!between.includes("\n")) return replaceText(source, last.end, last.end, `, ${text}`);
|
|
721
|
+
const lineStart = source.lastIndexOf("\n", last.start - 1) + 1;
|
|
722
|
+
const indent = source.slice(lineStart).match(/^[ \t]*/)?.[0] ?? "";
|
|
723
|
+
const lineEnd = source.indexOf("\n", last.end);
|
|
724
|
+
const comma = /^\s*,/.test(between);
|
|
725
|
+
return `${source.slice(0, last.end)}${comma ? "" : ","}${source.slice(last.end, lineEnd)}\n${indent}${text}${comma ? "," : ""}${source.slice(lineEnd)}`;
|
|
726
|
+
}
|
|
727
|
+
/** Drops the item at `index` from an object or array literal, with its own lines when it sits on them alone. */
|
|
728
|
+
function removeItem(source, node, index) {
|
|
729
|
+
const items = node.type === nodeTypes.objectExpression ? node.properties : node.elements.filter((element) => element !== null);
|
|
730
|
+
if (items.length === 1) return replaceText(source, node.start, node.end, node.type === nodeTypes.objectExpression ? "{}" : "[]");
|
|
731
|
+
const { start, end } = items[index];
|
|
732
|
+
const lineStart = source.lastIndexOf("\n", start - 1) + 1;
|
|
733
|
+
const nextNewline = source.indexOf("\n", end);
|
|
734
|
+
const lineEnd = nextNewline === -1 ? source.length : nextNewline;
|
|
735
|
+
if (!source.slice(lineStart, start).trim() && /^\s*,?\s*(\/\/.*)?$/.test(source.slice(end, lineEnd))) return replaceText(source, lineStart, Math.min(lineEnd + 1, source.length));
|
|
736
|
+
const next = items[index + 1];
|
|
737
|
+
return next ? replaceText(source, start, next.start) : replaceText(source, items[index - 1].end, end);
|
|
738
|
+
}
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region ../../internals/shared/src/configFile/configFile.ts
|
|
741
|
+
/** The marker `disable-plugin` writes, with the block's line count so `enable-plugin` restores exactly those lines. */
|
|
742
|
+
const DISABLED_MARKER_PATTERN = /^kubb:disabled (.+)\s+(\d+)$/;
|
|
743
|
+
const OPERATIONS = new Set(Object.values(ConfigEditOperation));
|
|
744
|
+
const OPTION_OPERATIONS = /* @__PURE__ */ new Set([ConfigEditOperation.SET_OPTION, ConfigEditOperation.REMOVE_OPTION]);
|
|
745
|
+
const OUTPUT_OPERATIONS = /* @__PURE__ */ new Set([ConfigEditOperation.SET_OUTPUT, ConfigEditOperation.REMOVE_OUTPUT]);
|
|
746
|
+
const KINDS = new Set(Object.values(extensionKinds));
|
|
747
|
+
/** Identifiers the editor matches by name. */
|
|
748
|
+
const identifiers = {
|
|
749
|
+
defineConfig: "defineConfig",
|
|
750
|
+
require: "require",
|
|
751
|
+
module: "module",
|
|
752
|
+
exports: "exports"
|
|
753
|
+
};
|
|
754
|
+
const moduleKinds = {
|
|
755
|
+
esm: "esm",
|
|
756
|
+
cjs: "cjs"
|
|
757
|
+
};
|
|
758
|
+
function isNonEmptyString(value) {
|
|
759
|
+
return typeof value === "string" && value !== "";
|
|
760
|
+
}
|
|
761
|
+
/** Edits cross the wire, so their shape is checked before anything reads them. */
|
|
762
|
+
function validateEdit(edit) {
|
|
763
|
+
const { operation, plugin, config, path, kind, input } = typeof edit === "object" && edit !== null ? edit : {};
|
|
764
|
+
if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
|
|
765
|
+
if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
|
|
766
|
+
if (operation === ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() !== "" ? void 0 : "the edit names no input";
|
|
767
|
+
const isOption = OPTION_OPERATIONS.has(operation);
|
|
768
|
+
const isOutput = OUTPUT_OPERATIONS.has(operation);
|
|
769
|
+
if (!isOutput && !isNonEmptyString(plugin)) return "the edit names no plugin";
|
|
770
|
+
if (kind !== void 0) {
|
|
771
|
+
if (typeof kind !== "string" || !KINDS.has(kind)) return `unknown kind ${JSON.stringify(kind)}`;
|
|
772
|
+
const takesKind = operation === ConfigEditOperation.ADD_PLUGIN || operation === ConfigEditOperation.REMOVE_EXTENSION;
|
|
773
|
+
if (takesKind && kind === extensionKinds.adapter) return `${operation} takes a plugin or a parser, since a config has one adapter`;
|
|
774
|
+
if (!isOption && !takesKind) return `${operation} edits the plugins array, so it takes no kind`;
|
|
775
|
+
}
|
|
776
|
+
if (!isOption && !isOutput) return void 0;
|
|
777
|
+
if (!Array.isArray(path) || !path.every(isNonEmptyString)) return "path must be a list of option names";
|
|
778
|
+
if (path.length === 0) return "no option path given";
|
|
779
|
+
const unsafe = path.find((key) => !isSafeKey(key));
|
|
780
|
+
if (unsafe !== void 0) return `${unsafe} cannot be used as an option name`;
|
|
781
|
+
if (isOutput && !isOutputKey(path[0])) return `output.${path[0]} can only be changed in the config file`;
|
|
782
|
+
}
|
|
783
|
+
/** Steps through `as`/`satisfies` and `() => ...` wrappers to the config object. */
|
|
784
|
+
function unwrapValue(node) {
|
|
785
|
+
if (!node) return void 0;
|
|
786
|
+
if (node.type === nodeTypes.tSAsExpression || node.type === nodeTypes.tSSatisfiesExpression) return unwrapValue(node.expression);
|
|
787
|
+
if (node.type !== nodeTypes.arrowFunctionExpression && node.type !== nodeTypes.functionExpression) return node;
|
|
788
|
+
if (node.body?.type !== nodeTypes.blockStatement) return unwrapValue(node.body);
|
|
789
|
+
const returned = node.body.body.find((statement) => statement.type === nodeTypes.returnStatement);
|
|
790
|
+
return unwrapValue(returned?.type === nodeTypes.returnStatement ? returned.argument : void 0);
|
|
791
|
+
}
|
|
792
|
+
function isModuleExports(node) {
|
|
793
|
+
return node.type === nodeTypes.memberExpression && !node.computed && node.object.type === nodeTypes.identifier && node.object.name === identifiers.module && node.property.type === nodeTypes.identifier && node.property.name === identifiers.exports;
|
|
794
|
+
}
|
|
795
|
+
/** The exported config value and how the file exports it. */
|
|
796
|
+
function findExport(program) {
|
|
797
|
+
for (const node of program.body) {
|
|
798
|
+
if (node.type === nodeTypes.exportDefaultDeclaration) return {
|
|
799
|
+
exported: node.declaration,
|
|
800
|
+
kind: moduleKinds.esm
|
|
801
|
+
};
|
|
802
|
+
if (node.type === nodeTypes.expressionStatement && node.expression.type === nodeTypes.assignmentExpression && isModuleExports(node.expression.left)) return {
|
|
803
|
+
exported: node.expression.right,
|
|
804
|
+
kind: moduleKinds.cjs
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
exported: void 0,
|
|
809
|
+
kind: moduleKinds.esm
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
/** Every config object in `export default` or `module.exports = defineConfig(...)`, or why the file is unmanaged. */
|
|
813
|
+
function findConfigs(source) {
|
|
814
|
+
const parsed = parseSource(source);
|
|
815
|
+
if ("reason" in parsed) return parsed;
|
|
816
|
+
const { exported, kind } = findExport(parsed.program);
|
|
817
|
+
const value = unwrapValue(exported);
|
|
818
|
+
if (!value) return { reason: "no `export default` or `module.exports` found" };
|
|
819
|
+
if (value.type !== nodeTypes.callExpression || value.callee.type !== nodeTypes.identifier || value.callee.name !== identifiers.defineConfig) return { reason: "the exported config is not a defineConfig(...) call" };
|
|
820
|
+
const argument = unwrapValue(value.arguments[0]);
|
|
821
|
+
if (!argument) return { reason: "defineConfig(...) was called without a config" };
|
|
822
|
+
const entries = argument.type === nodeTypes.arrayExpression ? argument.elements.map((element) => unwrapValue(element)) : [argument];
|
|
823
|
+
const configs = entries.filter((entry) => entry?.type === nodeTypes.objectExpression);
|
|
824
|
+
if (configs.length !== entries.length) return { reason: "config is not an object literal" };
|
|
825
|
+
return {
|
|
826
|
+
...parsed,
|
|
827
|
+
configs,
|
|
828
|
+
kind
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function findConfigEntry(configs, ref) {
|
|
832
|
+
return typeof ref === "string" ? configs.find((entry) => getConfigName(entry) === ref) : configs[ref ?? 0];
|
|
833
|
+
}
|
|
834
|
+
function getKey(entry) {
|
|
835
|
+
if (entry.type !== nodeTypes.property || entry.computed) return void 0;
|
|
836
|
+
if (entry.key.type === nodeTypes.identifier) return entry.key.name;
|
|
837
|
+
return entry.key.type === nodeTypes.literal && typeof entry.key.value === "string" ? entry.key.value : void 0;
|
|
838
|
+
}
|
|
839
|
+
/** The last one wins when the key repeats, as at runtime. */
|
|
840
|
+
function findProperty(node, key) {
|
|
841
|
+
const index = node.properties.findLastIndex((entry) => getKey(entry) === key);
|
|
842
|
+
return index === -1 ? void 0 : {
|
|
843
|
+
member: node.properties[index],
|
|
844
|
+
index
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function getValue(node, key) {
|
|
848
|
+
const member = findProperty(node, key)?.member;
|
|
849
|
+
return member?.type === nodeTypes.property ? member.value : member;
|
|
850
|
+
}
|
|
851
|
+
function getConfigName(config) {
|
|
852
|
+
const name = getValue(config, "name");
|
|
853
|
+
return name?.type === nodeTypes.literal && typeof name.value === "string" ? name.value : void 0;
|
|
854
|
+
}
|
|
855
|
+
function isOverridden(node, index) {
|
|
856
|
+
return node.properties.slice(index + 1).some((entry) => entry.type === nodeTypes.spreadElement);
|
|
857
|
+
}
|
|
858
|
+
/** `key: value`, not a method, getter, setter, or `{ key }` shorthand. */
|
|
859
|
+
function isPlainProperty(member) {
|
|
860
|
+
return !member.method && !member.shorthand && member.kind === propertyKinds.init;
|
|
861
|
+
}
|
|
862
|
+
/** A literal's value, `undefined` for code. */
|
|
863
|
+
function readLiteral(node) {
|
|
864
|
+
switch (node?.type) {
|
|
865
|
+
case nodeTypes.literal: return "regex" in node || "bigint" in node ? void 0 : node.value;
|
|
866
|
+
case nodeTypes.templateLiteral: return node.expressions.length ? void 0 : node.quasis[0]?.value.cooked ?? "";
|
|
867
|
+
case nodeTypes.unaryExpression: {
|
|
868
|
+
const value = readLiteral(node.argument);
|
|
869
|
+
if (typeof value !== "number") return void 0;
|
|
870
|
+
if (node.operator === unaryOperators.minus) return -value;
|
|
871
|
+
if (node.operator === unaryOperators.plus) return value;
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
case nodeTypes.arrayExpression: {
|
|
875
|
+
const values = node.elements.map((element) => readLiteral(element));
|
|
876
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
877
|
+
}
|
|
878
|
+
case nodeTypes.objectExpression: {
|
|
879
|
+
const entries = {};
|
|
880
|
+
for (const entry of node.properties) {
|
|
881
|
+
if (entry.type !== nodeTypes.property) return void 0;
|
|
882
|
+
const key = getKey(entry);
|
|
883
|
+
const value = readLiteral(entry.value);
|
|
884
|
+
if (key === void 0 || !isSafeKey(key) || value === void 0) return void 0;
|
|
885
|
+
entries[key] = value;
|
|
886
|
+
}
|
|
887
|
+
return entries;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function getRequirePath(node) {
|
|
892
|
+
if (node?.type !== nodeTypes.callExpression || node.callee.type !== nodeTypes.identifier || node.callee.name !== identifiers.require) return void 0;
|
|
893
|
+
const [argument] = node.arguments;
|
|
894
|
+
return argument?.type === nodeTypes.literal && typeof argument.value === "string" ? argument.value : void 0;
|
|
895
|
+
}
|
|
896
|
+
/** `import { exported as local } from '...'`. A default or namespace import has no exported name, so its local name stands in. */
|
|
897
|
+
function importBindings(statement) {
|
|
898
|
+
return statement.specifiers.map((specifier) => {
|
|
899
|
+
const imported = specifier.type === nodeTypes.importSpecifier ? specifier.imported : specifier.local;
|
|
900
|
+
const exported = imported.type === nodeTypes.identifier ? imported.name : imported.value;
|
|
901
|
+
return [specifier.local.name, {
|
|
902
|
+
from: statement.source.value,
|
|
903
|
+
exported
|
|
904
|
+
}];
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/** `const { exported: local } = require('...')`. */
|
|
908
|
+
function requireBindings(statement) {
|
|
909
|
+
return statement.declarations.flatMap((declarator) => {
|
|
910
|
+
const from = getRequirePath(declarator.init);
|
|
911
|
+
if (from === void 0 || declarator.id.type !== nodeTypes.objectPattern) return [];
|
|
912
|
+
return declarator.id.properties.flatMap((entry) => {
|
|
913
|
+
if (entry.type !== nodeTypes.property || entry.value.type !== nodeTypes.identifier) return [];
|
|
914
|
+
return [[entry.value.name, {
|
|
915
|
+
from,
|
|
916
|
+
exported: getKey(entry) ?? entry.value.name
|
|
917
|
+
}]];
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
function getImports(program) {
|
|
922
|
+
return new Map(program.body.flatMap((statement) => {
|
|
923
|
+
if (statement.type === nodeTypes.importDeclaration) return importBindings(statement);
|
|
924
|
+
if (statement.type === nodeTypes.variableDeclaration) return requireBindings(statement);
|
|
925
|
+
return [];
|
|
926
|
+
}));
|
|
927
|
+
}
|
|
928
|
+
function isImportStatement(node, kind) {
|
|
929
|
+
if (kind === moduleKinds.esm) return node.type === nodeTypes.importDeclaration;
|
|
930
|
+
return node.type === nodeTypes.variableDeclaration && node.declarations.some((declarator) => getRequirePath(declarator.init) !== void 0);
|
|
931
|
+
}
|
|
932
|
+
/** The quote of the first string literal in the file, so new code matches it. */
|
|
933
|
+
function detectQuote(node) {
|
|
934
|
+
if (!node || typeof node !== "object") return void 0;
|
|
935
|
+
const { type, raw } = node;
|
|
936
|
+
if (type === nodeTypes.literal && typeof raw === "string" && (raw[0] === quotes.single || raw[0] === quotes.double)) return raw[0];
|
|
937
|
+
for (const child of Object.values(node)) {
|
|
938
|
+
const quote = detectQuote(child);
|
|
939
|
+
if (quote) return quote;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/** The `adapter` value, or the elements of the `plugins` or `parsers` array. */
|
|
943
|
+
function factoryNodes(config, kind) {
|
|
944
|
+
if (kind === extensionKinds.adapter) return [getValue(config, "adapter")];
|
|
945
|
+
const array = getValue(config, `${kind}s`);
|
|
946
|
+
return array?.type === nodeTypes.arrayExpression ? array.elements : [];
|
|
947
|
+
}
|
|
948
|
+
/** The factory calls of one kind whose callee the file imports. */
|
|
949
|
+
function findCalls(program, config, kind = extensionKinds.plugin) {
|
|
950
|
+
const imports = getImports(program);
|
|
951
|
+
return factoryNodes(config, kind).flatMap((node) => {
|
|
952
|
+
if (node?.type !== nodeTypes.callExpression || node.callee.type !== nodeTypes.identifier) return [];
|
|
953
|
+
const imported = imports.get(node.callee.name);
|
|
954
|
+
return imported ? [{
|
|
955
|
+
importName: node.callee.name,
|
|
956
|
+
exportName: imported.exported,
|
|
957
|
+
packageName: imported.from,
|
|
958
|
+
call: node
|
|
959
|
+
}] : [];
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
/** Whether nothing but whitespace precedes `offset` on its line. */
|
|
963
|
+
function startsLine(source, offset) {
|
|
964
|
+
return source.slice(source.lastIndexOf("\n", offset - 1) + 1, offset).trim() === "";
|
|
965
|
+
}
|
|
966
|
+
/** The `disable-plugin` blocks in this config, read from marker comments since the call is no longer a node. */
|
|
967
|
+
function findDisabled(source, comments, config) {
|
|
968
|
+
const lines = source.split("\n");
|
|
969
|
+
return comments.flatMap((comment) => {
|
|
970
|
+
const match = comment.type === commentTypes.line ? comment.value.trim().match(DISABLED_MARKER_PATTERN) : null;
|
|
971
|
+
if (!match || comment.start < config.start || comment.end > config.end || !startsLine(source, comment.start)) return [];
|
|
972
|
+
const line = getPosition(source, comment.start).line;
|
|
973
|
+
const end = line + Number(match[2]);
|
|
974
|
+
const code = lines.slice(line, end).map((text) => text.replace(/^(\s*)\/\/ ?/, "$1").replace(/^\s+$/, ""));
|
|
975
|
+
return [{
|
|
976
|
+
packageName: match[1],
|
|
977
|
+
line,
|
|
978
|
+
end,
|
|
979
|
+
code
|
|
980
|
+
}];
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
/** Literal members as `value` and code paths as `locked`, `undefined` for a computed key. */
|
|
984
|
+
function readEditable(node, prefix = []) {
|
|
985
|
+
const value = {};
|
|
986
|
+
const locked = [];
|
|
987
|
+
for (const [index, entry] of node.properties.entries()) {
|
|
988
|
+
if (entry.type === nodeTypes.spreadElement) continue;
|
|
989
|
+
const key = getKey(entry);
|
|
990
|
+
if (key === void 0) return void 0;
|
|
991
|
+
if (findProperty(node, key)?.index !== index) continue;
|
|
992
|
+
const path = [...prefix, key];
|
|
993
|
+
if (!isSafeKey(key)) {
|
|
994
|
+
locked.push(path);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const member = isOverridden(node, index) ? void 0 : entry.value;
|
|
998
|
+
const literal = readLiteral(member);
|
|
999
|
+
if (literal !== void 0) {
|
|
1000
|
+
value[key] = literal;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (member?.type !== nodeTypes.objectExpression) {
|
|
1004
|
+
locked.push(path);
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const nested = readEditable(member, path);
|
|
1008
|
+
if (!nested) return void 0;
|
|
1009
|
+
value[key] = nested.value;
|
|
1010
|
+
locked.push(...nested.locked);
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
value,
|
|
1014
|
+
locked
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function readObjectOptions(options) {
|
|
1018
|
+
const entries = {};
|
|
1019
|
+
for (const [index, entry] of options.properties.entries()) {
|
|
1020
|
+
const key = getKey(entry);
|
|
1021
|
+
if (key === void 0 || !isSafeKey(key)) continue;
|
|
1022
|
+
const member = entry.type === nodeTypes.property && !isOverridden(options, index) ? entry.value : void 0;
|
|
1023
|
+
const value = readLiteral(member);
|
|
1024
|
+
if (value !== void 0) {
|
|
1025
|
+
entries[key] = {
|
|
1026
|
+
literal: true,
|
|
1027
|
+
value
|
|
1028
|
+
};
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
entries[key] = {
|
|
1032
|
+
literal: false,
|
|
1033
|
+
...member?.type === nodeTypes.objectExpression ? readEditable(member) : void 0
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
return entries;
|
|
1037
|
+
}
|
|
1038
|
+
function readCallOptions(call) {
|
|
1039
|
+
const options = call.arguments[0];
|
|
1040
|
+
return options?.type === nodeTypes.objectExpression ? readObjectOptions(options) : {};
|
|
1041
|
+
}
|
|
1042
|
+
function readDisabledOptions(code) {
|
|
1043
|
+
const call = parseExpression(code.join("\n").trim().replace(/,$/, ""));
|
|
1044
|
+
return call?.type === nodeTypes.callExpression ? readCallOptions(call) : {};
|
|
1045
|
+
}
|
|
1046
|
+
function toPluginView({ importName, packageName, call }) {
|
|
1047
|
+
return {
|
|
1048
|
+
importName,
|
|
1049
|
+
packageName,
|
|
1050
|
+
options: readCallOptions(call)
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
/** Active and disabled plugins, in source order so a disabled plugin keeps its place. */
|
|
1054
|
+
function readPlugins({ source, parsed, config }) {
|
|
1055
|
+
const importNames = new Map([...getImports(parsed.program)].map(([local, { from }]) => [from, local]));
|
|
1056
|
+
const active = findCalls(parsed.program, config).map((call) => ({
|
|
1057
|
+
line: getPosition(source, call.call.start).line,
|
|
1058
|
+
plugin: toPluginView(call)
|
|
1059
|
+
}));
|
|
1060
|
+
const disabled = findDisabled(source, parsed.comments, config).map(({ packageName, line, code }) => ({
|
|
1061
|
+
line,
|
|
1062
|
+
plugin: {
|
|
1063
|
+
importName: importNames.get(packageName) ?? toExportName(packageName),
|
|
1064
|
+
packageName,
|
|
1065
|
+
options: readDisabledOptions(code),
|
|
1066
|
+
disabled: true
|
|
1067
|
+
}
|
|
1068
|
+
}));
|
|
1069
|
+
return [...active, ...disabled].sort((a, b) => a.line - b.line).map(({ plugin }) => plugin);
|
|
1070
|
+
}
|
|
1071
|
+
function readConfigView({ source, parsed, config }) {
|
|
1072
|
+
const [adapter] = findCalls(parsed.program, config, extensionKinds.adapter).map(toPluginView);
|
|
1073
|
+
const parsers = findCalls(parsed.program, config, extensionKinds.parser).map(toPluginView);
|
|
1074
|
+
const output = getOutput(config);
|
|
1075
|
+
return {
|
|
1076
|
+
name: getConfigName(config),
|
|
1077
|
+
plugins: readPlugins({
|
|
1078
|
+
source,
|
|
1079
|
+
parsed,
|
|
1080
|
+
config
|
|
1081
|
+
}),
|
|
1082
|
+
...adapter && { adapter },
|
|
1083
|
+
...parsers.length ? { parsers } : {},
|
|
1084
|
+
...!("reason" in output) && { output: pickOutput(readObjectOptions(output.object)) }
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
/** Which plugins the file declares and which options Studio may write. */
|
|
1088
|
+
function readConfig(source) {
|
|
1089
|
+
const parsed = findConfigs(source);
|
|
1090
|
+
if ("reason" in parsed) return {
|
|
1091
|
+
managed: false,
|
|
1092
|
+
reason: parsed.reason
|
|
1093
|
+
};
|
|
1094
|
+
return {
|
|
1095
|
+
managed: true,
|
|
1096
|
+
configs: parsed.configs.map((config) => readConfigView({
|
|
1097
|
+
source,
|
|
1098
|
+
parsed,
|
|
1099
|
+
config
|
|
1100
|
+
}))
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
/** The modules the file imports, empty when it cannot be parsed. */
|
|
1104
|
+
function readImports(source) {
|
|
1105
|
+
const parsed = parseSource(source);
|
|
1106
|
+
if ("reason" in parsed) return [];
|
|
1107
|
+
return [...new Set([...getImports(parsed.program).values()].map(({ from }) => from))];
|
|
1108
|
+
}
|
|
1109
|
+
/** Follows `path` to its literal, or to the first missing key. Refuses a value set in code. */
|
|
1110
|
+
function findPath(options, path) {
|
|
1111
|
+
let object = options;
|
|
1112
|
+
for (const [depth, key] of path.entries()) {
|
|
1113
|
+
const found = findProperty(object, key);
|
|
1114
|
+
if (!found) return {
|
|
1115
|
+
object,
|
|
1116
|
+
missing: depth
|
|
1117
|
+
};
|
|
1118
|
+
if (isOverridden(object, found.index)) return { reason: `${path.slice(0, depth + 1).join(".")} can be overridden by a spread after it, so it is customized in code` };
|
|
1119
|
+
const { member, index } = found;
|
|
1120
|
+
if (depth === path.length - 1) return member.type === nodeTypes.property && readLiteral(member.value) !== void 0 ? {
|
|
1121
|
+
object,
|
|
1122
|
+
index,
|
|
1123
|
+
value: member.value
|
|
1124
|
+
} : { reason: `${path.join(".")} is customized in code` };
|
|
1125
|
+
if (member.type !== nodeTypes.property || member.value.type !== nodeTypes.objectExpression) return { reason: `${key} is not an object, so ${path.join(".")} cannot be reached` };
|
|
1126
|
+
object = member.value;
|
|
1127
|
+
}
|
|
1128
|
+
return { reason: "no option path given" };
|
|
1129
|
+
}
|
|
1130
|
+
function setAtPath({ source, options, path, value, quote }) {
|
|
1131
|
+
const target = findPath(options, path);
|
|
1132
|
+
if ("reason" in target) return target;
|
|
1133
|
+
if ("missing" in target) {
|
|
1134
|
+
const [key, ...rest] = path.slice(target.missing);
|
|
1135
|
+
return { source: appendItem(source, target.object, `${printKey(key, quote)}: ${printValue(wrapPath(rest, value), quote)}`) };
|
|
1136
|
+
}
|
|
1137
|
+
return { source: replaceText(source, target.value.start, target.value.end, printValue(value, quote)) };
|
|
1138
|
+
}
|
|
1139
|
+
function removeAtPath({ source, options, path }) {
|
|
1140
|
+
const target = findPath(options, path);
|
|
1141
|
+
if ("reason" in target) return target;
|
|
1142
|
+
if ("missing" in target) return { source };
|
|
1143
|
+
return { source: removeItem(source, target.object, target.index) };
|
|
1144
|
+
}
|
|
1145
|
+
function setOption(source, call, path, value, quote) {
|
|
1146
|
+
if (!isOptionValue(value)) return { reason: "the value is not a literal that can be written to a config file" };
|
|
1147
|
+
const options = call.arguments[0];
|
|
1148
|
+
if (!options) {
|
|
1149
|
+
const closing = call.end - 1;
|
|
1150
|
+
if (source[closing] !== ")") return { reason: "the factory call could not be located in the file" };
|
|
1151
|
+
return { source: replaceText(source, closing, closing, printValue(wrapPath(path, value), quote)) };
|
|
1152
|
+
}
|
|
1153
|
+
if (options.type !== nodeTypes.objectExpression) return { reason: "the factory was not called with an object literal" };
|
|
1154
|
+
return setAtPath({
|
|
1155
|
+
source,
|
|
1156
|
+
options,
|
|
1157
|
+
path,
|
|
1158
|
+
value,
|
|
1159
|
+
quote
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function removeOption(source, call, path) {
|
|
1163
|
+
const options = call.arguments[0];
|
|
1164
|
+
if (!options) return { source };
|
|
1165
|
+
if (options.type !== nodeTypes.objectExpression) return { reason: "the factory was not called with an object literal" };
|
|
1166
|
+
return removeAtPath({
|
|
1167
|
+
source,
|
|
1168
|
+
options,
|
|
1169
|
+
path
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
function editOption({ source, parsed, config, edit, quote, extensions }) {
|
|
1173
|
+
const kind = edit.kind ?? extensionKinds.plugin;
|
|
1174
|
+
const calls = findCalls(parsed.program, config, kind).filter((entry) => entry.packageName === edit.plugin);
|
|
1175
|
+
const exportName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
|
|
1176
|
+
const call = (calls.find((entry) => entry.exportName === exportName) ?? calls[0])?.call;
|
|
1177
|
+
if (!call) {
|
|
1178
|
+
if (kind === extensionKinds.adapter) return { reason: `${edit.plugin} is not the config's adapter` };
|
|
1179
|
+
return { reason: kind === extensionKinds.plugin && findDisabled(source, parsed.comments, config).some((block) => block.packageName === edit.plugin) ? `${edit.plugin} is disabled, enable it first` : `${edit.plugin} is not in the ${kind}s array` };
|
|
1180
|
+
}
|
|
1181
|
+
if (edit.operation === ConfigEditOperation.SET_OPTION) return setOption(source, call, edit.path, edit.value, quote);
|
|
1182
|
+
return removeOption(source, call, edit.path);
|
|
1183
|
+
}
|
|
1184
|
+
function setInput(source, config, input, quote) {
|
|
1185
|
+
const value = printValue(input, quote);
|
|
1186
|
+
const found = findProperty(config, "input");
|
|
1187
|
+
if (!found) return { source: appendItem(source, config, `input: ${value}`) };
|
|
1188
|
+
if (found.member.type !== nodeTypes.property || !isPlainProperty(found.member)) return { reason: "input is not a plain property" };
|
|
1189
|
+
if (isOverridden(config, found.index)) return { reason: "a spread after input could override it" };
|
|
1190
|
+
return { source: replaceText(source, found.member.value.start, found.member.value.end, value) };
|
|
1191
|
+
}
|
|
1192
|
+
function getOutput(config) {
|
|
1193
|
+
const found = findProperty(config, "output");
|
|
1194
|
+
if (found?.member.type !== nodeTypes.property || found.member.value.type !== nodeTypes.objectExpression) return { reason: "output is not an object literal" };
|
|
1195
|
+
if (isOverridden(config, found.index)) return { reason: "a spread after output could override it" };
|
|
1196
|
+
return { object: found.member.value };
|
|
1197
|
+
}
|
|
1198
|
+
function editOutput({ source, config, edit, quote }) {
|
|
1199
|
+
const output = getOutput(config);
|
|
1200
|
+
if ("reason" in output) return output;
|
|
1201
|
+
if (edit.operation === ConfigEditOperation.REMOVE_OUTPUT) return removeAtPath({
|
|
1202
|
+
source,
|
|
1203
|
+
options: output.object,
|
|
1204
|
+
path: edit.path
|
|
1205
|
+
});
|
|
1206
|
+
const [key, ...rest] = edit.path;
|
|
1207
|
+
if (!isOptionValue(edit.value) || !isOutputValue({
|
|
1208
|
+
key,
|
|
1209
|
+
value: wrapPath(rest, edit.value)
|
|
1210
|
+
})) return { reason: `the value is not one output.${key} takes` };
|
|
1211
|
+
return setAtPath({
|
|
1212
|
+
source,
|
|
1213
|
+
options: output.object,
|
|
1214
|
+
path: edit.path,
|
|
1215
|
+
value: edit.value,
|
|
1216
|
+
quote
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
/** Adds the import after the last one, matching its semicolon style. */
|
|
1220
|
+
function addImport({ source, parsed, importNames, moduleSpecifier, quote }) {
|
|
1221
|
+
const lastImport = parsed.program.body.findLast((node) => isImportStatement(node, parsed.kind));
|
|
1222
|
+
const afterLine = lastImport ? getPosition(source, lastImport.end).line : 0;
|
|
1223
|
+
const lines = source.split("\n");
|
|
1224
|
+
const semicolon = afterLine > 0 && lines[afterLine - 1]?.trimEnd().endsWith(";") ? ";" : "";
|
|
1225
|
+
const specifier = `${quote}${moduleSpecifier}${quote}`;
|
|
1226
|
+
const statement = parsed.kind === moduleKinds.esm ? `import { ${importNames.join(", ")} } from ${specifier}` : `const { ${importNames.join(", ")} } = require(${specifier})`;
|
|
1227
|
+
lines.splice(afterLine, 0, `${statement}${semicolon}`);
|
|
1228
|
+
if (afterLine === 0) lines.splice(1, 0, "");
|
|
1229
|
+
return lines.join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* The parsers `defineConfig` applies when a config sets no `parsers`, by import name. Adding a parser
|
|
1233
|
+
* to such a config writes these too, or the new array would replace them.
|
|
1234
|
+
*/
|
|
1235
|
+
const DEFAULT_PARSERS = [
|
|
1236
|
+
{
|
|
1237
|
+
importName: "parserTs",
|
|
1238
|
+
packageName: "@kubb/parser-ts"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
importName: "parserTsx",
|
|
1242
|
+
packageName: "@kubb/parser-ts"
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
importName: "parserMd",
|
|
1246
|
+
packageName: "@kubb/parser-md"
|
|
1247
|
+
}
|
|
1248
|
+
];
|
|
1249
|
+
/**
|
|
1250
|
+
* Adds a plugin to `plugins`, or with `kind: 'parser'` a parser to `parsers`. Replaying the same add
|
|
1251
|
+
* is a no-op, while an import name taken by another package is an error.
|
|
1252
|
+
*/
|
|
1253
|
+
function addPlugin({ source, parsed, config, edit, quote, extensions }) {
|
|
1254
|
+
const kind = edit.kind ?? extensionKinds.plugin;
|
|
1255
|
+
const isParser = kind === extensionKinds.parser;
|
|
1256
|
+
if (!isAllowedPlugin({
|
|
1257
|
+
packageName: edit.plugin,
|
|
1258
|
+
extensions,
|
|
1259
|
+
kind
|
|
1260
|
+
})) return { reason: isParser ? `"${edit.plugin}" is not an installed Kubb parser` : `"${edit.plugin}" is not a @kubb/plugin-* package or an installed Kubb plugin` };
|
|
1261
|
+
const importName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
|
|
1262
|
+
if (!IDENTIFIER_PATTERN.test(importName)) return { reason: `"${importName}" is not a valid import name` };
|
|
1263
|
+
if (findCalls(parsed.program, config, kind).some((entry) => entry.packageName === edit.plugin && (!isParser || entry.exportName === importName))) return { source };
|
|
1264
|
+
const options = edit.options ?? {};
|
|
1265
|
+
if (!isOptionValue(options)) return { reason: "the options are not literals that can be written to a config file" };
|
|
1266
|
+
const key = `${kind}s`;
|
|
1267
|
+
const array = getValue(config, key);
|
|
1268
|
+
if (array ? array.type !== nodeTypes.arrayExpression : !isParser) return { reason: `${key} is not an array literal` };
|
|
1269
|
+
const defaults = isParser && !array ? DEFAULT_PARSERS.filter((parser) => parser.importName !== importName) : [];
|
|
1270
|
+
const imports = getImports(parsed.program);
|
|
1271
|
+
const missing = /* @__PURE__ */ new Map();
|
|
1272
|
+
for (const item of [...defaults, {
|
|
1273
|
+
importName,
|
|
1274
|
+
packageName: edit.plugin
|
|
1275
|
+
}]) {
|
|
1276
|
+
const from = imports.get(item.importName)?.from;
|
|
1277
|
+
if (from && from !== item.packageName) return { reason: `${item.importName} is already imported from ${from}` };
|
|
1278
|
+
if (!from) missing.set(item.packageName, [...missing.get(item.packageName) ?? [], item.importName]);
|
|
1279
|
+
}
|
|
1280
|
+
const call = `${importName}(${Object.keys(options).length ? printValue(options, quote) : ""})`;
|
|
1281
|
+
const next = array?.type === nodeTypes.arrayExpression ? appendItem(source, array, call) : appendItem(source, config, `parsers: [${[...defaults.map((parser) => `${parser.importName}()`), call].join(", ")}]`);
|
|
1282
|
+
return { source: [...missing].reduceRight((current, [moduleSpecifier, importNames]) => addImport({
|
|
1283
|
+
source: current,
|
|
1284
|
+
parsed,
|
|
1285
|
+
importNames,
|
|
1286
|
+
moduleSpecifier,
|
|
1287
|
+
quote
|
|
1288
|
+
}), next) };
|
|
1289
|
+
}
|
|
1290
|
+
/** One item per line, so a call can be commented out alone. Skipped when comments sit between items. */
|
|
1291
|
+
function splitPlugins(source, config) {
|
|
1292
|
+
const array = getValue(config, "plugins");
|
|
1293
|
+
if (array?.type !== nodeTypes.arrayExpression) return void 0;
|
|
1294
|
+
const items = array.elements.flatMap((element) => element ? [element] : []);
|
|
1295
|
+
const { start, end } = array;
|
|
1296
|
+
const gaps = [source.slice(start + 1, items[0]?.start ?? end - 1), ...items.map((item, index) => source.slice(item.end, items[index + 1]?.start ?? end - 1))];
|
|
1297
|
+
if (!items.length || gaps.some((gap) => !/^[\s,]*$/.test(gap))) return void 0;
|
|
1298
|
+
const lineStart = source.lastIndexOf("\n", start - 1) + 1;
|
|
1299
|
+
const indent = source.slice(lineStart).match(/^[ \t]*/)?.[0] ?? "";
|
|
1300
|
+
return replaceText(source, start, end, `[\n${items.map((item) => `${indent} ${source.slice(item.start, item.end)},`).join("\n")}\n${indent}]`);
|
|
1301
|
+
}
|
|
1302
|
+
function disablePlugin(source, program, config, plugin) {
|
|
1303
|
+
const call = findCalls(program, config).find((entry) => entry.packageName === plugin)?.call;
|
|
1304
|
+
if (!call) return { reason: `${plugin} is not in the plugins array` };
|
|
1305
|
+
const start = getPosition(source, call.start);
|
|
1306
|
+
const end = getPosition(source, call.end);
|
|
1307
|
+
const lines = source.split("\n");
|
|
1308
|
+
const from = start.line - 1;
|
|
1309
|
+
const to = end.line - 1;
|
|
1310
|
+
const firstLine = lines[from] ?? "";
|
|
1311
|
+
if (firstLine.slice(0, start.column).trim() || !/^,?\s*$/.test((lines[to] ?? "").slice(end.column))) return { reason: `${plugin} shares a line with other code, so it cannot be commented out safely` };
|
|
1312
|
+
const indent = firstLine.match(/^\s*/)?.[0] ?? "";
|
|
1313
|
+
const commented = lines.slice(from, to + 1).map((line) => line.trim() ? `${indent}// ${line.slice(indent.length)}` : `${indent}//`);
|
|
1314
|
+
lines.splice(from, commented.length, `${indent}// kubb:disabled ${plugin} ${commented.length}`, ...commented);
|
|
1315
|
+
return { source: lines.join("\n") };
|
|
1316
|
+
}
|
|
1317
|
+
/** Disables the plugin, first putting a one-line `plugins: [a(), b()]` (what `add-plugin` writes) on separate lines. */
|
|
1318
|
+
function disablePluginInConfig({ source, parsed, config, edit }) {
|
|
1319
|
+
const result = disablePlugin(source, parsed.program, config, edit.plugin);
|
|
1320
|
+
if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
|
|
1321
|
+
const split = splitPlugins(source, config);
|
|
1322
|
+
if (split === void 0) return result;
|
|
1323
|
+
const reparsed = findConfigs(split);
|
|
1324
|
+
const reparsedConfig = "reason" in reparsed ? void 0 : findConfigEntry(reparsed.configs, edit.config);
|
|
1325
|
+
if ("reason" in reparsed || !reparsedConfig) return result;
|
|
1326
|
+
return disablePlugin(split, reparsed.program, reparsedConfig, edit.plugin);
|
|
1327
|
+
}
|
|
1328
|
+
function enablePlugin(source, comments, config, plugin) {
|
|
1329
|
+
const block = findDisabled(source, comments, config).find((entry) => entry.packageName === plugin);
|
|
1330
|
+
if (!block) return { reason: `${plugin} is not disabled` };
|
|
1331
|
+
const lines = source.split("\n");
|
|
1332
|
+
lines.splice(block.line - 1, block.end - block.line + 1, ...block.code);
|
|
1333
|
+
return { source: lines.join("\n") };
|
|
1334
|
+
}
|
|
1335
|
+
/** Drops the import statements of `packageName` once nothing else in the file uses their names. */
|
|
1336
|
+
function removeUnusedImports(source, packageName) {
|
|
1337
|
+
const parsed = parseSource(source);
|
|
1338
|
+
if ("reason" in parsed) return source;
|
|
1339
|
+
return parsed.program.body.reduceRight((current, statement) => {
|
|
1340
|
+
const bindings = statement.type === nodeTypes.importDeclaration ? importBindings(statement) : statement.type === nodeTypes.variableDeclaration ? requireBindings(statement) : [];
|
|
1341
|
+
if (!bindings.length || bindings.some(([, { from }]) => from !== packageName)) return current;
|
|
1342
|
+
const rest = replaceText(current, statement.start, statement.end);
|
|
1343
|
+
if (bindings.some(([local]) => new RegExp(`(?<![\\w$])${local.replaceAll("$", "\\$")}(?![\\w$])`).test(rest))) return current;
|
|
1344
|
+
const lineStart = current.lastIndexOf("\n", statement.start - 1) + 1;
|
|
1345
|
+
const lineEnd = current.indexOf("\n", statement.end);
|
|
1346
|
+
const after = lineEnd === -1 ? current.length : lineEnd + 1;
|
|
1347
|
+
return replaceText(current, lineStart, lineStart === 0 && current[after] === "\n" ? after + 1 : after);
|
|
1348
|
+
}, source);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Removes every call of a plugin or parser package, and the block `disable-plugin` commented out,
|
|
1352
|
+
* then its unused imports. Removing what the config does not hold is a no-op. Refuses to empty
|
|
1353
|
+
* `parsers`, since a config with no parser cannot print files.
|
|
1354
|
+
*/
|
|
1355
|
+
function removeExtension(source, edit) {
|
|
1356
|
+
const kind = edit.kind ?? extensionKinds.plugin;
|
|
1357
|
+
let current = source;
|
|
1358
|
+
for (;;) {
|
|
1359
|
+
const parsed = findConfigs(current);
|
|
1360
|
+
if ("reason" in parsed) return parsed;
|
|
1361
|
+
const config = findConfigEntry(parsed.configs, edit.config);
|
|
1362
|
+
if (!config) return { reason: `no config entry found for ${JSON.stringify(edit.config)}` };
|
|
1363
|
+
const array = getValue(config, `${kind}s`);
|
|
1364
|
+
const calls = findCalls(parsed.program, config, kind).filter((entry) => entry.packageName === edit.plugin);
|
|
1365
|
+
const block = findDisabled(current, parsed.comments, config).find((entry) => entry.packageName === edit.plugin);
|
|
1366
|
+
if (calls[0] && array?.type === nodeTypes.arrayExpression) {
|
|
1367
|
+
const elements = array.elements.filter((element) => element !== null);
|
|
1368
|
+
if (kind === extensionKinds.parser && elements.length === calls.length) return { reason: `${edit.plugin} is the last parser in the parsers array` };
|
|
1369
|
+
current = removeItem(current, array, elements.indexOf(calls[0].call));
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (block && kind === extensionKinds.plugin) {
|
|
1373
|
+
const lines = current.split("\n");
|
|
1374
|
+
lines.splice(block.line - 1, block.end - block.line + 1);
|
|
1375
|
+
current = lines.join("\n");
|
|
1376
|
+
continue;
|
|
1377
|
+
}
|
|
1378
|
+
return { source: current === source ? source : removeUnusedImports(current, edit.plugin) };
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
/** Every edit re-parses the file, since the previous one rewrote it. */
|
|
1382
|
+
function applyEdit({ source, edit, extensions }) {
|
|
1383
|
+
const parsed = findConfigs(source);
|
|
1384
|
+
if ("reason" in parsed) return parsed;
|
|
1385
|
+
const config = findConfigEntry(parsed.configs, edit.config);
|
|
1386
|
+
if (!config) return { reason: `no config entry found for ${JSON.stringify(edit.config)}` };
|
|
1387
|
+
const quote = detectQuote(parsed.program) ?? quotes.single;
|
|
1388
|
+
switch (edit.operation) {
|
|
1389
|
+
case ConfigEditOperation.SET_INPUT: return setInput(source, config, edit.input, quote);
|
|
1390
|
+
case ConfigEditOperation.SET_OUTPUT:
|
|
1391
|
+
case ConfigEditOperation.REMOVE_OUTPUT: return editOutput({
|
|
1392
|
+
source,
|
|
1393
|
+
config,
|
|
1394
|
+
edit,
|
|
1395
|
+
quote
|
|
1396
|
+
});
|
|
1397
|
+
case ConfigEditOperation.DISABLE_PLUGIN: return disablePluginInConfig({
|
|
1398
|
+
source,
|
|
1399
|
+
parsed,
|
|
1400
|
+
config,
|
|
1401
|
+
edit
|
|
1402
|
+
});
|
|
1403
|
+
case ConfigEditOperation.ENABLE_PLUGIN: return enablePlugin(source, parsed.comments, config, edit.plugin);
|
|
1404
|
+
case ConfigEditOperation.ADD_PLUGIN: return addPlugin({
|
|
1405
|
+
source,
|
|
1406
|
+
parsed,
|
|
1407
|
+
config,
|
|
1408
|
+
edit,
|
|
1409
|
+
quote,
|
|
1410
|
+
extensions
|
|
1411
|
+
});
|
|
1412
|
+
case ConfigEditOperation.REMOVE_EXTENSION: return removeExtension(source, edit);
|
|
1413
|
+
case ConfigEditOperation.SET_OPTION:
|
|
1414
|
+
case ConfigEditOperation.REMOVE_OPTION: return editOption({
|
|
1415
|
+
source,
|
|
1416
|
+
parsed,
|
|
1417
|
+
config,
|
|
1418
|
+
edit,
|
|
1419
|
+
quote,
|
|
1420
|
+
extensions
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
/** Applies edits in place, touching only the text each targets. A refused edit is reported and the rest still run. */
|
|
1425
|
+
function editConfig({ source, edits, extensions = /* @__PURE__ */ new Map() }) {
|
|
1426
|
+
let current = source;
|
|
1427
|
+
const outcomes = edits.map((edit) => {
|
|
1428
|
+
const invalid = validateEdit(edit);
|
|
1429
|
+
if (invalid) return {
|
|
1430
|
+
edit,
|
|
1431
|
+
applied: false,
|
|
1432
|
+
reason: invalid
|
|
1433
|
+
};
|
|
1434
|
+
try {
|
|
1435
|
+
const result = applyEdit({
|
|
1436
|
+
source: current,
|
|
1437
|
+
edit,
|
|
1438
|
+
extensions
|
|
1439
|
+
});
|
|
1440
|
+
if ("reason" in result) return {
|
|
1441
|
+
edit,
|
|
1442
|
+
applied: false,
|
|
1443
|
+
reason: result.reason
|
|
1444
|
+
};
|
|
1445
|
+
current = result.source;
|
|
1446
|
+
return {
|
|
1447
|
+
edit,
|
|
1448
|
+
applied: true
|
|
1449
|
+
};
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
return {
|
|
1452
|
+
edit,
|
|
1453
|
+
applied: false,
|
|
1454
|
+
reason: getErrorMessage(error)
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
return {
|
|
1459
|
+
source: current,
|
|
1460
|
+
outcomes,
|
|
1461
|
+
changed: current !== source
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
/** Applies `edits` to `filePath`, writing only when something changed. */
|
|
1465
|
+
async function editConfigFile({ filePath, edits, extensions }) {
|
|
1466
|
+
const result = editConfig({
|
|
1467
|
+
source: await read(filePath),
|
|
1468
|
+
edits,
|
|
1469
|
+
extensions
|
|
1470
|
+
});
|
|
1471
|
+
if (result.changed) await writeRaw(filePath, result.source);
|
|
1472
|
+
return result;
|
|
1473
|
+
}
|
|
1474
|
+
//#endregion
|
|
477
1475
|
//#region src/runners/init/utils.ts
|
|
478
1476
|
/** Metadata for each supported package manager, keyed by its short name. */
|
|
479
1477
|
const packageManagers = {
|
|
@@ -482,6 +1480,7 @@ const packageManagers = {
|
|
|
482
1480
|
lockFiles: ["pnpm-lock.yaml"],
|
|
483
1481
|
initCommand: ["init"],
|
|
484
1482
|
installCommand: ["add", "-D"],
|
|
1483
|
+
removeCommand: ["remove"],
|
|
485
1484
|
runCommand: []
|
|
486
1485
|
},
|
|
487
1486
|
yarn: {
|
|
@@ -489,6 +1488,7 @@ const packageManagers = {
|
|
|
489
1488
|
lockFiles: ["yarn.lock"],
|
|
490
1489
|
initCommand: ["init", "-y"],
|
|
491
1490
|
installCommand: ["add", "-D"],
|
|
1491
|
+
removeCommand: ["remove"],
|
|
492
1492
|
runCommand: []
|
|
493
1493
|
},
|
|
494
1494
|
bun: {
|
|
@@ -496,6 +1496,7 @@ const packageManagers = {
|
|
|
496
1496
|
lockFiles: ["bun.lock", "bun.lockb"],
|
|
497
1497
|
initCommand: ["init", "-y"],
|
|
498
1498
|
installCommand: ["add", "-d"],
|
|
1499
|
+
removeCommand: ["remove"],
|
|
499
1500
|
runCommand: ["run"]
|
|
500
1501
|
},
|
|
501
1502
|
npm: {
|
|
@@ -503,6 +1504,7 @@ const packageManagers = {
|
|
|
503
1504
|
lockFiles: ["package-lock.json"],
|
|
504
1505
|
initCommand: ["init", "-y"],
|
|
505
1506
|
installCommand: ["install", "--save-dev"],
|
|
1507
|
+
removeCommand: ["uninstall"],
|
|
506
1508
|
runCommand: ["run"]
|
|
507
1509
|
}
|
|
508
1510
|
};
|
|
@@ -537,8 +1539,8 @@ const INSTALL_TIMEOUT_MS = 3e5;
|
|
|
537
1539
|
* to `onLine`, so the caller can show it while the install runs; a failed install also keeps the
|
|
538
1540
|
* end of the output in the error.
|
|
539
1541
|
*/
|
|
540
|
-
async function installPackages(packages, packageManager, cwd = process$1.cwd(), onLine) {
|
|
541
|
-
const args = [...
|
|
1542
|
+
async function installPackages(packages, packageManager, cwd = process$1.cwd(), onLine, command = packageManager.installCommand) {
|
|
1543
|
+
const args = [...command, ...packages];
|
|
542
1544
|
const result = x(packageManager.name, args, {
|
|
543
1545
|
nodeOptions: {
|
|
544
1546
|
cwd,
|
|
@@ -562,6 +1564,12 @@ async function installPackages(packages, packageManager, cwd = process$1.cwd(),
|
|
|
562
1564
|
throw new Error(`\`${packageManager.name} ${args.join(" ")}\` failed with exit code ${result.exitCode}${output ? `:\n${output}` : ""}`);
|
|
563
1565
|
}
|
|
564
1566
|
/**
|
|
1567
|
+
* Uninstalls the given packages at `cwd` with the package manager's remove command, like {@link installPackages}.
|
|
1568
|
+
*/
|
|
1569
|
+
function uninstallPackages(packages, packageManager, cwd = process$1.cwd()) {
|
|
1570
|
+
return installPackages(packages, packageManager, cwd, void 0, packageManager.removeCommand);
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
565
1573
|
* What the selected plugins' `manifests` say is missing from the selection: a `dependencies`
|
|
566
1574
|
* entry, or a `client` plugin next to a `consumer`. Empty when nothing is missing.
|
|
567
1575
|
* `optionalDependencies` are skipped, since `kubb init` writes the default options.
|
|
@@ -735,8 +1743,9 @@ function toSnippet({ artifact, manifest }) {
|
|
|
735
1743
|
/** The `add-plugin` edits for `details`, and the manifests the edits read their export names from. */
|
|
736
1744
|
function toConfigEdits(details) {
|
|
737
1745
|
return {
|
|
738
|
-
edits: details.map(({ artifact }) => ({
|
|
739
|
-
operation:
|
|
1746
|
+
edits: details.map(({ artifact, manifest }) => ({
|
|
1747
|
+
operation: ConfigEditOperation.ADD_PLUGIN,
|
|
1748
|
+
...manifest.kind === extensionKinds.parser ? { kind: extensionKinds.parser } : {},
|
|
740
1749
|
plugin: artifact.package
|
|
741
1750
|
})),
|
|
742
1751
|
extensions: new Map(details.map(({ artifact, manifest }) => [artifact.package, {
|
|
@@ -747,24 +1756,22 @@ function toConfigEdits(details) {
|
|
|
747
1756
|
};
|
|
748
1757
|
}
|
|
749
1758
|
/** The lines adding `details` would put in the config `source`, trimmed, or why it could not add them. */
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
return { lines: result.source.split("\n").filter((line) => {
|
|
1759
|
+
function previewConfig({ details, source }) {
|
|
1760
|
+
const result = editConfig({
|
|
1761
|
+
source,
|
|
1762
|
+
...toConfigEdits(details)
|
|
1763
|
+
});
|
|
1764
|
+
const failed = result.outcomes.find((outcome) => !outcome.applied);
|
|
1765
|
+
if (failed) return { reason: failed.reason ?? "the config file was not changed" };
|
|
1766
|
+
const remaining = source.split("\n");
|
|
1767
|
+
return {
|
|
1768
|
+
lines: result.source.split("\n").filter((line) => {
|
|
761
1769
|
const index = remaining.indexOf(line);
|
|
762
1770
|
if (index !== -1) remaining.splice(index, 1);
|
|
763
1771
|
return index === -1;
|
|
764
|
-
}).map((line) => line.trim())
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
1772
|
+
}).map((line) => line.trim()),
|
|
1773
|
+
source: result.source
|
|
1774
|
+
};
|
|
768
1775
|
}
|
|
769
1776
|
async function selectExtension({ entries, versions, registry }) {
|
|
770
1777
|
if (!entries.length) throw new Diagnostics.Error({
|
|
@@ -816,12 +1823,13 @@ function summarize(description, max = 64) {
|
|
|
816
1823
|
return sentence.length > max ? `${sentence.slice(0, max - 1).trimEnd()}…` : sentence;
|
|
817
1824
|
}
|
|
818
1825
|
/**
|
|
819
|
-
* Installs an extension from the Kubb registry at its reviewed version and, for a plugin
|
|
820
|
-
* `kubb.config.ts`, offering to add the plugins it needs as well.
|
|
1826
|
+
* Installs an extension from the Kubb registry at its reviewed version and, for a plugin or a parser,
|
|
1827
|
+
* adds it to `kubb.config.ts`, offering to add the plugins it needs as well. An adapter is never
|
|
1828
|
+
* written: the command prints the snippet to swap it in by hand. Without an id, asks which one.
|
|
821
1829
|
* Previews the packages and config lines before asking. Refuses when the running Kubb or Node.js
|
|
822
1830
|
* version is outside its compatibility.
|
|
823
1831
|
*/
|
|
824
|
-
async function run$
|
|
1832
|
+
async function run$5({ id: input, configPath, yes, install }) {
|
|
825
1833
|
const cwd = process$1.cwd();
|
|
826
1834
|
const hrStart = process$1.hrtime();
|
|
827
1835
|
const summary = (title, status, lines) => logCommandSummary({
|
|
@@ -881,8 +1889,8 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
881
1889
|
message: problem,
|
|
882
1890
|
help: "Upgrade Kubb or Node.js to a version the extension supports."
|
|
883
1891
|
});
|
|
884
|
-
const configFile = manifest.kind ===
|
|
885
|
-
if (manifest.kind
|
|
1892
|
+
const configFile = manifest.kind === extensionKinds.adapter ? void 0 : foundConfig;
|
|
1893
|
+
if (manifest.kind !== extensionKinds.adapter && !configFile) throw new Diagnostics.Error({
|
|
886
1894
|
code: Diagnostics.code.unknown,
|
|
887
1895
|
severity: "error",
|
|
888
1896
|
message: "No kubb.config.ts found. Run `kubb init` first, or pass --config.",
|
|
@@ -906,15 +1914,19 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
906
1914
|
return;
|
|
907
1915
|
}
|
|
908
1916
|
const targets = [...withDependencies ? missing : [], entry];
|
|
909
|
-
const specs = targets.filter((target) => target === entry || !versions.has(target.artifact.package)).map((target) => installSpec(target.artifact));
|
|
910
1917
|
const dependencyStatus = (name) => {
|
|
911
1918
|
if (inConfig(name)) return `${name} ${styleText("green", "✓ installed")}`;
|
|
912
1919
|
return targets.some((target) => target.manifest.name === name) ? `${name} ${styleText("cyan", "+ adding")}` : `${name} ${styleText("yellow", "missing")}`;
|
|
913
1920
|
};
|
|
914
|
-
const preview = configFile && configSource !== void 0 ?
|
|
1921
|
+
const preview = configFile && configSource !== void 0 ? previewConfig({
|
|
915
1922
|
details: targets,
|
|
916
1923
|
source: configSource
|
|
917
1924
|
}) : void 0;
|
|
1925
|
+
const before = readImports(configSource ?? "");
|
|
1926
|
+
const added = preview && "lines" in preview ? readImports(preview.source).filter((name) => !before.includes(name)) : [];
|
|
1927
|
+
const bundled = BUNDLED_PACKAGES.filter((name) => added.includes(name) && !versions.has(name) && !targets.some((target) => target.artifact.package === name));
|
|
1928
|
+
const kubbVersion = versions.get("kubb");
|
|
1929
|
+
const specs = [...targets.filter((target) => target === entry || !versions.has(target.artifact.package)).map((target) => installSpec(target.artifact)), ...bundled.map((name) => kubbVersion ? `${name}@${kubbVersion}` : name)];
|
|
918
1930
|
logStep(`${kind} ${styleText("cyan", manifest.name)} v${artifact.version} · ${summarize(manifest.description)}`);
|
|
919
1931
|
logBlock([
|
|
920
1932
|
...dependencies.length ? [`Needs ${dependencies.map(dependencyStatus).join(" ")}`] : [],
|
|
@@ -934,7 +1946,7 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
934
1946
|
const packageManager = detectPackageManager(cwd);
|
|
935
1947
|
const packages = () => install ? `${installedPackages.length} installed with ${packageManager.name}` : "installation skipped (--no-install)";
|
|
936
1948
|
if (install) {
|
|
937
|
-
const names = targets.filter((target) => specs.includes(installSpec(target.artifact))).map((target) => target.artifact.package);
|
|
1949
|
+
const names = [...targets.filter((target) => specs.includes(installSpec(target.artifact))).map((target) => target.artifact.package), ...bundled];
|
|
938
1950
|
await installWithSpinner({
|
|
939
1951
|
packages: specs,
|
|
940
1952
|
names: names.join(", "),
|
|
@@ -945,8 +1957,7 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
945
1957
|
}
|
|
946
1958
|
const unconfigured = configFile ? [] : [entry];
|
|
947
1959
|
if (configFile) for (const target of targets) try {
|
|
948
|
-
const {
|
|
949
|
-
const { outcomes, changed } = await writeConfigEdits({
|
|
1960
|
+
const { outcomes, changed } = await editConfigFile({
|
|
950
1961
|
filePath: configFile,
|
|
951
1962
|
...toConfigEdits([target])
|
|
952
1963
|
});
|
|
@@ -958,14 +1969,14 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
958
1969
|
unconfigured.push(target);
|
|
959
1970
|
}
|
|
960
1971
|
const manual = unconfigured.length > 0;
|
|
961
|
-
if (manual && manifest.kind
|
|
1972
|
+
if (manual && manifest.kind !== extensionKinds.adapter) process$1.exitCode = 1;
|
|
962
1973
|
if (manual) logBlock([
|
|
963
1974
|
"Add the import to your Kubb config and merge the property into its defineConfig call:",
|
|
964
1975
|
"",
|
|
965
1976
|
...unconfigured.map((target) => toSnippet(target))
|
|
966
1977
|
]);
|
|
967
1978
|
const dependenciesMissing = !withDependencies && missing.length > 0;
|
|
968
|
-
summary(manual ? `${kind} needs configuration` : dependenciesMissing ? "Plugin configured; dependencies missing" : configured.length ? install ?
|
|
1979
|
+
summary(manual ? `${kind} needs configuration` : dependenciesMissing ? "Plugin configured; dependencies missing" : configured.length ? install ? `${kind} ready` : `${kind} configured` : `${kind} already configured`, manual || dependenciesMissing ? "warning" : "success", [
|
|
969
1980
|
` Packages ${packages()}`,
|
|
970
1981
|
...configured.map((target) => styleText("green", ` + ${target.manifest.export}()`)),
|
|
971
1982
|
"",
|
|
@@ -980,8 +1991,8 @@ async function run$4({ id: input, configPath, yes, install }) {
|
|
|
980
1991
|
process$1.exitCode = 1;
|
|
981
1992
|
}
|
|
982
1993
|
}
|
|
983
|
-
__name(run$
|
|
984
|
-
const command$
|
|
1994
|
+
__name(run$5, "run");
|
|
1995
|
+
const command$4 = define({
|
|
985
1996
|
name: "add",
|
|
986
1997
|
description: "Find and install plugins, adapters, and parsers from the Kubb registry.",
|
|
987
1998
|
examples: [
|
|
@@ -1007,7 +2018,7 @@ const command$3 = define({
|
|
|
1007
2018
|
default: true
|
|
1008
2019
|
}
|
|
1009
2020
|
},
|
|
1010
|
-
run: ({ values }) => run$
|
|
2021
|
+
run: ({ values }) => run$5({
|
|
1011
2022
|
id: values.id,
|
|
1012
2023
|
configPath: values.config,
|
|
1013
2024
|
yes: values.yes,
|
|
@@ -1025,15 +2036,14 @@ function unwrap(value) {
|
|
|
1025
2036
|
return value;
|
|
1026
2037
|
}
|
|
1027
2038
|
/** The full `kubb.config.ts` for `choices`, with each plugin added the way `kubb add` adds it. */
|
|
1028
|
-
|
|
1029
|
-
const {
|
|
1030
|
-
const { source, outcomes } = applyConfigEdits({
|
|
2039
|
+
function buildConfig({ inputPath, outputPath, plugins }) {
|
|
2040
|
+
const { source, outcomes } = editConfig({
|
|
1031
2041
|
source: generateConfigFile({
|
|
1032
2042
|
inputPath,
|
|
1033
2043
|
outputPath
|
|
1034
2044
|
}),
|
|
1035
2045
|
edits: plugins.map((entry) => ({
|
|
1036
|
-
operation:
|
|
2046
|
+
operation: ConfigEditOperation.ADD_PLUGIN,
|
|
1037
2047
|
plugin: entry.artifact.package
|
|
1038
2048
|
})),
|
|
1039
2049
|
extensions: new Map(plugins.map(({ artifact, manifest }) => [artifact.package, {
|
|
@@ -1043,7 +2053,7 @@ async function buildConfig({ inputPath, outputPath, plugins }) {
|
|
|
1043
2053
|
}]))
|
|
1044
2054
|
});
|
|
1045
2055
|
const failed = outcomes.find((outcome) => !outcome.applied);
|
|
1046
|
-
if (failed && failed.edit.operation ===
|
|
2056
|
+
if (failed && failed.edit.operation === ConfigEditOperation.ADD_PLUGIN) throw new Diagnostics.Error({
|
|
1047
2057
|
code: Diagnostics.code.unknown,
|
|
1048
2058
|
severity: "error",
|
|
1049
2059
|
message: `Could not add ${failed.edit.plugin} to ${KUBB_CONFIG_FILENAME}: ${failed.reason}`,
|
|
@@ -1079,7 +2089,7 @@ function generateCommand({ packageManager, script, watch }) {
|
|
|
1079
2089
|
* `generate` script, and runs the first generation.
|
|
1080
2090
|
* Pass `yes: true` to skip all prompts and print one line per step.
|
|
1081
2091
|
*/
|
|
1082
|
-
async function run$
|
|
2092
|
+
async function run$4({ yes, input: inputFlag, output: outputFlag, plugins: pluginsFlag, dryRun }) {
|
|
1083
2093
|
const cwd = process$1.cwd();
|
|
1084
2094
|
/** Runs `task` behind a spinner, or prints one line when it is done in `--yes` mode. */
|
|
1085
2095
|
async function step({ start, task, done, failed }) {
|
|
@@ -1102,7 +2112,6 @@ async function run$3({ yes, input: inputFlag, output: outputFlag, plugins: plugi
|
|
|
1102
2112
|
try {
|
|
1103
2113
|
const registry = fetchRegistryIndex({ registry: registryUrl() }).then((entries) => entries.filter((entry) => entry.kind === "plugin" && !incompatibility(entry.manifest)));
|
|
1104
2114
|
registry.catch(() => {});
|
|
1105
|
-
if (!yes) import("@kubb/studio").catch(() => {});
|
|
1106
2115
|
if (!await exists(path.join(cwd, "package.json"))) {
|
|
1107
2116
|
if (!yes) {
|
|
1108
2117
|
if (!unwrap(await clack.confirm({
|
|
@@ -1216,7 +2225,7 @@ async function run$3({ yes, input: inputFlag, output: outputFlag, plugins: plugi
|
|
|
1216
2225
|
logWarn(`${missing.join("\n")}\nAdd the missing plugins.`);
|
|
1217
2226
|
continue;
|
|
1218
2227
|
}
|
|
1219
|
-
const config =
|
|
2228
|
+
const config = buildConfig(choices);
|
|
1220
2229
|
const packages = [KUBB_PACKAGE_NAME, ...choices.plugins.map((entry) => entry.artifact.package)];
|
|
1221
2230
|
logStep(`Review ${KUBB_CONFIG_FILENAME}`);
|
|
1222
2231
|
logBlock([
|
|
@@ -1268,7 +2277,7 @@ async function run$3({ yes, input: inputFlag, output: outputFlag, plugins: plugi
|
|
|
1268
2277
|
});
|
|
1269
2278
|
await step({
|
|
1270
2279
|
start: `Writing ${KUBB_CONFIG_FILENAME}`,
|
|
1271
|
-
task: async () => dryRun.run(async () => void await write(configPath,
|
|
2280
|
+
task: async () => dryRun.run(async () => void await write(configPath, buildConfig(choices)), { message: `write ${KUBB_CONFIG_FILENAME}` }),
|
|
1272
2281
|
done: () => `Wrote ${KUBB_CONFIG_FILENAME}`,
|
|
1273
2282
|
failed: `Could not write ${KUBB_CONFIG_FILENAME}`
|
|
1274
2283
|
});
|
|
@@ -1320,8 +2329,8 @@ async function run$3({ yes, input: inputFlag, output: outputFlag, plugins: plugi
|
|
|
1320
2329
|
process$1.exit(1);
|
|
1321
2330
|
}
|
|
1322
2331
|
}
|
|
1323
|
-
__name(run$
|
|
1324
|
-
const command$
|
|
2332
|
+
__name(run$4, "run");
|
|
2333
|
+
const command$3 = defineWithTypes()({
|
|
1325
2334
|
name: "init",
|
|
1326
2335
|
description: "Set up a config, install plugins, and run the first generation.",
|
|
1327
2336
|
examples: [
|
|
@@ -1358,7 +2367,7 @@ const command$2 = defineWithTypes()({
|
|
|
1358
2367
|
metavar: "plugin-ts,plugin-zod,..."
|
|
1359
2368
|
}
|
|
1360
2369
|
},
|
|
1361
|
-
run: ({ values, extensions }) => run$
|
|
2370
|
+
run: ({ values, extensions }) => run$4({
|
|
1362
2371
|
yes: values.yes,
|
|
1363
2372
|
input: values.input,
|
|
1364
2373
|
output: values.output,
|
|
@@ -1402,7 +2411,7 @@ async function listExtensions({ cwd, registry }) {
|
|
|
1402
2411
|
});
|
|
1403
2412
|
}
|
|
1404
2413
|
/** Prints the installed extensions as a table. */
|
|
1405
|
-
async function run$
|
|
2414
|
+
async function run$3() {
|
|
1406
2415
|
try {
|
|
1407
2416
|
const rows = await listExtensions({
|
|
1408
2417
|
cwd: process$1.cwd(),
|
|
@@ -1431,12 +2440,176 @@ async function run$2() {
|
|
|
1431
2440
|
process$1.exitCode = 1;
|
|
1432
2441
|
}
|
|
1433
2442
|
}
|
|
1434
|
-
__name(run$
|
|
1435
|
-
const command$
|
|
2443
|
+
__name(run$3, "run");
|
|
2444
|
+
const command$2 = define({
|
|
1436
2445
|
name: "list",
|
|
1437
2446
|
description: "List installed extensions, their versions, and available updates.",
|
|
1438
2447
|
examples: "kubb list",
|
|
1439
|
-
run: () => run$
|
|
2448
|
+
run: () => run$3()
|
|
2449
|
+
});
|
|
2450
|
+
//#endregion
|
|
2451
|
+
//#region src/runners/remove/run.ts
|
|
2452
|
+
/** The lines of `before` that `after` no longer has, trimmed. */
|
|
2453
|
+
function removedLines({ before, after }) {
|
|
2454
|
+
const remaining = after.split("\n");
|
|
2455
|
+
return before.split("\n").filter((line) => {
|
|
2456
|
+
const index = remaining.indexOf(line);
|
|
2457
|
+
if (index !== -1) remaining.splice(index, 1);
|
|
2458
|
+
return index === -1 && line.trim() !== "";
|
|
2459
|
+
}).map((line) => line.trim());
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Removes a plugin or parser from `kubb.config.ts` and uninstalls its package. Refuses the adapter,
|
|
2463
|
+
* which the config needs, and an extension another one in the config depends on. Previews the
|
|
2464
|
+
* change before asking.
|
|
2465
|
+
*/
|
|
2466
|
+
async function run$2({ id: input, configPath, yes, uninstall }) {
|
|
2467
|
+
const cwd = process$1.cwd();
|
|
2468
|
+
const hrStart = process$1.hrtime();
|
|
2469
|
+
const summary = (title, status, lines) => logCommandSummary({
|
|
2470
|
+
title,
|
|
2471
|
+
status,
|
|
2472
|
+
hrStart,
|
|
2473
|
+
block: true,
|
|
2474
|
+
lines
|
|
2475
|
+
});
|
|
2476
|
+
const fail = (title, lines) => {
|
|
2477
|
+
summary(title, "failed", lines);
|
|
2478
|
+
process$1.exitCode = 1;
|
|
2479
|
+
};
|
|
2480
|
+
logIntro({ title: isInteractiveRun({ yes }) ? void 0 : styleText("bgCyan", styleText("black", " Kubb Remove ")) });
|
|
2481
|
+
try {
|
|
2482
|
+
const id = toRegistryId(input);
|
|
2483
|
+
const configFile = configPath ? path.resolve(configPath) : findConfigFile(cwd);
|
|
2484
|
+
if (!configFile) throw new Diagnostics.Error({
|
|
2485
|
+
code: Diagnostics.code.unknown,
|
|
2486
|
+
severity: "error",
|
|
2487
|
+
message: "No kubb.config.ts found. Pass its path with --config.",
|
|
2488
|
+
help: "Run `kubb remove <id> --config <path>`."
|
|
2489
|
+
});
|
|
2490
|
+
const configName = path.relative(cwd, configFile) || configFile;
|
|
2491
|
+
const [entries, installed, source] = await withSpinner({
|
|
2492
|
+
message: "Reading the registry",
|
|
2493
|
+
promise: Promise.all([
|
|
2494
|
+
fetchRegistryIndex({ registry: registryUrl() }),
|
|
2495
|
+
readInstalledPackages({ root: cwd }),
|
|
2496
|
+
read(configFile)
|
|
2497
|
+
])
|
|
2498
|
+
});
|
|
2499
|
+
const versions = new Map(installed.map((item) => [item.package, item.version]));
|
|
2500
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
2501
|
+
if (!entry) {
|
|
2502
|
+
const suggestions = closestIds({
|
|
2503
|
+
input: id,
|
|
2504
|
+
ids: entries.map((candidate) => candidate.id)
|
|
2505
|
+
}).map(toShortId);
|
|
2506
|
+
return fail(`No extension named ${input}`, suggestions.map((suggestion) => ` kubb remove ${suggestion}`));
|
|
2507
|
+
}
|
|
2508
|
+
const { artifact, manifest } = entry;
|
|
2509
|
+
const kind = manifest.kind.charAt(0).toUpperCase() + manifest.kind.slice(1);
|
|
2510
|
+
if (manifest.kind === extensionKinds.adapter) return fail("The adapter cannot be removed", [` A config needs one adapter. Change it in ${configName} by hand.`]);
|
|
2511
|
+
const view = readConfig(source);
|
|
2512
|
+
const running = new Set(view.managed ? view.configs.flatMap((config) => [...config.plugins, ...config.parsers ?? []].filter((call) => !call.disabled).map((call) => call.packageName)) : []);
|
|
2513
|
+
const dependents = entries.filter((other) => other.id !== entry.id && other.manifest.dependencies?.includes(manifest.name) && running.has(other.artifact.package));
|
|
2514
|
+
if (dependents.length) {
|
|
2515
|
+
const names = dependents.map((other) => other.manifest.name).join(", ");
|
|
2516
|
+
return fail(`${names} ${dependents.length === 1 ? "needs" : "need"} ${manifest.name}`, [` Remove ${names} first.`]);
|
|
2517
|
+
}
|
|
2518
|
+
const edits = [{
|
|
2519
|
+
operation: ConfigEditOperation.REMOVE_EXTENSION,
|
|
2520
|
+
...manifest.kind === extensionKinds.parser ? { kind: extensionKinds.parser } : {},
|
|
2521
|
+
plugin: artifact.package
|
|
2522
|
+
}];
|
|
2523
|
+
const preview = editConfig({
|
|
2524
|
+
source,
|
|
2525
|
+
edits
|
|
2526
|
+
});
|
|
2527
|
+
const outcome = preview.outcomes[0];
|
|
2528
|
+
if (!outcome?.applied) return fail(`Could not edit ${configName}`, [` ${outcome?.reason ?? "the config file was not changed"}`]);
|
|
2529
|
+
const bundled = BUNDLED_PACKAGES.includes(artifact.package);
|
|
2530
|
+
const stillImported = readImports(preview.source).includes(artifact.package);
|
|
2531
|
+
const uninstalls = uninstall && !bundled && !stillImported && versions.has(artifact.package);
|
|
2532
|
+
if (!preview.changed && !uninstalls) {
|
|
2533
|
+
summary(`${manifest.name} is not in the project`, "success", [" No changes made"]);
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
const packageLine = (() => {
|
|
2537
|
+
if (uninstalls) return `Uninstalls ${artifact.package}`;
|
|
2538
|
+
if (bundled) return `Keeps ${artifact.package}, which ships with kubb`;
|
|
2539
|
+
if (stillImported) return `Keeps ${artifact.package}, which another config entry still imports`;
|
|
2540
|
+
return "Uninstalls nothing";
|
|
2541
|
+
})();
|
|
2542
|
+
logStep(`${kind} ${styleText("cyan", manifest.name)} v${versions.get(artifact.package) ?? artifact.version}`);
|
|
2543
|
+
logBlock([
|
|
2544
|
+
`Config ${configName}`,
|
|
2545
|
+
...removedLines({
|
|
2546
|
+
before: source,
|
|
2547
|
+
after: preview.source
|
|
2548
|
+
}).map((line) => styleText("red", ` - ${line}`)),
|
|
2549
|
+
packageLine
|
|
2550
|
+
]);
|
|
2551
|
+
const confirmed = yes || await clack.confirm({
|
|
2552
|
+
message: "Remove it?",
|
|
2553
|
+
initialValue: true
|
|
2554
|
+
});
|
|
2555
|
+
if (clack.isCancel(confirmed) || !confirmed) {
|
|
2556
|
+
summary("Remove canceled", "canceled", [` ${kind} ${manifest.name}`, " No changes made"]);
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
if (preview.changed) {
|
|
2560
|
+
const written = await editConfigFile({
|
|
2561
|
+
filePath: configFile,
|
|
2562
|
+
edits
|
|
2563
|
+
});
|
|
2564
|
+
if (!written.outcomes[0]?.applied) return fail(`Could not edit ${configName}`, [` ${written.outcomes[0]?.reason ?? "the config file was not changed"}`]);
|
|
2565
|
+
logStep(`${manifest.export}() removed from ${configName}`);
|
|
2566
|
+
}
|
|
2567
|
+
const packageManager = detectPackageManager(cwd);
|
|
2568
|
+
if (uninstalls) await withSpinner({
|
|
2569
|
+
message: `Uninstalling ${artifact.package} with ${packageManager.name}`,
|
|
2570
|
+
promise: uninstallPackages([artifact.package], packageManager, cwd)
|
|
2571
|
+
});
|
|
2572
|
+
summary(`${kind} removed`, "success", [
|
|
2573
|
+
` Packages ${uninstalls ? `${artifact.package} uninstalled with ${packageManager.name}` : "none uninstalled"}`,
|
|
2574
|
+
"",
|
|
2575
|
+
"Next kubb generate"
|
|
2576
|
+
]);
|
|
2577
|
+
} catch (error) {
|
|
2578
|
+
logError(getErrorMessage(error));
|
|
2579
|
+
fail("Remove failed", [" See the error above"]);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
__name(run$2, "run");
|
|
2583
|
+
const command$1 = define({
|
|
2584
|
+
name: "remove",
|
|
2585
|
+
description: "Remove a plugin or parser from kubb.config.ts and uninstall its package.",
|
|
2586
|
+
examples: [
|
|
2587
|
+
"kubb remove plugin-zod",
|
|
2588
|
+
"kubb remove acme/plugin-x --yes",
|
|
2589
|
+
"kubb remove plugin-zod --no-uninstall"
|
|
2590
|
+
].join("\n"),
|
|
2591
|
+
toKebab: true,
|
|
2592
|
+
args: {
|
|
2593
|
+
id: {
|
|
2594
|
+
type: "positional",
|
|
2595
|
+
description: "Registry id: owner/slug, or a slug for an official extension",
|
|
2596
|
+
required: true
|
|
2597
|
+
},
|
|
2598
|
+
...configArg,
|
|
2599
|
+
...yesArg,
|
|
2600
|
+
uninstall: {
|
|
2601
|
+
type: "boolean",
|
|
2602
|
+
description: "Uninstall the package. Pass --no-uninstall to only edit the config",
|
|
2603
|
+
negatable: true,
|
|
2604
|
+
default: true
|
|
2605
|
+
}
|
|
2606
|
+
},
|
|
2607
|
+
run: ({ values }) => run$2({
|
|
2608
|
+
id: values.id,
|
|
2609
|
+
configPath: values.config,
|
|
2610
|
+
yes: values.yes,
|
|
2611
|
+
uninstall: values.uninstall
|
|
2612
|
+
})
|
|
1440
2613
|
});
|
|
1441
2614
|
//#endregion
|
|
1442
2615
|
//#region src/runners/update/run.ts
|
|
@@ -1449,7 +2622,7 @@ async function regenerate({ cwd, configPath }) {
|
|
|
1449
2622
|
const spinner = createSpinner();
|
|
1450
2623
|
spinner.start("Generating with the new versions");
|
|
1451
2624
|
try {
|
|
1452
|
-
const [{ getConfigs }, { generate }] = await Promise.all([import("./config-
|
|
2625
|
+
const [{ getConfigs }, { generate }] = await Promise.all([import("./config-DWDpFSn1.js"), import("./run-BDmTZPT7.js")]);
|
|
1453
2626
|
const { configs } = await getConfigs({ configPath });
|
|
1454
2627
|
const dirs = configs.map((config) => path.resolve(config.root ?? cwd, config.output.path));
|
|
1455
2628
|
const before = new Map((await Promise.all(dirs.map(readOutputFiles))).flatMap((files) => [...files]));
|
|
@@ -1498,7 +2671,11 @@ async function run$1({ ids, dryRun, yes }) {
|
|
|
1498
2671
|
});
|
|
1499
2672
|
const versions = new Map(installed.map((item) => [item.package, item.version]));
|
|
1500
2673
|
const tracked = entries.filter((entry) => versions.has(entry.artifact.package) && (!wanted.size || wanted.has(entry.id)));
|
|
1501
|
-
for (const id of wanted)
|
|
2674
|
+
for (const id of wanted) {
|
|
2675
|
+
if (tracked.some((entry) => entry.id === id)) continue;
|
|
2676
|
+
logWarn(`${id} is not installed, or not in the registry.`);
|
|
2677
|
+
process$1.exitCode = 1;
|
|
2678
|
+
}
|
|
1502
2679
|
const current = ({ artifact }) => versions.get(artifact.package);
|
|
1503
2680
|
const isMajor = (entry) => bumpOf({
|
|
1504
2681
|
installed: current(entry),
|
|
@@ -1656,19 +2833,20 @@ async function run(argv = process.argv.slice(2)) {
|
|
|
1656
2833
|
const isQuietFlag = args.some((arg) => QUIET_FLAGS.has(arg));
|
|
1657
2834
|
const isJsonOutput = args.includes("--json") || args.some((arg, index) => /^--reporter=.*\bjson\b/.test(arg) || arg === "--reporter" && /\bjson\b/.test(args[index + 1] ?? ""));
|
|
1658
2835
|
if (!isDisabled() && !isQuietFlag) console.error(`${styleText("yellow", "Notice:")} Kubb collects anonymous telemetry data to help improve the tool. No personal data or file contents are collected. \nTo disable, set ${styleText("cyan", "KUBB_DISABLE_TELEMETRY=1")}.\n`);
|
|
1659
|
-
await cli(args, command$
|
|
2836
|
+
await cli(args, command$14, {
|
|
1660
2837
|
name: "kubb",
|
|
1661
2838
|
version,
|
|
1662
2839
|
description: "Generate code from an OpenAPI specification, manage Kubb extensions, or connect the project to Kubb Studio.",
|
|
1663
2840
|
subCommands: {
|
|
1664
|
-
generate: command$
|
|
1665
|
-
init: command$
|
|
1666
|
-
validate: command$
|
|
1667
|
-
mcp: command$
|
|
1668
|
-
studio: command$
|
|
1669
|
-
add: command$
|
|
2841
|
+
generate: command$14,
|
|
2842
|
+
init: command$3,
|
|
2843
|
+
validate: command$5,
|
|
2844
|
+
mcp: command$13,
|
|
2845
|
+
studio: command$6,
|
|
2846
|
+
add: command$4,
|
|
1670
2847
|
update: command,
|
|
1671
|
-
|
|
2848
|
+
remove: command$1,
|
|
2849
|
+
list: command$2
|
|
1672
2850
|
},
|
|
1673
2851
|
fallbackToEntry: true,
|
|
1674
2852
|
renderHeader: isJsonOutput ? null : async (ctx) => {
|