@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.
Files changed (73) hide show
  1. package/README.md +1 -0
  2. package/dist/{Telemetry-B3ls8iy8.cjs → Telemetry-0Sr_THx9.cjs} +2 -2
  3. package/dist/{Telemetry-B3ls8iy8.cjs.map → Telemetry-0Sr_THx9.cjs.map} +1 -1
  4. package/dist/{Telemetry-Cwis9NRC.js → Telemetry-B4hk3xGv.js} +2 -2
  5. package/dist/{Telemetry-Cwis9NRC.js.map → Telemetry-B4hk3xGv.js.map} +1 -1
  6. package/dist/{background-BuZJ6isn.cjs → background-6xlebN_Y.cjs} +2 -2
  7. package/dist/{background-BuZJ6isn.cjs.map → background-6xlebN_Y.cjs.map} +1 -1
  8. package/dist/{background-nDhk-eNv.js → background-B7oEWLZs.js} +1 -1
  9. package/dist/{background-BpyuKytZ.js → background-DY4LTodc.js} +2 -2
  10. package/dist/{background-BpyuKytZ.js.map → background-DY4LTodc.js.map} +1 -1
  11. package/dist/{config-1tiH3heq.cjs → config-BG_Nr2CZ.cjs} +17 -1
  12. package/dist/config-BG_Nr2CZ.cjs.map +1 -0
  13. package/dist/{config-CbjbW2J5.js → config-CNqgDSu3.js} +12 -2
  14. package/dist/config-CNqgDSu3.js.map +1 -0
  15. package/dist/config-DWDpFSn1.js +1 -0
  16. package/dist/credentials-CZK6zPLv.js.map +1 -1
  17. package/dist/credentials-DDaKqtJn.cjs.map +1 -1
  18. package/dist/index.cjs +1275 -97
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +1276 -98
  21. package/dist/index.js.map +1 -1
  22. package/dist/{registry-DxHGGUfy.cjs → registry-9AEIs-Cm.cjs} +3 -3
  23. package/dist/{registry-DxHGGUfy.cjs.map → registry-9AEIs-Cm.cjs.map} +1 -1
  24. package/dist/{registry-D8Vxs2q6.js → registry-DLasg7du.js} +3 -3
  25. package/dist/{registry-D8Vxs2q6.js.map → registry-DLasg7du.js.map} +1 -1
  26. package/dist/{reporters-DMvezNR1.js → reporters-BfeB9jOt.js} +4 -4
  27. package/dist/{reporters-DMvezNR1.js.map → reporters-BfeB9jOt.js.map} +1 -1
  28. package/dist/{reporters-BRt7Cmp0.cjs → reporters-xwOZPZ42.cjs} +4 -4
  29. package/dist/{reporters-BRt7Cmp0.cjs.map → reporters-xwOZPZ42.cjs.map} +1 -1
  30. package/dist/{run-8K8KvqtO.js → run--OOU4YSR.js} +5 -5
  31. package/dist/{run-8K8KvqtO.js.map → run--OOU4YSR.js.map} +1 -1
  32. package/dist/{run-BPi2eh0e.js → run-BDmTZPT7.js} +7 -7
  33. package/dist/{run-BPi2eh0e.js.map → run-BDmTZPT7.js.map} +1 -1
  34. package/dist/{run-BFqPilTx.cjs → run-BTzmKup3.cjs} +2 -2
  35. package/dist/{run-BFqPilTx.cjs.map → run-BTzmKup3.cjs.map} +1 -1
  36. package/dist/{run-C61yKdb4.cjs → run-Bgr0F6xv.cjs} +5 -5
  37. package/dist/{run-C61yKdb4.cjs.map → run-Bgr0F6xv.cjs.map} +1 -1
  38. package/dist/{run-Be6_6_PJ.cjs → run-C2syKtAg.cjs} +7 -7
  39. package/dist/{run-Be6_6_PJ.cjs.map → run-C2syKtAg.cjs.map} +1 -1
  40. package/dist/{run-CfkP7AY1.js → run-C9Q5R-p9.js} +1 -1
  41. package/dist/{run-BJUijhbO.js → run-DoUk-2Xb.js} +2 -2
  42. package/dist/{run-BJUijhbO.js.map → run-DoUk-2Xb.js.map} +1 -1
  43. package/dist/{run-DZgSH5YE.js → run-kRFuEqeA.js} +14 -8
  44. package/dist/run-kRFuEqeA.js.map +1 -0
  45. package/dist/{run-Bk-PyLaA.cjs → run-w92b045L.cjs} +14 -8
  46. package/dist/run-w92b045L.cjs.map +1 -0
  47. package/dist/{snapshot-BApwIb2T.cjs → snapshot-B4eWToMC.cjs} +6 -6
  48. package/dist/{snapshot-BApwIb2T.cjs.map → snapshot-B4eWToMC.cjs.map} +1 -1
  49. package/dist/{snapshot-F8ShCYAC.js → snapshot-O36QFuVw.js} +6 -6
  50. package/dist/{snapshot-F8ShCYAC.js.map → snapshot-O36QFuVw.js.map} +1 -1
  51. package/dist/{status-D1O1SZi5.js → status-C5_IHiDc.js} +3 -3
  52. package/dist/{status-D1O1SZi5.js.map → status-C5_IHiDc.js.map} +1 -1
  53. package/dist/{status-CNvgGbU7.cjs → status-CteZmcug.cjs} +3 -3
  54. package/dist/{status-CNvgGbU7.cjs.map → status-CteZmcug.cjs.map} +1 -1
  55. package/dist/studioWorker.js +2 -2
  56. package/dist/{summary-D6hq3I9C.js → summary-B1aj8T_V.js} +3 -3
  57. package/dist/{summary-D6hq3I9C.js.map → summary-B1aj8T_V.js.map} +1 -1
  58. package/dist/{summary-D2g0ii83.cjs → summary-BuKuPfE_.cjs} +3 -3
  59. package/dist/{summary-D2g0ii83.cjs.map → summary-BuKuPfE_.cjs.map} +1 -1
  60. package/dist/{text-BBQbTT22.js → text-CaZG0KF2.js} +2 -2
  61. package/dist/{text-BBQbTT22.js.map → text-CaZG0KF2.js.map} +1 -1
  62. package/dist/{text-1NAngHDI.cjs → text-DVPYqR74.cjs} +2 -2
  63. package/dist/{text-1NAngHDI.cjs.map → text-DVPYqR74.cjs.map} +1 -1
  64. package/dist/{utils-DNOlQ-6B.js → utils--q06lp55.js} +4 -4
  65. package/dist/{utils-DNOlQ-6B.js.map → utils--q06lp55.js.map} +1 -1
  66. package/dist/{utils-N757uO9x.cjs → utils-Ckbz012p.cjs} +9 -3
  67. package/dist/{utils-N757uO9x.cjs.map → utils-Ckbz012p.cjs.map} +1 -1
  68. package/package.json +11 -10
  69. package/dist/config-1tiH3heq.cjs.map +0 -1
  70. package/dist/config-CbjbW2J5.js.map +0 -1
  71. package/dist/config-cP7k9YwA.js +0 -1
  72. package/dist/run-Bk-PyLaA.cjs.map +0 -1
  73. 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-Cwis9NRC.js";
4
- import { _ as write, a as readOutputFiles, c as createSpinner, g as read, l as withSpinner, m as exists, n as diffFiles } from "./utils-DNOlQ-6B.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-BBQbTT22.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-D8Vxs2q6.js";
7
- import { a as initDefaults, i as KUBB_PACKAGE_NAME, n as findConfigFile, r as KUBB_CONFIG_FILENAME, t as getConfigs } from "./config-CbjbW2J5.js";
8
- import { t as logCommandSummary } from "./summary-D6hq3I9C.js";
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$13 = defineWithTypes()({
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-BPi2eh0e.js");
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$12 = define({
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-BJUijhbO.js");
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-CfkP7AY1.js");
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$11 = studioCommand({
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-CfkP7AY1.js");
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$10 = studioCommand({
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$11.args,
306
+ args: command$12.args,
262
307
  async action(options) {
263
- const { login } = await import("./run-CfkP7AY1.js");
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$9 = studioCommand({
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-nDhk-eNv.js");
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$8 = define({
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-F8ShCYAC.js");
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$7 = studioCommand({
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$11.args,
372
+ args: command$12.args,
328
373
  async action(options) {
329
- const { startWorker } = await import("./background-nDhk-eNv.js");
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-nDhk-eNv.js");
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$6 = studioCommand({
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-nDhk-eNv.js");
395
+ const { getWorkerLogPath, getWorkerStatus } = await import("./background-B7oEWLZs.js");
351
396
  const { readCredentials } = await import("./credentials-C8tjAhTo.js");
352
- const { formatStatus } = await import("./status-D1O1SZi5.js");
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$5 = define({
370
- ...command$11,
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$7,
427
+ start: command$8,
383
428
  stop: stopCommand,
384
- connect: command$11,
385
- login: command$10,
386
- logout: command$9,
387
- status: command$6,
388
- snapshot: command$8
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$4 = define({
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-8K8KvqtO.js");
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 = [...packageManager.installCommand, ...packages];
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: "add-plugin",
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
- async function previewConfig({ details, source }) {
751
- try {
752
- const { applyConfigEdits } = await import("@kubb/studio");
753
- const result = applyConfigEdits({
754
- source,
755
- ...toConfigEdits(details)
756
- });
757
- const failed = result.outcomes.find((outcome) => !outcome.applied);
758
- if (failed) return { reason: failed.reason ?? "the config file was not changed" };
759
- const remaining = source.split("\n");
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
- } catch (error) {
766
- return { reason: getErrorMessage(error) };
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, adds it to
820
- * `kubb.config.ts`, offering to add the plugins it needs as well. Without an id, asks which one.
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$4({ id: input, configPath, yes, install }) {
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 === "plugin" ? foundConfig : void 0;
885
- if (manifest.kind === "plugin" && !configFile) throw new Diagnostics.Error({
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 ? await previewConfig({
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 { writeConfigEdits } = await import("@kubb/studio");
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 === "plugin") process$1.exitCode = 1;
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 ? "Plugin ready" : "Plugin configured" : "Plugin already configured", manual || dependenciesMissing ? "warning" : "success", [
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$4, "run");
984
- const command$3 = define({
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$4({
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
- async function buildConfig({ inputPath, outputPath, plugins }) {
1029
- const { applyConfigEdits } = await import("@kubb/studio");
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: "add-plugin",
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 === "add-plugin") throw new Diagnostics.Error({
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$3({ yes, input: inputFlag, output: outputFlag, plugins: pluginsFlag, dryRun }) {
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 = await buildConfig(choices);
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, await buildConfig(choices)), { message: `write ${KUBB_CONFIG_FILENAME}` }),
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$3, "run");
1324
- const command$2 = defineWithTypes()({
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$3({
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$2() {
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$2, "run");
1435
- const command$1 = define({
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$2()
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-cP7k9YwA.js"), import("./run-BPi2eh0e.js")]);
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) if (!tracked.some((entry) => entry.id === id)) logWarn(`${id} is not installed, or not in the registry.`);
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$13, {
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$13,
1665
- init: command$2,
1666
- validate: command$4,
1667
- mcp: command$12,
1668
- studio: command$5,
1669
- add: command$3,
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
- list: command$1
2848
+ remove: command$1,
2849
+ list: command$2
1672
2850
  },
1673
2851
  fallbackToEntry: true,
1674
2852
  renderHeader: isJsonOutput ? null : async (ctx) => {