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