@kubb/cli 5.14.5 → 5.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/bin/kubb.js +2 -1
  2. package/dist/{Telemetry-Dc79_cIA.js → Telemetry-B3edcFoB.js} +12 -86
  3. package/dist/Telemetry-B3edcFoB.js.map +1 -0
  4. package/dist/{Telemetry-Dc9DVxKQ.cjs → Telemetry-COWGqYmh.cjs} +14 -118
  5. package/dist/Telemetry-COWGqYmh.cjs.map +1 -0
  6. package/dist/{background-v5pDy3IJ.cjs → background-B24lIehn.cjs} +5 -6
  7. package/dist/{background-v5pDy3IJ.cjs.map → background-B24lIehn.cjs.map} +1 -1
  8. package/dist/{background-C877JFxE.js → background-D-7OGjvg.js} +1 -1
  9. package/dist/{background-3_KudPH-.js → background-_dKcGjM7.js} +5 -6
  10. package/dist/{background-3_KudPH-.js.map → background-_dKcGjM7.js.map} +1 -1
  11. package/dist/banner-BdTTDFkG.js +140 -0
  12. package/dist/banner-BdTTDFkG.js.map +1 -0
  13. package/dist/banner-wJFk-L-c.cjs +163 -0
  14. package/dist/banner-wJFk-L-c.cjs.map +1 -0
  15. package/dist/config-BL3HnFhb.js +1 -0
  16. package/dist/config-BWj9jBVj.cjs +62 -0
  17. package/dist/config-BWj9jBVj.cjs.map +1 -0
  18. package/dist/{config-CNqgDSu3.js → config-CQTxXvIG.js} +6 -52
  19. package/dist/config-CQTxXvIG.js.map +1 -0
  20. package/dist/{config-BG_Nr2CZ.cjs → config-CRrmj88c.cjs} +15 -62
  21. package/dist/config-CRrmj88c.cjs.map +1 -0
  22. package/dist/config-Uk3rbZfb.js +49 -0
  23. package/dist/config-Uk3rbZfb.js.map +1 -0
  24. package/dist/configFile-C0DIXNMD.js +993 -0
  25. package/dist/configFile-C0DIXNMD.js.map +1 -0
  26. package/dist/configFile-CoEpHdXG.cjs +1028 -0
  27. package/dist/configFile-CoEpHdXG.cjs.map +1 -0
  28. package/dist/{credentials-C8tjAhTo.js → credentials-B77iJx9H.js} +1 -1
  29. package/dist/{credentials-DDaKqtJn.cjs → credentials-DEsGwcGD.cjs} +3 -4
  30. package/dist/{credentials-DDaKqtJn.cjs.map → credentials-DEsGwcGD.cjs.map} +1 -1
  31. package/dist/{credentials-CZK6zPLv.js → credentials-osgjujE_.js} +3 -4
  32. package/dist/{credentials-CZK6zPLv.js.map → credentials-osgjujE_.js.map} +1 -1
  33. package/dist/env-C3xHRMez.js +1 -0
  34. package/dist/env-eu3XyKqJ.cjs +55 -0
  35. package/dist/env-eu3XyKqJ.cjs.map +1 -0
  36. package/dist/env-rv-VgSGN.js +34 -0
  37. package/dist/env-rv-VgSGN.js.map +1 -0
  38. package/dist/{errors-Cl69p_vI.cjs → errors-BponpdjN.cjs} +3 -4
  39. package/dist/{errors-Cl69p_vI.cjs.map → errors-BponpdjN.cjs.map} +1 -1
  40. package/dist/{errors-DmC83ALX.js → errors-wzqmMpMo.js} +3 -4
  41. package/dist/{errors-DmC83ALX.js.map → errors-wzqmMpMo.js.map} +1 -1
  42. package/dist/index.cjs +37 -2719
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +0 -2
  45. package/dist/index.d.ts +0 -2
  46. package/dist/index.js +35 -2713
  47. package/dist/index.js.map +1 -1
  48. package/dist/installWithSpinner-DpnpxHq5.js +49 -0
  49. package/dist/installWithSpinner-DpnpxHq5.js.map +1 -0
  50. package/dist/installWithSpinner-ONGfIVUJ.cjs +56 -0
  51. package/dist/installWithSpinner-ONGfIVUJ.cjs.map +1 -0
  52. package/dist/{text-CEyJhBnh.js → intro-9PB8sE8z.js} +8 -52
  53. package/dist/intro-9PB8sE8z.js.map +1 -0
  54. package/dist/{text-CC2xdpGL.cjs → intro-YPaf1Imr.cjs} +12 -80
  55. package/dist/intro-YPaf1Imr.cjs.map +1 -0
  56. package/dist/isCIEnvironment-D6xtAAZ2.js +14 -0
  57. package/dist/isCIEnvironment-D6xtAAZ2.js.map +1 -0
  58. package/dist/isCIEnvironment-DiVOMI5Q.cjs +20 -0
  59. package/dist/isCIEnvironment-DiVOMI5Q.cjs.map +1 -0
  60. package/dist/mascot--q1xjKw1.js +63 -0
  61. package/dist/mascot--q1xjKw1.js.map +1 -0
  62. package/dist/mascot-JSA0j3Q_.cjs +80 -0
  63. package/dist/mascot-JSA0j3Q_.cjs.map +1 -0
  64. package/dist/mcp-CYPOFKnP.cjs +20 -0
  65. package/dist/mcp-CYPOFKnP.cjs.map +1 -0
  66. package/dist/mcp-CxeZR5vb.js +21 -0
  67. package/dist/mcp-CxeZR5vb.js.map +1 -0
  68. package/dist/package-D3Yss9Bb.js +5 -0
  69. package/dist/package-D3Yss9Bb.js.map +1 -0
  70. package/dist/package-aX0q9SZr.cjs +10 -0
  71. package/dist/package-aX0q9SZr.cjs.map +1 -0
  72. package/dist/{registry-ov1RYcD4.js → registry-B3eYk4LN.js} +7 -67
  73. package/dist/registry-B3eYk4LN.js.map +1 -0
  74. package/dist/{registry-BInbtBm7.cjs → registry-YE1pFWL-.cjs} +4 -82
  75. package/dist/registry-YE1pFWL-.cjs.map +1 -0
  76. package/dist/{reporters-DQz--AdE.cjs → reporters-2cgjvXwR.cjs} +32 -40
  77. package/dist/{reporters-DQz--AdE.cjs.map → reporters-2cgjvXwR.cjs.map} +1 -1
  78. package/dist/{reporters-BtD41K-C.js → reporters-BM430HdQ.js} +19 -27
  79. package/dist/{reporters-BtD41K-C.js.map → reporters-BM430HdQ.js.map} +1 -1
  80. package/dist/{rolldown-runtime-CPE0t6oh.cjs → rolldown-runtime-C3-Zg-KD.cjs} +1 -2
  81. package/dist/{rolldown-runtime-CRm0XQPb.js → rolldown-runtime-xo6NMXYd.js} +1 -2
  82. package/dist/run-1H9TGT8v.js +77 -0
  83. package/dist/run-1H9TGT8v.js.map +1 -0
  84. package/dist/run-B4zvF9WY.cjs +233 -0
  85. package/dist/run-B4zvF9WY.cjs.map +1 -0
  86. package/dist/run-BYP_t9dR.cjs +324 -0
  87. package/dist/run-BYP_t9dR.cjs.map +1 -0
  88. package/dist/{run-1oXPIkli.cjs → run-Bb19PlwV.cjs} +44 -37
  89. package/dist/run-Bb19PlwV.cjs.map +1 -0
  90. package/dist/{run-B03V0Poj.cjs → run-BoO7H8RA.cjs} +49 -41
  91. package/dist/run-BoO7H8RA.cjs.map +1 -0
  92. package/dist/run-BrM7-NSV.js +181 -0
  93. package/dist/run-BrM7-NSV.js.map +1 -0
  94. package/dist/{run-UsAMlUyy.js → run-C0KI-NE3.js} +9 -9
  95. package/dist/{run-UsAMlUyy.js.map → run-C0KI-NE3.js.map} +1 -1
  96. package/dist/run-CCLPt4hr.js +1 -0
  97. package/dist/run-CCsdLz3X.js +385 -0
  98. package/dist/run-CCsdLz3X.js.map +1 -0
  99. package/dist/{run-YMSJnvYl.js → run-CbX6tpqB.js} +31 -23
  100. package/dist/run-CbX6tpqB.js.map +1 -0
  101. package/dist/run-CrM4jsix.cjs +80 -0
  102. package/dist/run-CrM4jsix.cjs.map +1 -0
  103. package/dist/run-CvFakE3B.cjs +185 -0
  104. package/dist/run-CvFakE3B.cjs.map +1 -0
  105. package/dist/run-D-nn9-Wq.js +319 -0
  106. package/dist/run-D-nn9-Wq.js.map +1 -0
  107. package/dist/{run-cBSr2F1q.cjs → run-D5Z6nfzK.cjs} +5 -6
  108. package/dist/{run-cBSr2F1q.cjs.map → run-D5Z6nfzK.cjs.map} +1 -1
  109. package/dist/{run-BUzdEUaE.cjs → run-DCqmf5W1.cjs} +10 -10
  110. package/dist/{run-BUzdEUaE.cjs.map → run-DCqmf5W1.cjs.map} +1 -1
  111. package/dist/{run-BK-A9JDf.js → run-DFXhBS64.js} +5 -6
  112. package/dist/{run-BK-A9JDf.js.map → run-DFXhBS64.js.map} +1 -1
  113. package/dist/run-DgGZOC_x.cjs +389 -0
  114. package/dist/run-DgGZOC_x.cjs.map +1 -0
  115. package/dist/{run-DDwdeHgU.js → run-KqdudiWQ.js} +26 -20
  116. package/dist/run-KqdudiWQ.js.map +1 -0
  117. package/dist/run-RN_31Q4u.js +228 -0
  118. package/dist/run-RN_31Q4u.js.map +1 -0
  119. package/dist/shared-C0EtrdUm.cjs +123 -0
  120. package/dist/shared-C0EtrdUm.cjs.map +1 -0
  121. package/dist/shared-Ct9stKpp.js +94 -0
  122. package/dist/shared-Ct9stKpp.js.map +1 -0
  123. package/dist/{snapshot-BrwynBfC.cjs → snapshot-D7Ytw8S0.cjs} +13 -14
  124. package/dist/{snapshot-BrwynBfC.cjs.map → snapshot-D7Ytw8S0.cjs.map} +1 -1
  125. package/dist/{snapshot-D2E7nhEf.js → snapshot-n7dJGCWv.js} +10 -11
  126. package/dist/{snapshot-D2E7nhEf.js.map → snapshot-n7dJGCWv.js.map} +1 -1
  127. package/dist/spinner-BZaln7O8.js +160 -0
  128. package/dist/spinner-BZaln7O8.js.map +1 -0
  129. package/dist/spinner-CfB0sIPy.cjs +208 -0
  130. package/dist/spinner-CfB0sIPy.cjs.map +1 -0
  131. package/dist/{status-CxzhvjKs.cjs → status-Bc0_4T41.cjs} +7 -8
  132. package/dist/{status-CxzhvjKs.cjs.map → status-Bc0_4T41.cjs.map} +1 -1
  133. package/dist/{status-BHIOMCM0.js → status-DxSkp1cH.js} +6 -7
  134. package/dist/{status-BHIOMCM0.js.map → status-DxSkp1cH.js.map} +1 -1
  135. package/dist/studio-BpYwgFb0.cjs +218 -0
  136. package/dist/studio-BpYwgFb0.cjs.map +1 -0
  137. package/dist/studio-mw6HaPkr.js +215 -0
  138. package/dist/studio-mw6HaPkr.js.map +1 -0
  139. package/dist/studioWorker.js +4 -5
  140. package/dist/{summary-B219y888.js → summary-CbmQKA8l.js} +5 -6
  141. package/dist/{summary-B219y888.js.map → summary-CbmQKA8l.js.map} +1 -1
  142. package/dist/{summary-BOYsqub3.cjs → summary-Clvl5Hxs.cjs} +7 -8
  143. package/dist/{summary-BOYsqub3.cjs.map → summary-Clvl5Hxs.cjs.map} +1 -1
  144. package/dist/table-BlRe9YTZ.cjs +15 -0
  145. package/dist/table-BlRe9YTZ.cjs.map +1 -0
  146. package/dist/table-CR7iZJGo.js +10 -0
  147. package/dist/table-CR7iZJGo.js.map +1 -0
  148. package/dist/text-BmiI4s6N.js +13 -0
  149. package/dist/text-BmiI4s6N.js.map +1 -0
  150. package/dist/text-Ep3AulAS.cjs +24 -0
  151. package/dist/text-Ep3AulAS.cjs.map +1 -0
  152. package/dist/time-3BKoci3G.cjs +82 -0
  153. package/dist/time-3BKoci3G.cjs.map +1 -0
  154. package/dist/time-C-wU7VhC.js +65 -0
  155. package/dist/time-C-wU7VhC.js.map +1 -0
  156. package/dist/utils-BdibacYq.js +244 -0
  157. package/dist/utils-BdibacYq.js.map +1 -0
  158. package/dist/utils-CO89mxRU.cjs +290 -0
  159. package/dist/utils-CO89mxRU.cjs.map +1 -0
  160. package/dist/utils-CbJ6hS_t.js +222 -0
  161. package/dist/utils-CbJ6hS_t.js.map +1 -0
  162. package/dist/utils-CcARPB45.cjs +286 -0
  163. package/dist/utils-CcARPB45.cjs.map +1 -0
  164. package/dist/validate-8ZcWXfMt.js +28 -0
  165. package/dist/validate-8ZcWXfMt.js.map +1 -0
  166. package/dist/validate-CMSeDpGT.cjs +27 -0
  167. package/dist/validate-CMSeDpGT.cjs.map +1 -0
  168. package/package.json +7 -7
  169. package/dist/Telemetry-Dc79_cIA.js.map +0 -1
  170. package/dist/Telemetry-Dc9DVxKQ.cjs.map +0 -1
  171. package/dist/config-BG_Nr2CZ.cjs.map +0 -1
  172. package/dist/config-CNqgDSu3.js.map +0 -1
  173. package/dist/config-DWDpFSn1.js +0 -1
  174. package/dist/registry-BInbtBm7.cjs.map +0 -1
  175. package/dist/registry-ov1RYcD4.js.map +0 -1
  176. package/dist/run-1oXPIkli.cjs.map +0 -1
  177. package/dist/run-B03V0Poj.cjs.map +0 -1
  178. package/dist/run-DDwdeHgU.js.map +0 -1
  179. package/dist/run-YMSJnvYl.js.map +0 -1
  180. package/dist/run-v5aW1jjQ.js +0 -1
  181. package/dist/text-CC2xdpGL.cjs.map +0 -1
  182. package/dist/text-CEyJhBnh.js.map +0 -1
  183. package/dist/utils-8E1nF4C1.cjs +0 -645
  184. package/dist/utils-8E1nF4C1.cjs.map +0 -1
  185. package/dist/utils-D8VN40bN.js +0 -536
  186. package/dist/utils-D8VN40bN.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,151 +1,13 @@
1
- import { t as __name } from "./rolldown-runtime-CRm0XQPb.js";
2
- import { t as getErrorMessage } from "./errors-DmC83ALX.js";
3
- import { a as getElapsedMs, i as formatMs, r as version, t as isDisabled } from "./Telemetry-Dc79_cIA.js";
4
- import { _ as write, a as readOutputFiles, c as createSpinner, g as read, l as withSpinner, m as exists, n as diffFiles, v as writeRaw } from "./utils-D8VN40bN.js";
5
- import { _ as isRichOutput, a as logOutro, c as logBlock, d as logStep, f as logWarn, i as logIntro, l as logError, r as isInteractiveRun, t as pluralize } from "./text-CEyJhBnh.js";
6
- import { a as installSpec, c as toRegistryId, f as wantsMascot, i as incompatibility, l as toShortId, n as closestIds, o as isOutdated, r as fetchRegistryIndex, s as registryUrl, t as bumpOf, u as getBanner } from "./registry-ov1RYcD4.js";
7
- import { a as KUBB_PACKAGE_NAME, i as KUBB_CONFIG_FILENAME, n as findConfigFile, o as initDefaults, r as BUNDLED_PACKAGES, t as getConfigs } from "./config-CNqgDSu3.js";
8
- import { t as logCommandSummary } from "./summary-B219y888.js";
1
+ import "./rolldown-runtime-xo6NMXYd.js";
2
+ import { t as version } from "./package-D3Yss9Bb.js";
3
+ import { t as isDisabled } from "./Telemetry-B3edcFoB.js";
4
+ import { r as wantsMascot, t as getBanner } from "./mascot--q1xjKw1.js";
5
+ import { n as dryRunId, r as logLevelArg, t as configArg } from "./shared-Ct9stKpp.js";
9
6
  import { styleText } from "node:util";
10
- import dryrun, { pluginId as dryRunId } from "@gunshi/plugin-dryrun";
11
- import { cli, define, defineWithTypes } from "gunshi";
12
- import process$1 from "node:process";
13
- import { x } from "tinyexec";
14
- import path from "node:path";
15
- import fs from "node:fs";
16
- import * as clack from "@clack/prompts";
17
- import { Diagnostics, Hookable, createKubb, isPackageName, readInstalledPackages } from "@kubb/core";
18
- import { parse } from "yuku-parser";
19
- //#region ../../internals/utils/src/casing.ts
20
- /**
21
- * Shared implementation for camelCase and PascalCase conversion.
22
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
23
- * and capitalizes each word according to `pascal`.
24
- *
25
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
26
- */
27
- function convert(text, pascal) {
28
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
29
- if (word.length > 1 && word === word.toUpperCase()) return word;
30
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
31
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
32
- }
33
- const cache = /* @__PURE__ */ new Map();
34
- const cacheLimit = 1e4;
35
- function toCamelOrPascal(text, pascal) {
36
- const key = `${pascal ? "P" : "c"}${text}`;
37
- const cached = cache.get(key);
38
- if (cached !== void 0) return cached;
39
- const result = convert(text, pascal);
40
- if (cache.size >= cacheLimit) cache.clear();
41
- cache.set(key, result);
42
- return result;
43
- }
44
- /**
45
- * Converts `text` to camelCase.
46
- *
47
- * @example Word boundaries
48
- * `camelCase('hello-world') // 'helloWorld'`
49
- *
50
- * @example With a prefix
51
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
52
- */
53
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
54
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
55
- }
56
- //#endregion
57
- //#region src/commands/shared.ts
58
- const configArg = { config: {
59
- type: "string",
60
- description: "Path to the Kubb config",
61
- short: "c"
62
- } };
63
- const yesArg = { yes: {
64
- type: "boolean",
65
- description: "Skip the confirmation",
66
- short: "y",
67
- default: false
68
- } };
69
- const logLevelArg = { logLevel: {
70
- type: "enum",
71
- choices: [
72
- "silent",
73
- "info",
74
- "verbose"
75
- ],
76
- description: "Info, silent or verbose",
77
- short: "l",
78
- default: "info",
79
- toKebab: true
80
- } };
81
- const studioConnectionArgs = {
82
- url: {
83
- type: "string",
84
- description: "Base URL of the Kubb Studio instance"
85
- },
86
- token: {
87
- type: "string",
88
- description: "Organization CI API key for `snapshot`. Defaults to KUBB_TOKEN"
89
- }
90
- };
91
- const studioPermissionArgs = {
92
- allowRead: {
93
- type: "boolean",
94
- description: "Read the source of files a generation produced. Asked for once per project when omitted",
95
- default: false,
96
- toKebab: true
97
- },
98
- allowReadAst: {
99
- type: "boolean",
100
- description: "Read the parsed spec (the Kubb AST). Asked for once per project when omitted",
101
- default: false,
102
- toKebab: true
103
- },
104
- allowWrite: {
105
- type: "boolean",
106
- description: "Write generated files to disk. Asked for once per project when omitted",
107
- default: false,
108
- toKebab: true
109
- },
110
- allowConfigEdit: {
111
- type: "boolean",
112
- description: "Let Studio change plugin options in kubb.config.ts. Asked for once per project when omitted",
113
- default: false,
114
- toKebab: true
115
- },
116
- allowInstall: {
117
- type: "boolean",
118
- description: "Let Studio install extensions from the registry with kubb add; needs --allow-config-edit too. Asked for once per project when omitted",
119
- default: false,
120
- toKebab: true
121
- },
122
- allowUpdate: {
123
- type: "boolean",
124
- description: "Let Studio update installed extensions with kubb update. Asked for once per project when omitted",
125
- default: false,
126
- toKebab: true
127
- },
128
- allowRemove: {
129
- type: "boolean",
130
- description: "Let Studio remove extensions with kubb remove, which edits kubb.config.ts and uninstalls packages; needs --allow-config-edit too. Asked for once per project when omitted",
131
- default: false,
132
- toKebab: true
133
- },
134
- allowSaveInput: {
135
- type: "boolean",
136
- description: "Let Studio save the spec it sends as the input in kubb.config.ts; needs --allow-config-edit too. Asked for once per project when omitted",
137
- default: false,
138
- toKebab: true
139
- },
140
- allowExec: {
141
- type: "boolean",
142
- description: "Run the formatter, the linter, and output.postGenerate after a generation",
143
- default: false,
144
- toKebab: true
145
- }
146
- };
147
- //#endregion
148
- //#region src/commands/generate.ts
7
+ import dryrun from "@gunshi/plugin-dryrun";
8
+ import { cli, defineWithTypes } from "gunshi";
9
+ import { Diagnostics } from "@kubb/core";
10
+ // src/commands/generate.ts
149
11
  const REPORTER_NAMES = [
150
12
  "cli",
151
13
  "json",
@@ -173,7 +35,7 @@ function resolveLogLevel({ verbose, silent, logLevel }) {
173
35
  if (silent) return "silent";
174
36
  return logLevel;
175
37
  }
176
- const command$14 = defineWithTypes()({
38
+ const command = defineWithTypes()({
177
39
  name: "generate",
178
40
  rendering: { header: null },
179
41
  description: "Generate types, clients, hooks, and schemas from an OpenAPI specification.",
@@ -224,7 +86,7 @@ const command$14 = defineWithTypes()({
224
86
  async run(ctx) {
225
87
  const { values } = ctx;
226
88
  const logLevel = resolveLogLevel(values);
227
- const { run } = await import("./run-YMSJnvYl.js");
89
+ const { run } = await import("./run-CbX6tpqB.js");
228
90
  await run({
229
91
  input: values.input,
230
92
  configPath: values.config,
@@ -235,242 +97,7 @@ const command$14 = defineWithTypes()({
235
97
  });
236
98
  }
237
99
  });
238
- //#endregion
239
- //#region src/commands/mcp.ts
240
- /** The runner is imported when the command runs, so listing `kubb --help` never loads `@kubb/mcp`. */
241
- const command$13 = define({
242
- name: "mcp",
243
- description: "Start an MCP server for code generation from an AI assistant.",
244
- examples: [
245
- "kubb mcp",
246
- "# Then add to your MCP client config:",
247
- "# { \"mcpServers\": { \"kubb\": { \"command\": \"npx\", \"args\": [\"kubb\", \"mcp\"] } } }"
248
- ].join("\n"),
249
- rendering: { header: null },
250
- async run() {
251
- const { run } = await import("./run-BK-A9JDf.js");
252
- await run();
253
- }
254
- });
255
- //#endregion
256
- //#region src/commands/studio/studioCommand.ts
257
- /**
258
- * Defines a `kubb studio` subcommand. The runner, and with it `@kubb/studio`, loads only when the
259
- * command runs, so `kubb --help` never does.
260
- */
261
- function studioCommand({ action, block, ...definition }) {
262
- return define({
263
- ...definition,
264
- toKebab: true,
265
- async run({ values }) {
266
- const { runStudio } = await import("./run-v5aW1jjQ.js");
267
- await runStudio(values, action, { block });
268
- }
269
- });
270
- }
271
- //#endregion
272
- //#region src/commands/studio/connect.ts
273
- const command$12 = studioCommand({
274
- name: "connect",
275
- description: "Connect this project to Kubb Studio and generate from the browser.",
276
- examples: [
277
- "kubb studio connect",
278
- "kubb studio connect --allow-read",
279
- "kubb studio connect --allow-write --allow-exec",
280
- "kubb studio connect --url http://localhost:3000"
281
- ].join("\n"),
282
- args: {
283
- ...configArg,
284
- ...studioConnectionArgs,
285
- ...studioPermissionArgs,
286
- open: {
287
- type: "boolean",
288
- description: "Open the approval page in a browser while pairing",
289
- default: true,
290
- negatable: true
291
- },
292
- ...logLevelArg
293
- },
294
- block: true,
295
- async action(options) {
296
- const { connect } = await import("./run-v5aW1jjQ.js");
297
- await connect(options);
298
- }
299
- });
300
- //#endregion
301
- //#region src/commands/studio/login.ts
302
- const command$11 = studioCommand({
303
- name: "login",
304
- description: "Pair this machine with Kubb Studio without connecting.",
305
- examples: ["kubb studio login", "kubb studio login --url http://localhost:3000"].join("\n"),
306
- args: command$12.args,
307
- async action(options) {
308
- const { login } = await import("./run-v5aW1jjQ.js");
309
- await login(options);
310
- }
311
- });
312
- //#endregion
313
- //#region src/commands/studio/logout.ts
314
- const command$10 = studioCommand({
315
- name: "logout",
316
- description: "Forget the stored Kubb Studio token.",
317
- examples: "kubb studio logout",
318
- args: {},
319
- async action() {
320
- const { stopWorker } = await import("./background-C877JFxE.js");
321
- const { clearCredentials } = await import("./credentials-C8tjAhTo.js");
322
- await stopWorker();
323
- await clearCredentials();
324
- console.log("Signed out of Kubb Studio.");
325
- }
326
- });
327
- //#endregion
328
- //#region src/commands/studio/snapshot.ts
329
- const command$9 = define({
330
- name: "snapshot",
331
- description: "Generate and publish a Kubb Studio snapshot from CI.",
332
- examples: ["kubb studio snapshot", "kubb studio snapshot --json"].join("\n"),
333
- toKebab: true,
334
- args: {
335
- ...configArg,
336
- ...studioConnectionArgs,
337
- id: {
338
- type: "string",
339
- description: "Stable identity for the CI agent, such as a pull request. Auto-detected on GitHub Actions, GitLab CI, Bitbucket Pipelines and CircleCI"
340
- },
341
- baseId: {
342
- type: "string",
343
- description: "Identity of another CI agent, such as the base branch's, to also compare this snapshot with. Auto-detected on GitHub pull requests and GitLab merge requests",
344
- toKebab: true
345
- },
346
- name: {
347
- type: "string",
348
- description: "Package name for the generated tarball. Defaults to the name in package.json"
349
- },
350
- packageVersion: {
351
- type: "string",
352
- description: "Package version for the generated tarball. Defaults to the version in package.json",
353
- toKebab: true
354
- },
355
- json: {
356
- type: "boolean",
357
- description: "Print the result as one JSON object instead of a summary",
358
- default: false
359
- },
360
- ...logLevelArg
361
- },
362
- async run(ctx) {
363
- const { runner } = await import("./snapshot-D2E7nhEf.js");
364
- await runner(ctx);
365
- }
366
- });
367
- //#endregion
368
- //#region src/commands/studio/background.ts
369
- const command$8 = studioCommand({
370
- name: "start",
371
- description: "Keep this project connected to Kubb Studio in the background.",
372
- args: command$12.args,
373
- async action(options) {
374
- const { startWorker } = await import("./background-C877JFxE.js");
375
- await startWorker(options);
376
- }
377
- });
378
- const stopCommand = define({
379
- name: "stop",
380
- description: "Stop this project's background connection and keep its pairing.",
381
- async run() {
382
- const { stopWorker } = await import("./background-C877JFxE.js");
383
- await stopWorker();
384
- console.log("Background connection stopped.");
385
- }
386
- });
387
- //#endregion
388
- //#region src/commands/studio/status.ts
389
- const command$7 = studioCommand({
390
- name: "status",
391
- description: "Show the project worker, Kubb Studio pairing, and saved permissions.",
392
- examples: "kubb studio status",
393
- args: { url: studioConnectionArgs.url },
394
- async action(options) {
395
- const { getWorkerLogPath, getWorkerStatus } = await import("./background-C877JFxE.js");
396
- const { readCredentials } = await import("./credentials-C8tjAhTo.js");
397
- const { formatStatus } = await import("./status-BHIOMCM0.js");
398
- const [worker, credentials] = await Promise.all([getWorkerStatus(), readCredentials()]);
399
- console.log([
400
- "",
401
- ...formatStatus({
402
- studioUrl: options.studioUrl,
403
- credentials,
404
- worker,
405
- project: process$1.cwd(),
406
- logPath: getWorkerLogPath()
407
- }),
408
- ""
409
- ].join("\n"));
410
- }
411
- });
412
- //#endregion
413
- //#region src/commands/studio/index.ts
414
- const command$6 = define({
415
- ...command$12,
416
- name: "studio",
417
- description: "Connect this project to Kubb Studio, or manage its pairing and snapshots.",
418
- examples: [
419
- "kubb studio",
420
- "kubb studio --allow-read",
421
- "kubb studio login",
422
- "kubb studio status",
423
- "kubb studio logout",
424
- "kubb studio snapshot"
425
- ].join("\n"),
426
- subCommands: {
427
- start: command$8,
428
- stop: stopCommand,
429
- connect: command$12,
430
- login: command$11,
431
- logout: command$10,
432
- status: command$7,
433
- snapshot: command$9
434
- }
435
- });
436
- /**
437
- * Sends `kubb studio --flag value` to `kubb studio connect`, the same runner. gunshi picks a
438
- * subcommand from the first token that is not a flag, so `--url http://…` would read the URL as one.
439
- */
440
- function routeStudioFlags(args) {
441
- const [name, next] = args;
442
- if (name !== "studio" || !next?.startsWith("-") || next === "--help" || next === "-h") return args;
443
- return [
444
- "studio",
445
- "connect",
446
- ...args.slice(1)
447
- ];
448
- }
449
- //#endregion
450
- //#region src/commands/validate.ts
451
- /** The runner is imported when the command runs, so listing `kubb --help` never loads `@kubb/adapter-oas`. */
452
- const command$5 = define({
453
- name: "validate",
454
- description: "Check a specification with the configured adapter before generating.",
455
- examples: ["kubb validate ./openapi.yaml", "kubb validate https://petstore3.swagger.io/api/v3/openapi.json"].join("\n"),
456
- args: {
457
- ...configArg,
458
- input: {
459
- type: "positional",
460
- description: "Path or URL to the specification to validate",
461
- required: true
462
- }
463
- },
464
- async run({ values }) {
465
- const { run } = await import("./run-UsAMlUyy.js");
466
- await run({
467
- input: values.input,
468
- configPath: values.config
469
- });
470
- }
471
- });
472
- //#endregion
473
- //#region src/deprecatedFlags.ts
100
+ // src/deprecatedFlags.ts
474
101
  /**
475
102
  * Maps each camelCase flag removed in favor of kebab-case (kubb-labs/kubb#4046) to its
476
103
  * replacement. Drop this table, and the rewrite it drives, once the deprecation window ends.
@@ -502,2323 +129,20 @@ function resolveDeprecatedFlags(args) {
502
129
  return `--${replacement}${match?.[2] ?? ""}`;
503
130
  });
504
131
  }
505
- //#endregion
506
- //#region ../../internals/shared/src/init.ts
507
- /** The `kubb.config.ts` `kubb init` writes before `kubb add` puts each plugin in. */
508
- function generateConfigFile({ inputPath, outputPath }) {
509
- return `import { defineConfig } from 'kubb/config'
510
-
511
- export default defineConfig({
512
- input: '${inputPath}',
513
- output: {
514
- path: '${outputPath}',
515
- clean: true,
516
- },
517
- plugins: [],
518
- })
519
- `;
520
- }
521
- //#endregion
522
- //#region ../../internals/shared/src/configFile/ast.ts
523
- /** Node types the config editor reads from the yuku AST. */
524
- const nodeTypes = {
525
- arrayExpression: "ArrayExpression",
526
- arrowFunctionExpression: "ArrowFunctionExpression",
527
- assignmentExpression: "AssignmentExpression",
528
- blockStatement: "BlockStatement",
529
- callExpression: "CallExpression",
530
- exportDefaultDeclaration: "ExportDefaultDeclaration",
531
- expressionStatement: "ExpressionStatement",
532
- functionExpression: "FunctionExpression",
533
- identifier: "Identifier",
534
- importDeclaration: "ImportDeclaration",
535
- importSpecifier: "ImportSpecifier",
536
- literal: "Literal",
537
- memberExpression: "MemberExpression",
538
- objectExpression: "ObjectExpression",
539
- objectPattern: "ObjectPattern",
540
- property: "Property",
541
- returnStatement: "ReturnStatement",
542
- spreadElement: "SpreadElement",
543
- tSAsExpression: "TSAsExpression",
544
- tSSatisfiesExpression: "TSSatisfiesExpression",
545
- templateLiteral: "TemplateLiteral",
546
- unaryExpression: "UnaryExpression",
547
- variableDeclaration: "VariableDeclaration"
548
- };
549
- const commentTypes = { line: "Line" };
550
- const propertyKinds = { init: "init" };
551
- const unaryOperators = {
552
- minus: "-",
553
- plus: "+"
554
- };
555
- const diagnosticSeverities = { error: "error" };
556
- /**
557
- * Parses `source` as a TypeScript module. Never throws: a syntax error, or a native parser that
558
- * cannot load on this platform, comes back as a `reason`.
559
- */
560
- function parseSource(source) {
561
- try {
562
- const { program, comments, diagnostics } = parse(source, {
563
- lang: "ts",
564
- sourceType: "module",
565
- preserveParens: false
566
- });
567
- if (diagnostics.some((diagnostic) => diagnostic.severity === diagnosticSeverities.error)) return { reason: "the config file could not be parsed" };
568
- return {
569
- program,
570
- comments
571
- };
572
- } catch (error) {
573
- return { reason: `the config parser could not load on this platform: ${getErrorMessage(error)}` };
574
- }
575
- }
576
- /** Parses `source` as one expression, such as a commented-out `pluginTs({ ... })` call. */
577
- function parseExpression(source) {
578
- const parsed = parseSource(source);
579
- if ("reason" in parsed) return void 0;
580
- const [statement] = parsed.program.body;
581
- return parsed.program.body.length === 1 && statement?.type === nodeTypes.expressionStatement ? statement.expression : void 0;
582
- }
583
- //#endregion
584
- //#region ../../internals/shared/src/configFile/types.ts
585
- const extensionKinds = {
586
- plugin: "plugin",
587
- adapter: "adapter",
588
- parser: "parser"
589
- };
590
- const ConfigEditOperation = {
591
- SET_OPTION: "set-option",
592
- REMOVE_OPTION: "remove-option",
593
- ADD_PLUGIN: "add-plugin",
594
- REMOVE_EXTENSION: "remove-extension",
595
- DISABLE_PLUGIN: "disable-plugin",
596
- ENABLE_PLUGIN: "enable-plugin",
597
- SET_INPUT: "set-input",
598
- SET_OUTPUT: "set-output",
599
- REMOVE_OUTPUT: "remove-output"
600
- };
601
- //#endregion
602
- //#region ../../internals/shared/src/configFile/rules.ts
603
- /** `@kubb/plugin-ts` becomes `plugin-ts`, the `name` Kubb plugin factories use. */
604
- function toPluginName(packageName) {
605
- return packageName.split("/").pop() ?? packageName;
606
- }
607
- /** `@kubb/plugin-react-query` becomes `pluginReactQuery`, the export a plugin package uses by convention. */
608
- function toExportName(packageName) {
609
- return camelCase(toPluginName(packageName));
610
- }
611
- const KUBB_PLUGIN_SPECIFIER = /^@kubb\/plugin-[\w.-]+$/;
612
- /** Only a `@kubb/plugin-*` package or an installed extension of `kind` may be imported, never a path or URL. */
613
- function isAllowedPlugin({ packageName, extensions, kind = extensionKinds.plugin }) {
614
- if (!isPackageName(packageName)) return false;
615
- return kind === extensionKinds.plugin && KUBB_PLUGIN_SPECIFIER.test(packageName) || extensions.get(packageName)?.manifest.kind === kind;
616
- }
617
- const UNSAFE_KEYS = /* @__PURE__ */ new Set([
618
- "__proto__",
619
- "constructor",
620
- "prototype"
621
- ]);
622
- /** Whether `key` can be an option key without reaching the object's prototype. */
623
- function isSafeKey(key) {
624
- return !UNSAFE_KEYS.has(key);
625
- }
626
- function isPlainObject(value) {
627
- if (typeof value !== "object" || value === null) return false;
628
- const prototype = Object.getPrototypeOf(value);
629
- return prototype === null || prototype === Object.prototype;
630
- }
631
- /** Whether `value` survives a JSON round trip, so an edit never writes a lossy copy into the file. */
632
- function isOptionValue(value) {
633
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
634
- if (typeof value === "number") return Number.isFinite(value);
635
- if (Array.isArray(value)) return value.every(isOptionValue);
636
- if (isPlainObject(value)) return Object.entries(value).every(([key, entry]) => isSafeKey(key) && isOptionValue(entry));
637
- return false;
638
- }
639
- /** The root `output` members Studio may set, with the values each takes. */
640
- const OUTPUT_VALUES = {
641
- barrel: (value) => value === false || isPlainObject(value) && Object.keys(value).length === 1 && (value.type === "named" || value.type === "all"),
642
- format: (value) => value === false || [
643
- "auto",
644
- "prettier",
645
- "biome",
646
- "oxfmt"
647
- ].includes(value),
648
- lint: (value) => value === false || [
649
- "auto",
650
- "eslint",
651
- "biome",
652
- "oxlint"
653
- ].includes(value),
654
- clean: (value) => typeof value === "boolean",
655
- defaultBanner: (value) => value === false || value === "simple" || value === "full"
656
- };
657
- function isOutputKey(key) {
658
- return Object.hasOwn(OUTPUT_VALUES, key);
659
- }
660
- function isOutputValue({ key, value }) {
661
- return isOutputKey(key) && OUTPUT_VALUES[key](value);
662
- }
663
- function pickOutput(output) {
664
- return Object.fromEntries(Object.entries(output).filter(([key]) => isOutputKey(key)));
665
- }
666
- //#endregion
667
- //#region ../../internals/shared/src/configFile/text.ts
668
- /**
669
- * A valid JavaScript identifier, so an import name can only ever print as `import { name } from`,
670
- * never as source that breaks out of the import statement.
671
- */
672
- const IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/;
673
- const quotes = {
674
- single: `'`,
675
- double: `"`
676
- };
677
- /** `source` with the text between `start` and `end` replaced by `text`. */
678
- function replaceText(source, start, end, text = "") {
679
- return `${source.slice(0, start)}${text}${source.slice(end)}`;
680
- }
681
- /** The 1-based line and 0-based column of `offset` in `source`. */
682
- function getPosition(source, offset) {
683
- return {
684
- line: source.slice(0, offset).split("\n").length,
685
- column: offset - source.lastIndexOf("\n", offset - 1) - 1
686
- };
687
- }
688
- /** Writes `value` as a single-line literal with the file's quote style. */
689
- function printValue(value, quote) {
690
- if (typeof value === "string") {
691
- const escaped = JSON.stringify(value).slice(1, -1);
692
- return quote === quotes.double ? `"${escaped}"` : `'${escaped.replaceAll(`\\"`, `"`).replaceAll(`'`, `\\'`)}'`;
693
- }
694
- if (value === null || typeof value !== "object") return String(value);
695
- if (Array.isArray(value)) return `[${value.map((item) => printValue(item, quote)).join(", ")}]`;
696
- const entries = Object.entries(value);
697
- return entries.length ? `{ ${entries.map(([key, item]) => `${printKey(key, quote)}: ${printValue(item, quote)}`).join(", ")} }` : "{}";
698
- }
699
- /** An object key as source text: bare when it is an identifier, quoted otherwise. */
700
- function printKey(key, quote) {
701
- return IDENTIFIER_PATTERN.test(key) ? key : printValue(key, quote);
702
- }
703
- /** `value` wrapped in one object per key of `path`: `wrapPath(['enum', 'type'], 'enum')` is `{ enum: { type: 'enum' } }`. */
704
- function wrapPath(path, value) {
705
- return path.reduceRight((inner, key) => ({ [key]: inner }), value);
706
- }
707
- /**
708
- * Adds `text` as the last item of an object or array literal, following its layout: on a line of
709
- * its own with the last item's indentation when the literal spans lines, inline otherwise.
710
- */
711
- function appendItem(source, container, text) {
712
- const { start, end } = container;
713
- const last = (container.type === nodeTypes.objectExpression ? container.properties : container.elements).filter((item) => item !== null).at(-1);
714
- if (!last) {
715
- const inner = source.slice(start + 1, end - 1);
716
- if (inner.trim()) return replaceText(source, start + 1, start + 1, /^\s/.test(inner) ? `${text},` : `${text}, `);
717
- return replaceText(source, start, end, container.type === nodeTypes.objectExpression ? `{ ${text} }` : `[${text}]`);
718
- }
719
- const between = source.slice(last.end, end - 1);
720
- if (!between.includes("\n")) return replaceText(source, last.end, last.end, `, ${text}`);
721
- const lineStart = source.lastIndexOf("\n", last.start - 1) + 1;
722
- const indent = source.slice(lineStart).match(/^[ \t]*/)?.[0] ?? "";
723
- const lineEnd = source.indexOf("\n", last.end);
724
- const comma = /^\s*,/.test(between);
725
- return `${source.slice(0, last.end)}${comma ? "" : ","}${source.slice(last.end, lineEnd)}\n${indent}${text}${comma ? "," : ""}${source.slice(lineEnd)}`;
726
- }
727
- /** Drops the item at `index` from an object or array literal, with its own lines when it sits on them alone. */
728
- function removeItem(source, node, index) {
729
- const items = node.type === nodeTypes.objectExpression ? node.properties : node.elements.filter((element) => element !== null);
730
- if (items.length === 1) return replaceText(source, node.start, node.end, node.type === nodeTypes.objectExpression ? "{}" : "[]");
731
- const { start, end } = items[index];
732
- const lineStart = source.lastIndexOf("\n", start - 1) + 1;
733
- const nextNewline = source.indexOf("\n", end);
734
- const lineEnd = nextNewline === -1 ? source.length : nextNewline;
735
- if (!source.slice(lineStart, start).trim() && /^\s*,?\s*(\/\/.*)?$/.test(source.slice(end, lineEnd))) return replaceText(source, lineStart, Math.min(lineEnd + 1, source.length));
736
- const next = items[index + 1];
737
- return next ? replaceText(source, start, next.start) : replaceText(source, items[index - 1].end, end);
738
- }
739
- //#endregion
740
- //#region ../../internals/shared/src/configFile/configFile.ts
741
- /** The marker `disable-plugin` writes, with the block's line count so `enable-plugin` restores exactly those lines. */
742
- const DISABLED_MARKER_PATTERN = /^kubb:disabled (.+)\s+(\d+)$/;
743
- const OPERATIONS = new Set(Object.values(ConfigEditOperation));
744
- const OPTION_OPERATIONS = /* @__PURE__ */ new Set([ConfigEditOperation.SET_OPTION, ConfigEditOperation.REMOVE_OPTION]);
745
- const OUTPUT_OPERATIONS = /* @__PURE__ */ new Set([ConfigEditOperation.SET_OUTPUT, ConfigEditOperation.REMOVE_OUTPUT]);
746
- const KINDS = new Set(Object.values(extensionKinds));
747
- /** Identifiers the editor matches by name. */
748
- const identifiers = {
749
- defineConfig: "defineConfig",
750
- require: "require",
751
- module: "module",
752
- exports: "exports"
753
- };
754
- const moduleKinds = {
755
- esm: "esm",
756
- cjs: "cjs"
132
+ // src/index.ts
133
+ /** The subcommands besides `generate`, imported only when the run needs them, so `kubb generate` never loads their code. */
134
+ const commandLoaders = {
135
+ init: () => import("./run-CCsdLz3X.js"),
136
+ validate: () => import("./validate-8ZcWXfMt.js"),
137
+ mcp: () => import("./mcp-CxeZR5vb.js"),
138
+ studio: () => import("./studio-mw6HaPkr.js"),
139
+ add: () => import("./run-D-nn9-Wq.js"),
140
+ update: () => import("./run-RN_31Q4u.js"),
141
+ remove: () => import("./run-BrM7-NSV.js"),
142
+ list: () => import("./run-1H9TGT8v.js")
757
143
  };
758
- function isNonEmptyString(value) {
759
- return typeof value === "string" && value !== "";
760
- }
761
- /** Edits cross the wire, so their shape is checked before anything reads them. */
762
- function validateEdit(edit) {
763
- const { operation, plugin, config, path, kind, input } = typeof edit === "object" && edit !== null ? edit : {};
764
- if (typeof operation !== "string" || !OPERATIONS.has(operation)) return `unknown operation ${JSON.stringify(operation)}`;
765
- if (config !== void 0 && typeof config !== "string" && !Number.isSafeInteger(config)) return "config must name an entry or give its index";
766
- if (operation === ConfigEditOperation.SET_INPUT) return typeof input === "string" && input.trim() !== "" ? void 0 : "the edit names no input";
767
- const isOption = OPTION_OPERATIONS.has(operation);
768
- const isOutput = OUTPUT_OPERATIONS.has(operation);
769
- if (!isOutput && !isNonEmptyString(plugin)) return "the edit names no plugin";
770
- if (kind !== void 0) {
771
- if (typeof kind !== "string" || !KINDS.has(kind)) return `unknown kind ${JSON.stringify(kind)}`;
772
- const takesKind = operation === ConfigEditOperation.ADD_PLUGIN || operation === ConfigEditOperation.REMOVE_EXTENSION;
773
- if (takesKind && kind === extensionKinds.adapter) return `${operation} takes a plugin or a parser, since a config has one adapter`;
774
- if (!isOption && !takesKind) return `${operation} edits the plugins array, so it takes no kind`;
775
- }
776
- if (!isOption && !isOutput) return void 0;
777
- if (!Array.isArray(path) || !path.every(isNonEmptyString)) return "path must be a list of option names";
778
- if (path.length === 0) return "no option path given";
779
- const unsafe = path.find((key) => !isSafeKey(key));
780
- if (unsafe !== void 0) return `${unsafe} cannot be used as an option name`;
781
- if (isOutput && !isOutputKey(path[0])) return `output.${path[0]} can only be changed in the config file`;
782
- }
783
- /** Steps through `as`/`satisfies` and `() => ...` wrappers to the config object. */
784
- function unwrapValue(node) {
785
- if (!node) return void 0;
786
- if (node.type === nodeTypes.tSAsExpression || node.type === nodeTypes.tSSatisfiesExpression) return unwrapValue(node.expression);
787
- if (node.type !== nodeTypes.arrowFunctionExpression && node.type !== nodeTypes.functionExpression) return node;
788
- if (node.body?.type !== nodeTypes.blockStatement) return unwrapValue(node.body);
789
- const returned = node.body.body.find((statement) => statement.type === nodeTypes.returnStatement);
790
- return unwrapValue(returned?.type === nodeTypes.returnStatement ? returned.argument : void 0);
791
- }
792
- function isModuleExports(node) {
793
- return node.type === nodeTypes.memberExpression && !node.computed && node.object.type === nodeTypes.identifier && node.object.name === identifiers.module && node.property.type === nodeTypes.identifier && node.property.name === identifiers.exports;
794
- }
795
- /** The exported config value and how the file exports it. */
796
- function findExport(program) {
797
- for (const node of program.body) {
798
- if (node.type === nodeTypes.exportDefaultDeclaration) return {
799
- exported: node.declaration,
800
- kind: moduleKinds.esm
801
- };
802
- if (node.type === nodeTypes.expressionStatement && node.expression.type === nodeTypes.assignmentExpression && isModuleExports(node.expression.left)) return {
803
- exported: node.expression.right,
804
- kind: moduleKinds.cjs
805
- };
806
- }
807
- return {
808
- exported: void 0,
809
- kind: moduleKinds.esm
810
- };
811
- }
812
- /** Every config object in `export default` or `module.exports = defineConfig(...)`, or why the file is unmanaged. */
813
- function findConfigs(source) {
814
- const parsed = parseSource(source);
815
- if ("reason" in parsed) return parsed;
816
- const { exported, kind } = findExport(parsed.program);
817
- const value = unwrapValue(exported);
818
- if (!value) return { reason: "no `export default` or `module.exports` found" };
819
- if (value.type !== nodeTypes.callExpression || value.callee.type !== nodeTypes.identifier || value.callee.name !== identifiers.defineConfig) return { reason: "the exported config is not a defineConfig(...) call" };
820
- const argument = unwrapValue(value.arguments[0]);
821
- if (!argument) return { reason: "defineConfig(...) was called without a config" };
822
- const entries = argument.type === nodeTypes.arrayExpression ? argument.elements.map((element) => unwrapValue(element)) : [argument];
823
- const configs = entries.filter((entry) => entry?.type === nodeTypes.objectExpression);
824
- if (configs.length !== entries.length) return { reason: "config is not an object literal" };
825
- return {
826
- ...parsed,
827
- configs,
828
- kind
829
- };
830
- }
831
- function findConfigEntry(configs, ref) {
832
- return typeof ref === "string" ? configs.find((entry) => getConfigName(entry) === ref) : configs[ref ?? 0];
833
- }
834
- function getKey(entry) {
835
- if (entry.type !== nodeTypes.property || entry.computed) return void 0;
836
- if (entry.key.type === nodeTypes.identifier) return entry.key.name;
837
- return entry.key.type === nodeTypes.literal && typeof entry.key.value === "string" ? entry.key.value : void 0;
838
- }
839
- /** The last one wins when the key repeats, as at runtime. */
840
- function findProperty(node, key) {
841
- const index = node.properties.findLastIndex((entry) => getKey(entry) === key);
842
- return index === -1 ? void 0 : {
843
- member: node.properties[index],
844
- index
845
- };
846
- }
847
- function getValue(node, key) {
848
- const member = findProperty(node, key)?.member;
849
- return member?.type === nodeTypes.property ? member.value : member;
850
- }
851
- function getConfigName(config) {
852
- const name = getValue(config, "name");
853
- return name?.type === nodeTypes.literal && typeof name.value === "string" ? name.value : void 0;
854
- }
855
- function isOverridden(node, index) {
856
- return node.properties.slice(index + 1).some((entry) => entry.type === nodeTypes.spreadElement);
857
- }
858
- /** `key: value`, not a method, getter, setter, or `{ key }` shorthand. */
859
- function isPlainProperty(member) {
860
- return !member.method && !member.shorthand && member.kind === propertyKinds.init;
861
- }
862
- /** A literal's value, `undefined` for code. */
863
- function readLiteral(node) {
864
- switch (node?.type) {
865
- case nodeTypes.literal: return "regex" in node || "bigint" in node ? void 0 : node.value;
866
- case nodeTypes.templateLiteral: return node.expressions.length ? void 0 : node.quasis[0]?.value.cooked ?? "";
867
- case nodeTypes.unaryExpression: {
868
- const value = readLiteral(node.argument);
869
- if (typeof value !== "number") return void 0;
870
- if (node.operator === unaryOperators.minus) return -value;
871
- if (node.operator === unaryOperators.plus) return value;
872
- return;
873
- }
874
- case nodeTypes.arrayExpression: {
875
- const values = node.elements.map((element) => readLiteral(element));
876
- return values.every((value) => value !== void 0) ? values : void 0;
877
- }
878
- case nodeTypes.objectExpression: {
879
- const entries = {};
880
- for (const entry of node.properties) {
881
- if (entry.type !== nodeTypes.property) return void 0;
882
- const key = getKey(entry);
883
- const value = readLiteral(entry.value);
884
- if (key === void 0 || !isSafeKey(key) || value === void 0) return void 0;
885
- entries[key] = value;
886
- }
887
- return entries;
888
- }
889
- }
890
- }
891
- function getRequirePath(node) {
892
- if (node?.type !== nodeTypes.callExpression || node.callee.type !== nodeTypes.identifier || node.callee.name !== identifiers.require) return void 0;
893
- const [argument] = node.arguments;
894
- return argument?.type === nodeTypes.literal && typeof argument.value === "string" ? argument.value : void 0;
895
- }
896
- /** `import { exported as local } from '...'`. A default or namespace import has no exported name, so its local name stands in. */
897
- function importBindings(statement) {
898
- return statement.specifiers.map((specifier) => {
899
- const imported = specifier.type === nodeTypes.importSpecifier ? specifier.imported : specifier.local;
900
- const exported = imported.type === nodeTypes.identifier ? imported.name : imported.value;
901
- return [specifier.local.name, {
902
- from: statement.source.value,
903
- exported
904
- }];
905
- });
906
- }
907
- /** `const { exported: local } = require('...')`. */
908
- function requireBindings(statement) {
909
- return statement.declarations.flatMap((declarator) => {
910
- const from = getRequirePath(declarator.init);
911
- if (from === void 0 || declarator.id.type !== nodeTypes.objectPattern) return [];
912
- return declarator.id.properties.flatMap((entry) => {
913
- if (entry.type !== nodeTypes.property || entry.value.type !== nodeTypes.identifier) return [];
914
- return [[entry.value.name, {
915
- from,
916
- exported: getKey(entry) ?? entry.value.name
917
- }]];
918
- });
919
- });
920
- }
921
- function getImports(program) {
922
- return new Map(program.body.flatMap((statement) => {
923
- if (statement.type === nodeTypes.importDeclaration) return importBindings(statement);
924
- if (statement.type === nodeTypes.variableDeclaration) return requireBindings(statement);
925
- return [];
926
- }));
927
- }
928
- function isImportStatement(node, kind) {
929
- if (kind === moduleKinds.esm) return node.type === nodeTypes.importDeclaration;
930
- return node.type === nodeTypes.variableDeclaration && node.declarations.some((declarator) => getRequirePath(declarator.init) !== void 0);
931
- }
932
- /** The quote of the first string literal in the file, so new code matches it. */
933
- function detectQuote(node) {
934
- if (!node || typeof node !== "object") return void 0;
935
- const { type, raw } = node;
936
- if (type === nodeTypes.literal && typeof raw === "string" && (raw[0] === quotes.single || raw[0] === quotes.double)) return raw[0];
937
- for (const child of Object.values(node)) {
938
- const quote = detectQuote(child);
939
- if (quote) return quote;
940
- }
941
- }
942
- /** The `adapter` value, or the elements of the `plugins` or `parsers` array. */
943
- function factoryNodes(config, kind) {
944
- if (kind === extensionKinds.adapter) return [getValue(config, "adapter")];
945
- const array = getValue(config, `${kind}s`);
946
- return array?.type === nodeTypes.arrayExpression ? array.elements : [];
947
- }
948
- /** The factory calls of one kind whose callee the file imports. */
949
- function findCalls(program, config, kind = extensionKinds.plugin) {
950
- const imports = getImports(program);
951
- return factoryNodes(config, kind).flatMap((node) => {
952
- if (node?.type !== nodeTypes.callExpression || node.callee.type !== nodeTypes.identifier) return [];
953
- const imported = imports.get(node.callee.name);
954
- return imported ? [{
955
- importName: node.callee.name,
956
- exportName: imported.exported,
957
- packageName: imported.from,
958
- call: node
959
- }] : [];
960
- });
961
- }
962
- /** Whether nothing but whitespace precedes `offset` on its line. */
963
- function startsLine(source, offset) {
964
- return source.slice(source.lastIndexOf("\n", offset - 1) + 1, offset).trim() === "";
965
- }
966
- /** The `disable-plugin` blocks in this config, read from marker comments since the call is no longer a node. */
967
- function findDisabled(source, comments, config) {
968
- const lines = source.split("\n");
969
- return comments.flatMap((comment) => {
970
- const match = comment.type === commentTypes.line ? comment.value.trim().match(DISABLED_MARKER_PATTERN) : null;
971
- if (!match || comment.start < config.start || comment.end > config.end || !startsLine(source, comment.start)) return [];
972
- const line = getPosition(source, comment.start).line;
973
- const end = line + Number(match[2]);
974
- const code = lines.slice(line, end).map((text) => text.replace(/^(\s*)\/\/ ?/, "$1").replace(/^\s+$/, ""));
975
- return [{
976
- packageName: match[1],
977
- line,
978
- end,
979
- code
980
- }];
981
- });
982
- }
983
- /** Literal members as `value` and code paths as `locked`, `undefined` for a computed key. */
984
- function readEditable(node, prefix = []) {
985
- const value = {};
986
- const locked = [];
987
- for (const [index, entry] of node.properties.entries()) {
988
- if (entry.type === nodeTypes.spreadElement) continue;
989
- const key = getKey(entry);
990
- if (key === void 0) return void 0;
991
- if (findProperty(node, key)?.index !== index) continue;
992
- const path = [...prefix, key];
993
- if (!isSafeKey(key)) {
994
- locked.push(path);
995
- continue;
996
- }
997
- const member = isOverridden(node, index) ? void 0 : entry.value;
998
- const literal = readLiteral(member);
999
- if (literal !== void 0) {
1000
- value[key] = literal;
1001
- continue;
1002
- }
1003
- if (member?.type !== nodeTypes.objectExpression) {
1004
- locked.push(path);
1005
- continue;
1006
- }
1007
- const nested = readEditable(member, path);
1008
- if (!nested) return void 0;
1009
- value[key] = nested.value;
1010
- locked.push(...nested.locked);
1011
- }
1012
- return {
1013
- value,
1014
- locked
1015
- };
1016
- }
1017
- function readObjectOptions(options) {
1018
- const entries = {};
1019
- for (const [index, entry] of options.properties.entries()) {
1020
- const key = getKey(entry);
1021
- if (key === void 0 || !isSafeKey(key)) continue;
1022
- const member = entry.type === nodeTypes.property && !isOverridden(options, index) ? entry.value : void 0;
1023
- const value = readLiteral(member);
1024
- if (value !== void 0) {
1025
- entries[key] = {
1026
- literal: true,
1027
- value
1028
- };
1029
- continue;
1030
- }
1031
- entries[key] = {
1032
- literal: false,
1033
- ...member?.type === nodeTypes.objectExpression ? readEditable(member) : void 0
1034
- };
1035
- }
1036
- return entries;
1037
- }
1038
- function readCallOptions(call) {
1039
- const options = call.arguments[0];
1040
- return options?.type === nodeTypes.objectExpression ? readObjectOptions(options) : {};
1041
- }
1042
- function readDisabledOptions(code) {
1043
- const call = parseExpression(code.join("\n").trim().replace(/,$/, ""));
1044
- return call?.type === nodeTypes.callExpression ? readCallOptions(call) : {};
1045
- }
1046
- function toPluginView({ importName, packageName, call }) {
1047
- return {
1048
- importName,
1049
- packageName,
1050
- options: readCallOptions(call)
1051
- };
1052
- }
1053
- /** Active and disabled plugins, in source order so a disabled plugin keeps its place. */
1054
- function readPlugins({ source, parsed, config }) {
1055
- const importNames = new Map([...getImports(parsed.program)].map(([local, { from }]) => [from, local]));
1056
- const active = findCalls(parsed.program, config).map((call) => ({
1057
- line: getPosition(source, call.call.start).line,
1058
- plugin: toPluginView(call)
1059
- }));
1060
- const disabled = findDisabled(source, parsed.comments, config).map(({ packageName, line, code }) => ({
1061
- line,
1062
- plugin: {
1063
- importName: importNames.get(packageName) ?? toExportName(packageName),
1064
- packageName,
1065
- options: readDisabledOptions(code),
1066
- disabled: true
1067
- }
1068
- }));
1069
- return [...active, ...disabled].sort((a, b) => a.line - b.line).map(({ plugin }) => plugin);
1070
- }
1071
- function readConfigView({ source, parsed, config }) {
1072
- const [adapter] = findCalls(parsed.program, config, extensionKinds.adapter).map(toPluginView);
1073
- const parsers = findCalls(parsed.program, config, extensionKinds.parser).map(toPluginView);
1074
- const output = getOutput(config);
1075
- return {
1076
- name: getConfigName(config),
1077
- plugins: readPlugins({
1078
- source,
1079
- parsed,
1080
- config
1081
- }),
1082
- ...adapter && { adapter },
1083
- ...parsers.length ? { parsers } : {},
1084
- ...!("reason" in output) && { output: pickOutput(readObjectOptions(output.object)) }
1085
- };
1086
- }
1087
- /** Which plugins the file declares and which options Studio may write. */
1088
- function readConfig(source) {
1089
- const parsed = findConfigs(source);
1090
- if ("reason" in parsed) return {
1091
- managed: false,
1092
- reason: parsed.reason
1093
- };
1094
- return {
1095
- managed: true,
1096
- configs: parsed.configs.map((config) => readConfigView({
1097
- source,
1098
- parsed,
1099
- config
1100
- }))
1101
- };
1102
- }
1103
- /** The modules the file imports, empty when it cannot be parsed. */
1104
- function readImports(source) {
1105
- const parsed = parseSource(source);
1106
- if ("reason" in parsed) return [];
1107
- return [...new Set([...getImports(parsed.program).values()].map(({ from }) => from))];
1108
- }
1109
- /** Follows `path` to its literal, or to the first missing key. Refuses a value set in code. */
1110
- function findPath(options, path) {
1111
- let object = options;
1112
- for (const [depth, key] of path.entries()) {
1113
- const found = findProperty(object, key);
1114
- if (!found) return {
1115
- object,
1116
- missing: depth
1117
- };
1118
- if (isOverridden(object, found.index)) return { reason: `${path.slice(0, depth + 1).join(".")} can be overridden by a spread after it, so it is customized in code` };
1119
- const { member, index } = found;
1120
- if (depth === path.length - 1) return member.type === nodeTypes.property && readLiteral(member.value) !== void 0 ? {
1121
- object,
1122
- index,
1123
- value: member.value
1124
- } : { reason: `${path.join(".")} is customized in code` };
1125
- if (member.type !== nodeTypes.property || member.value.type !== nodeTypes.objectExpression) return { reason: `${key} is not an object, so ${path.join(".")} cannot be reached` };
1126
- object = member.value;
1127
- }
1128
- return { reason: "no option path given" };
1129
- }
1130
- function setAtPath({ source, options, path, value, quote }) {
1131
- const target = findPath(options, path);
1132
- if ("reason" in target) return target;
1133
- if ("missing" in target) {
1134
- const [key, ...rest] = path.slice(target.missing);
1135
- return { source: appendItem(source, target.object, `${printKey(key, quote)}: ${printValue(wrapPath(rest, value), quote)}`) };
1136
- }
1137
- return { source: replaceText(source, target.value.start, target.value.end, printValue(value, quote)) };
1138
- }
1139
- function removeAtPath({ source, options, path }) {
1140
- const target = findPath(options, path);
1141
- if ("reason" in target) return target;
1142
- if ("missing" in target) return { source };
1143
- return { source: removeItem(source, target.object, target.index) };
1144
- }
1145
- function setOption(source, call, path, value, quote) {
1146
- if (!isOptionValue(value)) return { reason: "the value is not a literal that can be written to a config file" };
1147
- const options = call.arguments[0];
1148
- if (!options) {
1149
- const closing = call.end - 1;
1150
- if (source[closing] !== ")") return { reason: "the factory call could not be located in the file" };
1151
- return { source: replaceText(source, closing, closing, printValue(wrapPath(path, value), quote)) };
1152
- }
1153
- if (options.type !== nodeTypes.objectExpression) return { reason: "the factory was not called with an object literal" };
1154
- return setAtPath({
1155
- source,
1156
- options,
1157
- path,
1158
- value,
1159
- quote
1160
- });
1161
- }
1162
- function removeOption(source, call, path) {
1163
- const options = call.arguments[0];
1164
- if (!options) return { source };
1165
- if (options.type !== nodeTypes.objectExpression) return { reason: "the factory was not called with an object literal" };
1166
- return removeAtPath({
1167
- source,
1168
- options,
1169
- path
1170
- });
1171
- }
1172
- function editOption({ source, parsed, config, edit, quote, extensions }) {
1173
- const kind = edit.kind ?? extensionKinds.plugin;
1174
- const calls = findCalls(parsed.program, config, kind).filter((entry) => entry.packageName === edit.plugin);
1175
- const exportName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
1176
- const call = (calls.find((entry) => entry.exportName === exportName) ?? calls[0])?.call;
1177
- if (!call) {
1178
- if (kind === extensionKinds.adapter) return { reason: `${edit.plugin} is not the config's adapter` };
1179
- return { reason: kind === extensionKinds.plugin && findDisabled(source, parsed.comments, config).some((block) => block.packageName === edit.plugin) ? `${edit.plugin} is disabled, enable it first` : `${edit.plugin} is not in the ${kind}s array` };
1180
- }
1181
- if (edit.operation === ConfigEditOperation.SET_OPTION) return setOption(source, call, edit.path, edit.value, quote);
1182
- return removeOption(source, call, edit.path);
1183
- }
1184
- function setInput(source, config, input, quote) {
1185
- const value = printValue(input, quote);
1186
- const found = findProperty(config, "input");
1187
- if (!found) return { source: appendItem(source, config, `input: ${value}`) };
1188
- if (found.member.type !== nodeTypes.property || !isPlainProperty(found.member)) return { reason: "input is not a plain property" };
1189
- if (isOverridden(config, found.index)) return { reason: "a spread after input could override it" };
1190
- return { source: replaceText(source, found.member.value.start, found.member.value.end, value) };
1191
- }
1192
- function getOutput(config) {
1193
- const found = findProperty(config, "output");
1194
- if (found?.member.type !== nodeTypes.property || found.member.value.type !== nodeTypes.objectExpression) return { reason: "output is not an object literal" };
1195
- if (isOverridden(config, found.index)) return { reason: "a spread after output could override it" };
1196
- return { object: found.member.value };
1197
- }
1198
- function editOutput({ source, config, edit, quote }) {
1199
- const output = getOutput(config);
1200
- if ("reason" in output) return output;
1201
- if (edit.operation === ConfigEditOperation.REMOVE_OUTPUT) return removeAtPath({
1202
- source,
1203
- options: output.object,
1204
- path: edit.path
1205
- });
1206
- const [key, ...rest] = edit.path;
1207
- if (!isOptionValue(edit.value) || !isOutputValue({
1208
- key,
1209
- value: wrapPath(rest, edit.value)
1210
- })) return { reason: `the value is not one output.${key} takes` };
1211
- return setAtPath({
1212
- source,
1213
- options: output.object,
1214
- path: edit.path,
1215
- value: edit.value,
1216
- quote
1217
- });
1218
- }
1219
- /** Adds the import after the last one, matching its semicolon style. */
1220
- function addImport({ source, parsed, importNames, moduleSpecifier, quote }) {
1221
- const lastImport = parsed.program.body.findLast((node) => isImportStatement(node, parsed.kind));
1222
- const afterLine = lastImport ? getPosition(source, lastImport.end).line : 0;
1223
- const lines = source.split("\n");
1224
- const semicolon = afterLine > 0 && lines[afterLine - 1]?.trimEnd().endsWith(";") ? ";" : "";
1225
- const specifier = `${quote}${moduleSpecifier}${quote}`;
1226
- const statement = parsed.kind === moduleKinds.esm ? `import { ${importNames.join(", ")} } from ${specifier}` : `const { ${importNames.join(", ")} } = require(${specifier})`;
1227
- lines.splice(afterLine, 0, `${statement}${semicolon}`);
1228
- if (afterLine === 0) lines.splice(1, 0, "");
1229
- return lines.join("\n");
1230
- }
1231
- /**
1232
- * The parsers `defineConfig` applies when a config sets no `parsers`, by import name. Adding a parser
1233
- * to such a config writes these too, or the new array would replace them.
1234
- */
1235
- const DEFAULT_PARSERS = [
1236
- {
1237
- importName: "parserTs",
1238
- packageName: "@kubb/parser-ts"
1239
- },
1240
- {
1241
- importName: "parserTsx",
1242
- packageName: "@kubb/parser-ts"
1243
- },
1244
- {
1245
- importName: "parserMd",
1246
- packageName: "@kubb/parser-md"
1247
- }
1248
- ];
1249
- /**
1250
- * Adds a plugin to `plugins`, or with `kind: 'parser'` a parser to `parsers`. Replaying the same add
1251
- * is a no-op, while an import name taken by another package is an error.
1252
- */
1253
- function addPlugin({ source, parsed, config, edit, quote, extensions }) {
1254
- const kind = edit.kind ?? extensionKinds.plugin;
1255
- const isParser = kind === extensionKinds.parser;
1256
- if (!isAllowedPlugin({
1257
- packageName: edit.plugin,
1258
- extensions,
1259
- kind
1260
- })) return { reason: isParser ? `"${edit.plugin}" is not an installed Kubb parser` : `"${edit.plugin}" is not a @kubb/plugin-* package or an installed Kubb plugin` };
1261
- const importName = extensions.get(edit.plugin)?.manifest.export ?? toExportName(edit.plugin);
1262
- if (!IDENTIFIER_PATTERN.test(importName)) return { reason: `"${importName}" is not a valid import name` };
1263
- if (findCalls(parsed.program, config, kind).some((entry) => entry.packageName === edit.plugin && (!isParser || entry.exportName === importName))) return { source };
1264
- const options = edit.options ?? {};
1265
- if (!isOptionValue(options)) return { reason: "the options are not literals that can be written to a config file" };
1266
- const key = `${kind}s`;
1267
- const array = getValue(config, key);
1268
- if (array ? array.type !== nodeTypes.arrayExpression : !isParser) return { reason: `${key} is not an array literal` };
1269
- const defaults = isParser && !array ? DEFAULT_PARSERS.filter((parser) => parser.importName !== importName) : [];
1270
- const imports = getImports(parsed.program);
1271
- const missing = /* @__PURE__ */ new Map();
1272
- for (const item of [...defaults, {
1273
- importName,
1274
- packageName: edit.plugin
1275
- }]) {
1276
- const from = imports.get(item.importName)?.from;
1277
- if (from && from !== item.packageName) return { reason: `${item.importName} is already imported from ${from}` };
1278
- if (!from) missing.set(item.packageName, [...missing.get(item.packageName) ?? [], item.importName]);
1279
- }
1280
- const call = `${importName}(${Object.keys(options).length ? printValue(options, quote) : ""})`;
1281
- const next = array?.type === nodeTypes.arrayExpression ? appendItem(source, array, call) : appendItem(source, config, `parsers: [${[...defaults.map((parser) => `${parser.importName}()`), call].join(", ")}]`);
1282
- return { source: [...missing].reduceRight((current, [moduleSpecifier, importNames]) => addImport({
1283
- source: current,
1284
- parsed,
1285
- importNames,
1286
- moduleSpecifier,
1287
- quote
1288
- }), next) };
1289
- }
1290
- /** One item per line, so a call can be commented out alone. Skipped when comments sit between items. */
1291
- function splitPlugins(source, config) {
1292
- const array = getValue(config, "plugins");
1293
- if (array?.type !== nodeTypes.arrayExpression) return void 0;
1294
- const items = array.elements.flatMap((element) => element ? [element] : []);
1295
- const { start, end } = array;
1296
- const gaps = [source.slice(start + 1, items[0]?.start ?? end - 1), ...items.map((item, index) => source.slice(item.end, items[index + 1]?.start ?? end - 1))];
1297
- if (!items.length || gaps.some((gap) => !/^[\s,]*$/.test(gap))) return void 0;
1298
- const lineStart = source.lastIndexOf("\n", start - 1) + 1;
1299
- const indent = source.slice(lineStart).match(/^[ \t]*/)?.[0] ?? "";
1300
- return replaceText(source, start, end, `[\n${items.map((item) => `${indent} ${source.slice(item.start, item.end)},`).join("\n")}\n${indent}]`);
1301
- }
1302
- function disablePlugin(source, program, config, plugin) {
1303
- const call = findCalls(program, config).find((entry) => entry.packageName === plugin)?.call;
1304
- if (!call) return { reason: `${plugin} is not in the plugins array` };
1305
- const start = getPosition(source, call.start);
1306
- const end = getPosition(source, call.end);
1307
- const lines = source.split("\n");
1308
- const from = start.line - 1;
1309
- const to = end.line - 1;
1310
- const firstLine = lines[from] ?? "";
1311
- if (firstLine.slice(0, start.column).trim() || !/^,?\s*$/.test((lines[to] ?? "").slice(end.column))) return { reason: `${plugin} shares a line with other code, so it cannot be commented out safely` };
1312
- const indent = firstLine.match(/^\s*/)?.[0] ?? "";
1313
- const commented = lines.slice(from, to + 1).map((line) => line.trim() ? `${indent}// ${line.slice(indent.length)}` : `${indent}//`);
1314
- lines.splice(from, commented.length, `${indent}// kubb:disabled ${plugin} ${commented.length}`, ...commented);
1315
- return { source: lines.join("\n") };
1316
- }
1317
- /** Disables the plugin, first putting a one-line `plugins: [a(), b()]` (what `add-plugin` writes) on separate lines. */
1318
- function disablePluginInConfig({ source, parsed, config, edit }) {
1319
- const result = disablePlugin(source, parsed.program, config, edit.plugin);
1320
- if (!("reason" in result) || !result.reason.includes("shares a line")) return result;
1321
- const split = splitPlugins(source, config);
1322
- if (split === void 0) return result;
1323
- const reparsed = findConfigs(split);
1324
- const reparsedConfig = "reason" in reparsed ? void 0 : findConfigEntry(reparsed.configs, edit.config);
1325
- if ("reason" in reparsed || !reparsedConfig) return result;
1326
- return disablePlugin(split, reparsed.program, reparsedConfig, edit.plugin);
1327
- }
1328
- function enablePlugin(source, comments, config, plugin) {
1329
- const block = findDisabled(source, comments, config).find((entry) => entry.packageName === plugin);
1330
- if (!block) return { reason: `${plugin} is not disabled` };
1331
- const lines = source.split("\n");
1332
- lines.splice(block.line - 1, block.end - block.line + 1, ...block.code);
1333
- return { source: lines.join("\n") };
1334
- }
1335
- /** Drops the import statements of `packageName` once nothing else in the file uses their names. */
1336
- function removeUnusedImports(source, packageName) {
1337
- const parsed = parseSource(source);
1338
- if ("reason" in parsed) return source;
1339
- return parsed.program.body.reduceRight((current, statement) => {
1340
- const bindings = statement.type === nodeTypes.importDeclaration ? importBindings(statement) : statement.type === nodeTypes.variableDeclaration ? requireBindings(statement) : [];
1341
- if (!bindings.length || bindings.some(([, { from }]) => from !== packageName)) return current;
1342
- const rest = replaceText(current, statement.start, statement.end);
1343
- if (bindings.some(([local]) => new RegExp(`(?<![\\w$])${local.replaceAll("$", "\\$")}(?![\\w$])`).test(rest))) return current;
1344
- const lineStart = current.lastIndexOf("\n", statement.start - 1) + 1;
1345
- const lineEnd = current.indexOf("\n", statement.end);
1346
- const after = lineEnd === -1 ? current.length : lineEnd + 1;
1347
- return replaceText(current, lineStart, lineStart === 0 && current[after] === "\n" ? after + 1 : after);
1348
- }, source);
1349
- }
1350
- /**
1351
- * Removes every call of a plugin or parser package, and the block `disable-plugin` commented out,
1352
- * then its unused imports. Removing what the config does not hold is a no-op. Refuses to empty
1353
- * `parsers`, since a config with no parser cannot print files.
1354
- */
1355
- function removeExtension(source, edit) {
1356
- const kind = edit.kind ?? extensionKinds.plugin;
1357
- let current = source;
1358
- for (;;) {
1359
- const parsed = findConfigs(current);
1360
- if ("reason" in parsed) return parsed;
1361
- const config = findConfigEntry(parsed.configs, edit.config);
1362
- if (!config) return { reason: `no config entry found for ${JSON.stringify(edit.config)}` };
1363
- const array = getValue(config, `${kind}s`);
1364
- const calls = findCalls(parsed.program, config, kind).filter((entry) => entry.packageName === edit.plugin);
1365
- const block = findDisabled(current, parsed.comments, config).find((entry) => entry.packageName === edit.plugin);
1366
- if (calls[0] && array?.type === nodeTypes.arrayExpression) {
1367
- const elements = array.elements.filter((element) => element !== null);
1368
- if (kind === extensionKinds.parser && elements.length === calls.length) return { reason: `${edit.plugin} is the last parser in the parsers array` };
1369
- current = removeItem(current, array, elements.indexOf(calls[0].call));
1370
- continue;
1371
- }
1372
- if (block && kind === extensionKinds.plugin) {
1373
- const lines = current.split("\n");
1374
- lines.splice(block.line - 1, block.end - block.line + 1);
1375
- current = lines.join("\n");
1376
- continue;
1377
- }
1378
- return { source: current === source ? source : removeUnusedImports(current, edit.plugin) };
1379
- }
1380
- }
1381
- /** Every edit re-parses the file, since the previous one rewrote it. */
1382
- function applyEdit({ source, edit, extensions }) {
1383
- const parsed = findConfigs(source);
1384
- if ("reason" in parsed) return parsed;
1385
- const config = findConfigEntry(parsed.configs, edit.config);
1386
- if (!config) return { reason: `no config entry found for ${JSON.stringify(edit.config)}` };
1387
- const quote = detectQuote(parsed.program) ?? quotes.single;
1388
- switch (edit.operation) {
1389
- case ConfigEditOperation.SET_INPUT: return setInput(source, config, edit.input, quote);
1390
- case ConfigEditOperation.SET_OUTPUT:
1391
- case ConfigEditOperation.REMOVE_OUTPUT: return editOutput({
1392
- source,
1393
- config,
1394
- edit,
1395
- quote
1396
- });
1397
- case ConfigEditOperation.DISABLE_PLUGIN: return disablePluginInConfig({
1398
- source,
1399
- parsed,
1400
- config,
1401
- edit
1402
- });
1403
- case ConfigEditOperation.ENABLE_PLUGIN: return enablePlugin(source, parsed.comments, config, edit.plugin);
1404
- case ConfigEditOperation.ADD_PLUGIN: return addPlugin({
1405
- source,
1406
- parsed,
1407
- config,
1408
- edit,
1409
- quote,
1410
- extensions
1411
- });
1412
- case ConfigEditOperation.REMOVE_EXTENSION: return removeExtension(source, edit);
1413
- case ConfigEditOperation.SET_OPTION:
1414
- case ConfigEditOperation.REMOVE_OPTION: return editOption({
1415
- source,
1416
- parsed,
1417
- config,
1418
- edit,
1419
- quote,
1420
- extensions
1421
- });
1422
- }
1423
- }
1424
- /** Applies edits in place, touching only the text each targets. A refused edit is reported and the rest still run. */
1425
- function editConfig({ source, edits, extensions = /* @__PURE__ */ new Map() }) {
1426
- let current = source;
1427
- const outcomes = edits.map((edit) => {
1428
- const invalid = validateEdit(edit);
1429
- if (invalid) return {
1430
- edit,
1431
- applied: false,
1432
- reason: invalid
1433
- };
1434
- try {
1435
- const result = applyEdit({
1436
- source: current,
1437
- edit,
1438
- extensions
1439
- });
1440
- if ("reason" in result) return {
1441
- edit,
1442
- applied: false,
1443
- reason: result.reason
1444
- };
1445
- current = result.source;
1446
- return {
1447
- edit,
1448
- applied: true
1449
- };
1450
- } catch (error) {
1451
- return {
1452
- edit,
1453
- applied: false,
1454
- reason: getErrorMessage(error)
1455
- };
1456
- }
1457
- });
1458
- return {
1459
- source: current,
1460
- outcomes,
1461
- changed: current !== source
1462
- };
1463
- }
1464
- /** Applies `edits` to `filePath`, writing only when something changed. */
1465
- async function editConfigFile({ filePath, edits, extensions }) {
1466
- const result = editConfig({
1467
- source: await read(filePath),
1468
- edits,
1469
- extensions
1470
- });
1471
- if (result.changed) await writeRaw(filePath, result.source);
1472
- return result;
1473
- }
1474
- //#endregion
1475
- //#region src/runners/init/utils.ts
1476
- /** Metadata for each supported package manager, keyed by its short name. */
1477
- const packageManagers = {
1478
- pnpm: {
1479
- name: "pnpm",
1480
- lockFiles: ["pnpm-lock.yaml"],
1481
- initCommand: ["init"],
1482
- installCommand: ["add", "-D"],
1483
- removeCommand: ["remove"],
1484
- runCommand: []
1485
- },
1486
- yarn: {
1487
- name: "yarn",
1488
- lockFiles: ["yarn.lock"],
1489
- initCommand: ["init", "-y"],
1490
- installCommand: ["add", "-D"],
1491
- removeCommand: ["remove"],
1492
- runCommand: []
1493
- },
1494
- bun: {
1495
- name: "bun",
1496
- lockFiles: ["bun.lock", "bun.lockb"],
1497
- initCommand: ["init", "-y"],
1498
- installCommand: ["add", "-d"],
1499
- removeCommand: ["remove"],
1500
- runCommand: ["run"]
1501
- },
1502
- npm: {
1503
- name: "npm",
1504
- lockFiles: ["package-lock.json"],
1505
- initCommand: ["init", "-y"],
1506
- installCommand: ["install", "--save-dev"],
1507
- removeCommand: ["uninstall"],
1508
- runCommand: ["run"]
1509
- }
1510
- };
1511
- /** Detects the package manager for `cwd`: the `packageManager` field in `package.json`, then a lock file, then `npm`. */
1512
- function detectPackageManager(cwd = process$1.cwd()) {
1513
- const packageJsonPath = path.join(cwd, "package.json");
1514
- if (fs.existsSync(packageJsonPath)) try {
1515
- const pmField = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")).packageManager;
1516
- if (typeof pmField === "string") {
1517
- const name = pmField.split("@")[0];
1518
- if (name && name in packageManagers) return packageManagers[name];
1519
- }
1520
- } catch {}
1521
- for (const pm of Object.values(packageManagers)) if (pm.lockFiles.some((lockFile) => fs.existsSync(path.join(cwd, lockFile)))) return pm;
1522
- return packageManagers.npm;
1523
- }
1524
- /**
1525
- * Initializes a new `package.json` at `cwd` using the detected package manager.
1526
- */
1527
- async function initPackageJson(cwd, packageManager) {
1528
- await x(packageManager.name, [...packageManager.initCommand], {
1529
- nodeOptions: {
1530
- cwd,
1531
- stdio: "inherit"
1532
- },
1533
- throwOnError: true
1534
- });
1535
- }
1536
- const INSTALL_TIMEOUT_MS = 3e5;
1537
- /**
1538
- * Installs the given packages at `cwd` using the detected package manager. Each output line goes
1539
- * to `onLine`, so the caller can show it while the install runs; a failed install also keeps the
1540
- * end of the output in the error.
1541
- */
1542
- async function installPackages(packages, packageManager, cwd = process$1.cwd(), onLine, command = packageManager.installCommand) {
1543
- const args = [...command, ...packages];
1544
- const result = x(packageManager.name, args, {
1545
- nodeOptions: {
1546
- cwd,
1547
- stdio: [
1548
- "ignore",
1549
- "pipe",
1550
- "pipe"
1551
- ]
1552
- },
1553
- timeout: INSTALL_TIMEOUT_MS,
1554
- throwOnError: false
1555
- });
1556
- const lines = [];
1557
- for await (const line of result) {
1558
- lines.push(line);
1559
- onLine?.(line);
1560
- }
1561
- if (result.exitCode === 0) return;
1562
- if (result.exitCode === void 0) throw new Error(`\`${packageManager.name} ${args.join(" ")}\` timed out after ${INSTALL_TIMEOUT_MS / 6e4} minutes`);
1563
- const output = lines.slice(-20).join("\n");
1564
- throw new Error(`\`${packageManager.name} ${args.join(" ")}\` failed with exit code ${result.exitCode}${output ? `:\n${output}` : ""}`);
1565
- }
1566
- /**
1567
- * Uninstalls the given packages at `cwd` with the package manager's remove command, like {@link installPackages}.
1568
- */
1569
- function uninstallPackages(packages, packageManager, cwd = process$1.cwd()) {
1570
- return installPackages(packages, packageManager, cwd, void 0, packageManager.removeCommand);
1571
- }
1572
- /**
1573
- * What the selected plugins' `manifests` say is missing from the selection: a `dependencies`
1574
- * entry, or a `client` plugin next to a `consumer`. Empty when nothing is missing.
1575
- * `optionalDependencies` are skipped, since `kubb init` writes the default options.
1576
- */
1577
- function missingPluginDependencies(manifests) {
1578
- const selected = manifests.map((manifest) => manifest.name);
1579
- const hasClient = manifests.some((manifest) => manifest.role === "client");
1580
- return manifests.flatMap((manifest) => [...(manifest.dependencies ?? []).filter((name) => !selected.includes(name)).map((name) => `${manifest.name} needs ${name}.`), ...manifest.role === "consumer" && !hasClient ? [`${manifest.name} needs a client plugin, such as plugin-axios or plugin-fetch.`] : []]);
1581
- }
1582
- async function readPackageJson(cwd) {
1583
- try {
1584
- return JSON.parse(await read(path.join(cwd, "package.json")));
1585
- } catch {
1586
- return {};
1587
- }
1588
- }
1589
- const SPEC_FILE = /^(openapi|swagger)[\w.-]*\.(ya?ml|json)$/i;
1590
- const SKIPPED_DIRS = /* @__PURE__ */ new Set([
1591
- "node_modules",
1592
- "dist",
1593
- "build",
1594
- "coverage"
1595
- ]);
1596
- /**
1597
- * Finds OpenAPI spec files up to `depth` folders below `cwd`, shallowest first, as `./`-relative
1598
- * paths. Walks one level at a time and stops after `limit` files, so a large repo stays fast.
1599
- */
1600
- async function findSpecFiles(cwd, { depth = 3, limit = 20 } = {}) {
1601
- const found = [];
1602
- let level = [""];
1603
- for (let current = 0; current <= depth && level.length && found.length < limit; current++) {
1604
- const listings = await Promise.all(level.map(async (dir) => ({
1605
- dir,
1606
- entries: await fs.promises.readdir(path.join(cwd, dir), { withFileTypes: true }).catch(() => [])
1607
- })));
1608
- level = [];
1609
- for (const { dir, entries } of listings) for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
1610
- if (entry.isFile() && SPEC_FILE.test(entry.name)) found.push(`./${path.posix.join(dir, entry.name)}`);
1611
- if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIPPED_DIRS.has(entry.name)) level.push(path.posix.join(dir, entry.name));
1612
- }
1613
- }
1614
- return found.slice(0, limit);
1615
- }
1616
- const SPEC_HEAD_BYTES = 262144;
1617
- const METHOD = "(?:get|put|post|delete|options|head|patch|trace)";
1618
- /**
1619
- * Reads the OpenAPI or Swagger version and counts the operations from the first 256 KB of the
1620
- * file, so a large spec is never parsed. The count is left out when the file is longer.
1621
- */
1622
- async function readSpecInfo(file) {
1623
- const handle = await fs.promises.open(file);
1624
- try {
1625
- const buffer = Buffer.alloc(SPEC_HEAD_BYTES);
1626
- const { bytesRead } = await handle.read(buffer, 0, SPEC_HEAD_BYTES, 0);
1627
- const head = buffer.toString("utf-8", 0, bytesRead);
1628
- const complete = bytesRead < SPEC_HEAD_BYTES;
1629
- const version = head.match(/^\s*["']?(?:openapi|swagger)["']?\s*:\s*["']?(\d[\d.]*)/m)?.[1];
1630
- const methods = new RegExp(`^\\s*["']?${METHOD}["']?\\s*:\\s*(?:\\{|$)`, "gm");
1631
- return {
1632
- version,
1633
- operations: complete ? head.match(methods)?.length ?? 0 : void 0
1634
- };
1635
- } finally {
1636
- await handle.close();
1637
- }
1638
- }
1639
- /** Finds a plugin by registry id, slug, or short name, so `ts`, `plugin-ts`, and `kubb-labs/plugin-ts` all match. */
1640
- function findPlugin({ plugins, id }) {
1641
- const ids = id.includes("/") ? [id] : [`kubb-labs/${id}`, `kubb-labs/plugin-${id}`];
1642
- return plugins.find((entry) => ids.includes(entry.id));
1643
- }
1644
- /**
1645
- * Turns the comma-separated `--plugins` value into registry entries.
1646
- *
1647
- * @throws when an id is not in `plugins`.
1648
- */
1649
- function parsePluginsFlag({ plugins, value }) {
1650
- const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
1651
- const unknown = ids.filter((id) => !findPlugin({
1652
- plugins,
1653
- id
1654
- }));
1655
- if (unknown.length) throw new Diagnostics.Error({
1656
- code: Diagnostics.code.pluginNotFound,
1657
- severity: "error",
1658
- message: `Not in the Kubb registry: ${unknown.join(", ")}. Browse the plugins at https://kubb.dev/extensions.`,
1659
- help: "Use plugin ids from https://kubb.dev/extensions, such as plugin-ts or ts."
1660
- });
1661
- return ids.map((id) => findPlugin({
1662
- plugins,
1663
- id
1664
- }));
1665
- }
1666
- /** Adds `"generate": "kubb generate"` to the `package.json` scripts at `cwd`. Returns `false`, and changes nothing, when a `generate` script exists. */
1667
- async function addGenerateScript(cwd) {
1668
- const file = path.join(cwd, "package.json");
1669
- const source = await read(file);
1670
- const packageJson = JSON.parse(source);
1671
- if (packageJson.scripts?.generate !== void 0) return false;
1672
- const indent = source.match(/^[ \t]+/m)?.[0] ?? " ";
1673
- const updated = {
1674
- ...packageJson,
1675
- scripts: {
1676
- ...packageJson.scripts,
1677
- generate: "kubb generate"
1678
- }
1679
- };
1680
- await write(file, JSON.stringify(updated, null, indent));
1681
- return true;
1682
- }
1683
- /** Reads the `generate` script from the `package.json` at `cwd`. */
1684
- async function generateScript(cwd) {
1685
- return (await readPackageJson(cwd)).scripts?.generate;
1686
- }
1687
- //#endregion
1688
- //#region src/runners/installWithSpinner.ts
1689
- /** How many package manager lines stay visible under the install title while it runs. */
1690
- const LOG_LINES = 6;
1691
- /**
1692
- * Installs `packages` and reports how long it took. A rich terminal shows the package manager's
1693
- * last lines under the title while the install runs and clears them when it succeeds; a plain one
1694
- * prints the start and end lines. A failed install keeps the output, reports the failure and rethrows.
1695
- */
1696
- async function installWithSpinner({ packages, names, packageManager, cwd, install = installPackages }) {
1697
- const start = process$1.hrtime();
1698
- const task = createTask(`Installing ${names} with ${packageManager.name}`);
1699
- try {
1700
- await install(packages, packageManager, cwd, task.message);
1701
- } catch (error) {
1702
- task.error("Install failed");
1703
- throw error;
1704
- }
1705
- task.success(`Installed ${names} with ${packageManager.name} in ${formatMs(getElapsedMs(start))}`);
1706
- }
1707
- function createTask(title) {
1708
- if (isRichOutput()) {
1709
- const log = clack.taskLog({
1710
- title,
1711
- limit: LOG_LINES
1712
- });
1713
- return {
1714
- message: (line) => log.message(line),
1715
- error: (text) => log.error(text, { showLog: false }),
1716
- success: (text) => log.success(text)
1717
- };
1718
- }
1719
- const spinner = createSpinner();
1720
- spinner.start(title);
1721
- return {
1722
- message: () => {},
1723
- error: (text) => spinner.error(text),
1724
- success: (text) => spinner.stop(text)
1725
- };
1726
- }
1727
- //#endregion
1728
- //#region src/runners/add/run.ts
1729
- /** The import and the `defineConfig` property to merge into `kubb.config.ts` by hand. */
1730
- function toSnippet({ artifact, manifest }) {
1731
- const call = `${manifest.export}()`;
1732
- const property = {
1733
- plugin: `plugins: [..., ${call}],`,
1734
- adapter: `adapter: ${call},`,
1735
- parser: `parsers: [..., ${call}],`
1736
- };
1737
- return [
1738
- `import { ${manifest.export} } from '${artifact.package}'`,
1739
- "",
1740
- property[manifest.kind]
1741
- ].join("\n");
1742
- }
1743
- /** The `add-plugin` edits for `details`, and the manifests the edits read their export names from. */
1744
- function toConfigEdits(details) {
1745
- return {
1746
- edits: details.map(({ artifact, manifest }) => ({
1747
- operation: ConfigEditOperation.ADD_PLUGIN,
1748
- ...manifest.kind === extensionKinds.parser ? { kind: extensionKinds.parser } : {},
1749
- plugin: artifact.package
1750
- })),
1751
- extensions: new Map(details.map(({ artifact, manifest }) => [artifact.package, {
1752
- package: artifact.package,
1753
- version: artifact.version,
1754
- manifest
1755
- }]))
1756
- };
1757
- }
1758
- /** The lines adding `details` would put in the config `source`, trimmed, or why it could not add them. */
1759
- function previewConfig({ details, source }) {
1760
- const result = editConfig({
1761
- source,
1762
- ...toConfigEdits(details)
1763
- });
1764
- const failed = result.outcomes.find((outcome) => !outcome.applied);
1765
- if (failed) return { reason: failed.reason ?? "the config file was not changed" };
1766
- const remaining = source.split("\n");
1767
- return {
1768
- lines: result.source.split("\n").filter((line) => {
1769
- const index = remaining.indexOf(line);
1770
- if (index !== -1) remaining.splice(index, 1);
1771
- return index === -1;
1772
- }).map((line) => line.trim()),
1773
- source: result.source
1774
- };
1775
- }
1776
- async function selectExtension({ entries, versions, registry }) {
1777
- if (!entries.length) throw new Diagnostics.Error({
1778
- code: Diagnostics.code.unknown,
1779
- severity: "error",
1780
- message: `The registry at ${registry} lists no extensions.`,
1781
- help: "Check that `KUBB_REGISTRY` points at a Kubb registry, or unset it to use https://kubb.dev/registry."
1782
- });
1783
- const searchText = new Map(entries.map((entry) => [entry.id, `${entry.id} ${entry.manifest.displayName} ${entry.manifest.description}`.toLowerCase()]));
1784
- const options = entries.toSorted((a, b) => a.id.localeCompare(b.id)).map((entry) => {
1785
- const installed = versions.has(entry.artifact.package);
1786
- return {
1787
- value: entry.id,
1788
- label: toShortId(entry.id),
1789
- hint: installed ? "installed" : summarize(entry.manifest.description),
1790
- disabled: installed
1791
- };
1792
- });
1793
- const id = await clack.autocomplete({
1794
- message: `Add an extension · ${entries.length} in the registry`,
1795
- placeholder: "Search",
1796
- options,
1797
- filter: (search, option) => searchText.get(option.value)?.includes(search.toLowerCase()) ?? false
1798
- });
1799
- return clack.isCancel(id) ? void 0 : id;
1800
- }
1801
- /** Asks whether to also add the required plugins the config lacks. `--yes` adds them. */
1802
- async function confirmDependencies({ entry, missing, yes }) {
1803
- if (!missing.length || yes) return true;
1804
- const names = missing.map((dependency) => dependency.manifest.name).join(", ");
1805
- const answer = await clack.select({
1806
- message: `${entry.manifest.name} needs ${names}. Add ${names} as well?`,
1807
- options: [{
1808
- value: true,
1809
- label: `Yes, add ${missing.length === 1 ? "both" : "all"}`,
1810
- hint: "recommended"
1811
- }, {
1812
- value: false,
1813
- label: `Only ${entry.manifest.name}`,
1814
- hint: "generate fails until you add it"
1815
- }],
1816
- initialValue: true
1817
- });
1818
- return clack.isCancel(answer) ? void 0 : answer;
1819
- }
1820
- /** The first sentence of a registry description, cut to one terminal line. */
1821
- function summarize(description, max = 64) {
1822
- const sentence = description.split(/\.\s/)[0].replace(/\.$/, "");
1823
- return sentence.length > max ? `${sentence.slice(0, max - 1).trimEnd()}…` : sentence;
1824
- }
1825
- /**
1826
- * Installs an extension from the Kubb registry at its reviewed version and, for a plugin or a parser,
1827
- * adds it to `kubb.config.ts`, offering to add the plugins it needs as well. An adapter is never
1828
- * written: the command prints the snippet to swap it in by hand. Without an id, asks which one.
1829
- * Previews the packages and config lines before asking. Refuses when the running Kubb or Node.js
1830
- * version is outside its compatibility.
1831
- */
1832
- async function run$5({ id: input, configPath, yes, install }) {
1833
- const cwd = process$1.cwd();
1834
- const hrStart = process$1.hrtime();
1835
- const summary = (title, status, lines) => logCommandSummary({
1836
- title,
1837
- status,
1838
- hrStart,
1839
- block: true,
1840
- lines
1841
- });
1842
- const installedPackages = [];
1843
- const configured = [];
1844
- logIntro({ title: isInteractiveRun({ yes }) ? void 0 : styleText("bgCyan", styleText("black", " Kubb Add ")) });
1845
- try {
1846
- const registry = registryUrl();
1847
- if (!input && yes) throw new Diagnostics.Error({
1848
- code: Diagnostics.code.unknown,
1849
- severity: "error",
1850
- message: "Pass a registry id, such as plugin-zod, when using --yes.",
1851
- help: "Run `kubb add plugin-zod --yes`, or drop `--yes` to pick from a list."
1852
- });
1853
- const inputId = input ? toRegistryId(input) : void 0;
1854
- const foundConfig = configPath ? path.resolve(configPath) : findConfigFile(cwd);
1855
- const [entries, installed, configSource] = await withSpinner({
1856
- message: "Reading the registry",
1857
- promise: Promise.all([
1858
- fetchRegistryIndex({ registry }),
1859
- readInstalledPackages({ root: cwd }),
1860
- foundConfig ? read(foundConfig).catch(() => void 0) : void 0
1861
- ])
1862
- });
1863
- const versions = new Map(installed.map((item) => [item.package, item.version]));
1864
- const id = inputId ?? await selectExtension({
1865
- entries,
1866
- versions,
1867
- registry
1868
- });
1869
- if (!id) {
1870
- summary("Add canceled", "canceled", [" No changes made"]);
1871
- return;
1872
- }
1873
- const entry = entries.find((candidate) => candidate.id === id);
1874
- if (!entry) {
1875
- const suggestions = closestIds({
1876
- input: id,
1877
- ids: entries.map((entry) => entry.id)
1878
- }).map(toShortId);
1879
- summary(`No extension named ${input ?? id}`, "failed", [...suggestions.map((suggestion) => ` kubb add ${suggestion}`), " Browse all kubb.dev/extensions"]);
1880
- process$1.exitCode = 1;
1881
- return;
1882
- }
1883
- const { artifact, manifest } = entry;
1884
- const kind = manifest.kind.charAt(0).toUpperCase() + manifest.kind.slice(1);
1885
- const problem = incompatibility(manifest);
1886
- if (problem) throw new Diagnostics.Error({
1887
- code: Diagnostics.code.incompatibleExtension,
1888
- severity: "error",
1889
- message: problem,
1890
- help: "Upgrade Kubb or Node.js to a version the extension supports."
1891
- });
1892
- const configFile = manifest.kind === extensionKinds.adapter ? void 0 : foundConfig;
1893
- if (manifest.kind !== extensionKinds.adapter && !configFile) throw new Diagnostics.Error({
1894
- code: Diagnostics.code.unknown,
1895
- severity: "error",
1896
- message: "No kubb.config.ts found. Run `kubb init` first, or pass --config.",
1897
- help: "Run `kubb init` to create a config, or pass its path with `--config`."
1898
- });
1899
- const configName = configFile ? path.relative(cwd, configFile) || configFile : void 0;
1900
- const byName = new Map(entries.map((candidate) => [candidate.manifest.name, candidate]));
1901
- const inConfig = (name) => {
1902
- const dependency = byName.get(name);
1903
- return dependency ? !!configSource?.includes(`${dependency.manifest.export}(`) : installed.some((item) => item.manifest?.name === name);
1904
- };
1905
- const dependencies = manifest.dependencies ?? [];
1906
- const missing = configFile ? dependencies.filter((name) => !inConfig(name)).flatMap((name) => byName.get(name) ?? []) : [];
1907
- const withDependencies = await confirmDependencies({
1908
- entry,
1909
- missing,
1910
- yes
1911
- });
1912
- if (withDependencies === void 0) {
1913
- summary("Add canceled", "canceled", [` ${kind} ${manifest.name}`, " No changes made"]);
1914
- return;
1915
- }
1916
- const targets = [...withDependencies ? missing : [], entry];
1917
- const dependencyStatus = (name) => {
1918
- if (inConfig(name)) return `${name} ${styleText("green", "✓ installed")}`;
1919
- return targets.some((target) => target.manifest.name === name) ? `${name} ${styleText("cyan", "+ adding")}` : `${name} ${styleText("yellow", "missing")}`;
1920
- };
1921
- const preview = configFile && configSource !== void 0 ? previewConfig({
1922
- details: targets,
1923
- source: configSource
1924
- }) : void 0;
1925
- const before = readImports(configSource ?? "");
1926
- const added = preview && "lines" in preview ? readImports(preview.source).filter((name) => !before.includes(name)) : [];
1927
- const bundled = BUNDLED_PACKAGES.filter((name) => added.includes(name) && !versions.has(name) && !targets.some((target) => target.artifact.package === name));
1928
- const kubbVersion = versions.get("kubb");
1929
- const specs = [...targets.filter((target) => target === entry || !versions.has(target.artifact.package)).map((target) => installSpec(target.artifact)), ...bundled.map((name) => kubbVersion ? `${name}@${kubbVersion}` : name)];
1930
- logStep(`${kind} ${styleText("cyan", manifest.name)} v${artifact.version} · ${summarize(manifest.description)}`);
1931
- logBlock([
1932
- ...dependencies.length ? [`Needs ${dependencies.map(dependencyStatus).join(" ")}`] : [],
1933
- install ? `Installs ${specs.join(" ")}` : "Installs nothing (--no-install)",
1934
- ...configName ? [`Config ${configName}`] : [`Config prints the ${manifest.kind} snippet to add`],
1935
- ...preview && "lines" in preview ? preview.lines.map((line) => styleText("green", ` + ${line}`)) : [],
1936
- ...preview && "reason" in preview ? [styleText("yellow", ` cannot edit it: ${preview.reason}`)] : []
1937
- ]);
1938
- const confirmed = yes || await clack.confirm({
1939
- message: "Add it?",
1940
- initialValue: true
1941
- });
1942
- if (clack.isCancel(confirmed) || !confirmed) {
1943
- summary("Add canceled", "canceled", [` ${kind} ${manifest.name}`, " No changes made"]);
1944
- return;
1945
- }
1946
- const packageManager = detectPackageManager(cwd);
1947
- const packages = () => install ? `${installedPackages.length} installed with ${packageManager.name}` : "installation skipped (--no-install)";
1948
- if (install) {
1949
- const names = [...targets.filter((target) => specs.includes(installSpec(target.artifact))).map((target) => target.artifact.package), ...bundled];
1950
- await installWithSpinner({
1951
- packages: specs,
1952
- names: names.join(", "),
1953
- packageManager,
1954
- cwd
1955
- });
1956
- installedPackages.push(...names);
1957
- }
1958
- const unconfigured = configFile ? [] : [entry];
1959
- if (configFile) for (const target of targets) try {
1960
- const { outcomes, changed } = await editConfigFile({
1961
- filePath: configFile,
1962
- ...toConfigEdits([target])
1963
- });
1964
- if (!outcomes[0]?.applied) throw new Error(outcomes[0]?.reason ?? "the config file was not changed");
1965
- if (changed) configured.push(target);
1966
- logStep(`${target.manifest.export}() configured in ${configName}`);
1967
- } catch (error) {
1968
- logWarn(`Could not edit ${configName}: ${getErrorMessage(error)}`);
1969
- unconfigured.push(target);
1970
- }
1971
- const manual = unconfigured.length > 0;
1972
- if (manual && manifest.kind !== extensionKinds.adapter) process$1.exitCode = 1;
1973
- if (manual) logBlock([
1974
- "Add the import to your Kubb config and merge the property into its defineConfig call:",
1975
- "",
1976
- ...unconfigured.map((target) => toSnippet(target))
1977
- ]);
1978
- const dependenciesMissing = !withDependencies && missing.length > 0;
1979
- summary(manual ? `${kind} needs configuration` : dependenciesMissing ? "Plugin configured; dependencies missing" : configured.length ? install ? `${kind} ready` : `${kind} configured` : `${kind} already configured`, manual || dependenciesMissing ? "warning" : "success", [
1980
- ` Packages ${packages()}`,
1981
- ...configured.map((target) => styleText("green", ` + ${target.manifest.export}()`)),
1982
- "",
1983
- ...!install ? [`Install ${specs.join(" ")} to use it.`] : [],
1984
- ...dependenciesMissing ? [`Missing plugins ${missing.map((target) => target.manifest.name).join(", ")}`] : [],
1985
- manual ? "Add the snippet above, then run kubb generate." : "Next kubb generate",
1986
- `Options kubb.dev/extensions/${entry.id}`
1987
- ]);
1988
- } catch (error) {
1989
- logError(getErrorMessage(error));
1990
- summary("Add failed", "failed", installedPackages.length ? [` Packages ${installedPackages.join(", ")} installed`] : [" No changes made"]);
1991
- process$1.exitCode = 1;
1992
- }
1993
- }
1994
- __name(run$5, "run");
1995
- const command$4 = define({
1996
- name: "add",
1997
- description: "Find and install plugins, adapters, and parsers from the Kubb registry.",
1998
- examples: [
1999
- "kubb add",
2000
- "kubb add plugin-zod",
2001
- "kubb add kubb-labs/plugin-react-query",
2002
- "kubb add acme/plugin-x --yes",
2003
- "kubb add plugin-zod --no-install"
2004
- ].join("\n"),
2005
- toKebab: true,
2006
- args: {
2007
- id: {
2008
- type: "positional",
2009
- description: "Registry id: owner/slug, or a slug for an official extension. Asked for when omitted",
2010
- required: false
2011
- },
2012
- ...configArg,
2013
- ...yesArg,
2014
- install: {
2015
- type: "boolean",
2016
- description: "Install the package. Pass --no-install to only edit the config",
2017
- negatable: true,
2018
- default: true
2019
- }
2020
- },
2021
- run: ({ values }) => run$5({
2022
- id: values.id,
2023
- configPath: values.config,
2024
- yes: values.yes,
2025
- install: values.install
2026
- })
2027
- });
2028
- //#endregion
2029
- //#region src/runners/init/run.ts
2030
- function cancelAndExit(message = "Operation canceled.") {
2031
- clack.cancel(message);
2032
- process$1.exit(0);
2033
- }
2034
- function unwrap(value) {
2035
- if (clack.isCancel(value)) cancelAndExit();
2036
- return value;
2037
- }
2038
- /** The full `kubb.config.ts` for `choices`, with each plugin added the way `kubb add` adds it. */
2039
- function buildConfig({ inputPath, outputPath, plugins }) {
2040
- const { source, outcomes } = editConfig({
2041
- source: generateConfigFile({
2042
- inputPath,
2043
- outputPath
2044
- }),
2045
- edits: plugins.map((entry) => ({
2046
- operation: ConfigEditOperation.ADD_PLUGIN,
2047
- plugin: entry.artifact.package
2048
- })),
2049
- extensions: new Map(plugins.map(({ artifact, manifest }) => [artifact.package, {
2050
- package: artifact.package,
2051
- version: artifact.version,
2052
- manifest
2053
- }]))
2054
- });
2055
- const failed = outcomes.find((outcome) => !outcome.applied);
2056
- if (failed && failed.edit.operation === ConfigEditOperation.ADD_PLUGIN) throw new Diagnostics.Error({
2057
- code: Diagnostics.code.unknown,
2058
- severity: "error",
2059
- message: `Could not add ${failed.edit.plugin} to ${KUBB_CONFIG_FILENAME}: ${failed.reason}`,
2060
- help: `Run kubb init without that plugin, then add it with kubb add.`
2061
- });
2062
- return source;
2063
- }
2064
- /** Generates from the config at `configPath` without reporters, and returns how many files it wrote. */
2065
- async function firstRun(configPath) {
2066
- const { configs } = await getConfigs({ configPath });
2067
- let files = 0;
2068
- for (const config of configs) {
2069
- const result = await createKubb(config).generate();
2070
- if (!result.success) throw new Error(result.diagnostics.flatMap((diagnostic) => diagnostic.severity === "error" ? [diagnostic.message] : []).join("\n"));
2071
- files += result.files.length;
2072
- }
2073
- return files;
2074
- }
2075
- /** The command that runs `kubb generate`: the `generate` script when it runs Kubb, otherwise `npx kubb generate`. */
2076
- function generateCommand({ packageManager, script, watch }) {
2077
- if (script !== "kubb generate") return `npx kubb generate${watch ? " --watch" : ""}`;
2078
- const run = [
2079
- packageManager.name,
2080
- ...packageManager.runCommand,
2081
- "generate"
2082
- ].join(" ");
2083
- if (!watch) return run;
2084
- return packageManager.name === "npm" ? `${run} -- --watch` : `${run} --watch`;
2085
- }
2086
- /**
2087
- * Runs the Kubb setup wizard: detects the package manager and specs, asks for the spec, plugins, and
2088
- * output folder, shows the config for review, then installs, writes `kubb.config.ts`, adds a
2089
- * `generate` script, and runs the first generation.
2090
- * Pass `yes: true` to skip all prompts and print one line per step.
2091
- */
2092
- async function run$4({ yes, input: inputFlag, output: outputFlag, plugins: pluginsFlag, dryRun }) {
2093
- const cwd = process$1.cwd();
2094
- /** Runs `task` behind a spinner, or prints one line when it is done in `--yes` mode. */
2095
- async function step({ start, task, done, failed }) {
2096
- const spinner = createSpinner();
2097
- if (!yes) spinner.start(start);
2098
- try {
2099
- const result = await task();
2100
- if (yes) console.log(`${styleText("green", "✓")} ${done(result)}`);
2101
- else spinner.stop(done(result));
2102
- return result;
2103
- } catch (error) {
2104
- if (!yes) spinner.error(failed);
2105
- throw error;
2106
- }
2107
- }
2108
- if (!yes) logIntro({ title: isInteractiveRun({
2109
- yes,
2110
- dryRun: dryRun.enabled
2111
- }) ? void 0 : "Set up Kubb" });
2112
- try {
2113
- const registry = fetchRegistryIndex({ registry: registryUrl() }).then((entries) => entries.filter((entry) => entry.kind === "plugin" && !incompatibility(entry.manifest)));
2114
- registry.catch(() => {});
2115
- if (!await exists(path.join(cwd, "package.json"))) {
2116
- if (!yes) {
2117
- if (!unwrap(await clack.confirm({
2118
- message: "No package.json found. Would you like to create one?",
2119
- initialValue: true
2120
- }))) cancelAndExit();
2121
- }
2122
- const packageManager = detectPackageManager(cwd);
2123
- await step({
2124
- start: `Initializing package.json with ${packageManager.name}`,
2125
- task: () => initPackageJson(cwd, packageManager),
2126
- done: () => `Created package.json with ${packageManager.name}`,
2127
- failed: "Could not create package.json"
2128
- });
2129
- }
2130
- const packageManager = detectPackageManager(cwd);
2131
- const specs = await findSpecFiles(cwd);
2132
- if (!yes) {
2133
- logStep(`Package manager ${packageManager.name}`);
2134
- if (specs[0]) {
2135
- const { version: specVersion, operations } = await readSpecInfo(path.join(cwd, specs[0]));
2136
- const details = [
2137
- specs[0].slice(2),
2138
- specVersion ? `${specVersion.startsWith("2") ? "Swagger" : "OpenAPI"} ${specVersion}` : void 0,
2139
- operations === void 0 ? void 0 : pluralize(operations, "operation"),
2140
- specs.length > 1 ? `+${specs.length - 1} more` : void 0
2141
- ];
2142
- logStep(`Found ${details.filter(Boolean).join(" · ")}`);
2143
- }
2144
- }
2145
- const plugins = pluginsFlag || yes ? await registry : void 0;
2146
- const defaultPlugins = (entries) => initDefaults.plugins.flatMap((id) => findPlugin({
2147
- plugins: entries,
2148
- id
2149
- }) ?? []);
2150
- const flagPlugins = pluginsFlag && plugins ? parsePluginsFlag({
2151
- plugins,
2152
- value: pluginsFlag
2153
- }) : void 0;
2154
- let choices = {
2155
- inputPath: inputFlag ?? specs[0] ?? initDefaults.inputPath,
2156
- outputPath: outputFlag ?? initDefaults.outputPath,
2157
- plugins: flagPlugins ?? (plugins ? defaultPlugins(plugins) : [])
2158
- };
2159
- if (yes && !choices.plugins.length) throw new Error("The registry has no default plugin. Pass --plugins to choose one.");
2160
- async function askInput(current) {
2161
- const choice = specs.length ? unwrap(await clack.select({
2162
- message: "Which spec should Kubb read?",
2163
- options: [
2164
- ...specs.map((spec) => ({
2165
- value: spec,
2166
- label: spec,
2167
- hint: "found in this repo"
2168
- })),
2169
- {
2170
- value: "url",
2171
- label: "A URL"
2172
- },
2173
- {
2174
- value: "file",
2175
- label: "Another file"
2176
- }
2177
- ],
2178
- initialValue: specs.includes(current) ? current : /^https?:\/\//.test(current) ? "url" : "file"
2179
- })) : /^https?:\/\//.test(current) ? "url" : "file";
2180
- if (specs.includes(choice)) return choice;
2181
- const isUrl = choice === "url";
2182
- const initial = isUrl === /^https?:\/\//.test(current) && !specs.includes(current) ? current : void 0;
2183
- return unwrap(await clack.text({
2184
- message: isUrl ? "Spec URL" : "Spec path",
2185
- placeholder: isUrl ? "https://example.com/openapi.yaml" : initDefaults.inputPath,
2186
- initialValue: initial,
2187
- defaultValue: isUrl ? void 0 : initDefaults.inputPath,
2188
- validate: (value) => {
2189
- if (isUrl && !/^https?:\/\//.test(value ?? "")) return "Enter an http(s) URL";
2190
- }
2191
- }));
2192
- }
2193
- async function askPlugins(previous) {
2194
- const plugins = await registry;
2195
- const current = previous.length ? previous : defaultPlugins(plugins);
2196
- const width = Math.max(...plugins.map((entry) => entry.manifest.displayName.length));
2197
- const nameWidth = Math.max(...plugins.map((entry) => entry.manifest.name.length));
2198
- const values = unwrap(await clack.multiselect({
2199
- message: "What should Kubb generate?",
2200
- options: plugins.map(({ id, manifest }) => ({
2201
- value: id,
2202
- label: `${manifest.displayName.padEnd(width)} ${styleText("dim", manifest.name.padEnd(nameWidth))}`.trimEnd()
2203
- })),
2204
- initialValues: current.map((entry) => entry.id),
2205
- required: true
2206
- }));
2207
- return plugins.filter((entry) => values.includes(entry.id));
2208
- }
2209
- const answered = yes || Boolean(inputFlag && outputFlag && pluginsFlag);
2210
- if (!answered) {
2211
- let firstPass = true;
2212
- while (true) {
2213
- choices = {
2214
- inputPath: firstPass && inputFlag ? inputFlag : await askInput(choices.inputPath),
2215
- plugins: firstPass && flagPlugins ? flagPlugins : await askPlugins(choices.plugins),
2216
- outputPath: firstPass && outputFlag ? outputFlag : unwrap(await clack.text({
2217
- message: "Output folder",
2218
- placeholder: choices.outputPath,
2219
- defaultValue: choices.outputPath
2220
- }))
2221
- };
2222
- firstPass = false;
2223
- const missing = missingPluginDependencies(choices.plugins.map((entry) => entry.manifest));
2224
- if (missing.length) {
2225
- logWarn(`${missing.join("\n")}\nAdd the missing plugins.`);
2226
- continue;
2227
- }
2228
- const config = buildConfig(choices);
2229
- const packages = [KUBB_PACKAGE_NAME, ...choices.plugins.map((entry) => entry.artifact.package)];
2230
- logStep(`Review ${KUBB_CONFIG_FILENAME}`);
2231
- logBlock([
2232
- ...config.trimEnd().split("\n"),
2233
- "",
2234
- `${styleText("dim", "Installs")} ${packages.join(" ")} with ${packageManager.name}`
2235
- ]);
2236
- const configExists = await exists(path.join(cwd, KUBB_CONFIG_FILENAME));
2237
- if (unwrap(await clack.select({
2238
- message: configExists ? `kubb.config.ts exists. Overwrite it?` : "Create it?",
2239
- options: [{
2240
- value: true,
2241
- label: "Yes"
2242
- }, {
2243
- value: false,
2244
- label: "Change a choice"
2245
- }]
2246
- }))) break;
2247
- }
2248
- }
2249
- const missing = missingPluginDependencies(choices.plugins.map((entry) => entry.manifest));
2250
- if (missing.length) throw new Diagnostics.Error({
2251
- code: Diagnostics.code.missingDependency,
2252
- severity: "error",
2253
- message: `${missing.join("\n")}\nAdd the missing plugins and run kubb init again.`
2254
- });
2255
- const configPath = path.join(cwd, KUBB_CONFIG_FILENAME);
2256
- if (answered && !yes && await exists(configPath)) {
2257
- if (!unwrap(await clack.confirm({
2258
- message: `kubb.config.ts exists. Overwrite it?`,
2259
- initialValue: false
2260
- }))) cancelAndExit("Keeping existing configuration.");
2261
- }
2262
- const packagesToInstall = [`${KUBB_PACKAGE_NAME}@${version}`, ...choices.plugins.map((entry) => installSpec(entry.artifact))];
2263
- const packageNames = [KUBB_PACKAGE_NAME, ...choices.plugins.map((entry) => entry.artifact.package)];
2264
- const dry = { message: `install ${packagesToInstall.length} packages with ${packageManager.name}` };
2265
- if (yes) await step({
2266
- start: "",
2267
- task: async () => void await dryRun.run(() => installPackages(packagesToInstall, packageManager, cwd), dry),
2268
- done: () => `Installed ${packageNames.join(" ")} with ${packageManager.name}`,
2269
- failed: "Installation failed"
2270
- });
2271
- else await installWithSpinner({
2272
- packages: packagesToInstall,
2273
- names: `${packagesToInstall.length} packages`,
2274
- packageManager,
2275
- cwd,
2276
- install: async (packages, packageManager, cwd, onLine) => void await dryRun.run(() => installPackages(packages, packageManager, cwd, onLine), dry)
2277
- });
2278
- await step({
2279
- start: `Writing ${KUBB_CONFIG_FILENAME}`,
2280
- task: async () => dryRun.run(async () => void await write(configPath, buildConfig(choices)), { message: `write ${KUBB_CONFIG_FILENAME}` }),
2281
- done: () => `Wrote ${KUBB_CONFIG_FILENAME}`,
2282
- failed: `Could not write ${KUBB_CONFIG_FILENAME}`
2283
- });
2284
- const scriptAdded = await dryRun.run(() => addGenerateScript(cwd), {
2285
- message: "add a generate script to package.json",
2286
- resolve: async () => await generateScript(cwd) === void 0
2287
- });
2288
- if (scriptAdded && !yes) logStep("Added \"generate\": \"kubb generate\" to package.json");
2289
- const script = scriptAdded ? "kubb generate" : await generateScript(cwd);
2290
- const runStart = process$1.hrtime();
2291
- try {
2292
- await dryRun.run(() => step({
2293
- start: "Running the first generation",
2294
- task: () => firstRun(configPath),
2295
- done: (files) => yes ? `Generated ${pluralize(files, "file")} in ${choices.outputPath}` : `First run ${pluralize(files, "file")} in ${choices.outputPath} · ${formatMs(getElapsedMs(runStart))}`,
2296
- failed: "First run failed"
2297
- }), {
2298
- message: "run the first generation",
2299
- resolve: () => 0
2300
- });
2301
- } catch (error) {
2302
- logError(error instanceof Error ? error.message : String(error));
2303
- const message = `Kubb is set up, but the first run failed. Fix the error, then run ${generateCommand({
2304
- packageManager,
2305
- script,
2306
- watch: false
2307
- })}.`;
2308
- if (yes) console.log(message);
2309
- else logOutro(styleText("yellow", message));
2310
- process$1.exitCode = 1;
2311
- return;
2312
- }
2313
- if (yes) {
2314
- console.log(`Kubb is set up. Run ${generateCommand({
2315
- packageManager,
2316
- script,
2317
- watch: false
2318
- })} after changing the spec.`);
2319
- return;
2320
- }
2321
- logOutro(`${styleText("green", "✓ Kubb is set up")}\n ${styleText("dim", "Next")} ${generateCommand({
2322
- packageManager,
2323
- script,
2324
- watch: true
2325
- })}`);
2326
- } catch (error) {
2327
- logError(styleText("red", "An error occurred during initialization"));
2328
- if (error instanceof Error) logError(error.message);
2329
- process$1.exit(1);
2330
- }
2331
- }
2332
- __name(run$4, "run");
2333
- const command$3 = defineWithTypes()({
2334
- name: "init",
2335
- description: "Set up a config, install plugins, and run the first generation.",
2336
- examples: [
2337
- "kubb init",
2338
- "kubb init --yes",
2339
- "kubb init --input ./openapi.yaml --output ./src/gen --plugins plugin-ts,plugin-zod",
2340
- "kubb init --yes --input ./openapi.yaml --plugins ts,zod --output src/api",
2341
- "kubb init --plugins plugin-ts,plugin-axios,plugin-react-query",
2342
- "kubb init --yes --dry-run"
2343
- ].join("\n"),
2344
- toKebab: true,
2345
- args: {
2346
- yes: {
2347
- type: "boolean",
2348
- description: "Skip prompts and use the detected spec, the default plugin, and the default output folder",
2349
- short: "y",
2350
- default: false
2351
- },
2352
- input: {
2353
- type: "string",
2354
- description: "Path to the OpenAPI specification",
2355
- short: "i",
2356
- metavar: "path"
2357
- },
2358
- output: {
2359
- type: "string",
2360
- description: "Output directory for generated files",
2361
- short: "o",
2362
- metavar: "path"
2363
- },
2364
- plugins: {
2365
- type: "string",
2366
- description: "Comma-separated Kubb registry ids of the plugins to add, the same ids `kubb add` takes (plugin-ts, plugin-zod, acme/plugin-x), or short names (ts, zod)",
2367
- metavar: "plugin-ts,plugin-zod,..."
2368
- }
2369
- },
2370
- run: ({ values, extensions }) => run$4({
2371
- yes: values.yes,
2372
- input: values.input,
2373
- output: values.output,
2374
- plugins: values.plugins,
2375
- dryRun: extensions[dryRunId]
2376
- })
2377
- });
2378
- //#endregion
2379
- //#region src/ui/table.ts
2380
- /** Pads each column to its widest cell, one line per row. */
2381
- function formatTable(rows) {
2382
- const widths = Array.from({ length: Math.max(0, ...rows.map((row) => row.length)) }, (_, index) => Math.max(...rows.map((row) => row[index]?.length ?? 0)));
2383
- return rows.map((row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ").trimEnd());
2384
- }
2385
- //#endregion
2386
- //#region src/runners/list/run.ts
2387
- /**
2388
- * The installed packages that ship a `kubb-extension.json` or have a registry entry, with the
2389
- * version the registry pins.
2390
- */
2391
- async function listExtensions({ cwd, registry }) {
2392
- const [installed, entries] = await Promise.all([readInstalledPackages({ root: cwd }), fetchRegistryIndex({ registry }).catch((error) => {
2393
- logWarn(`Showing installed extensions only: ${getErrorMessage(error)}`);
2394
- return [];
2395
- })]);
2396
- const byPackage = new Map(entries.map((entry) => [entry.artifact.package, entry]));
2397
- return installed.flatMap(({ package: name, version, manifest }) => {
2398
- const entry = byPackage.get(name);
2399
- if (!entry && !manifest) return [];
2400
- const latest = entry?.artifact.version;
2401
- return [{
2402
- id: entry?.id,
2403
- package: name,
2404
- installed: version,
2405
- registry: latest,
2406
- update: !!latest && isOutdated({
2407
- installed: version,
2408
- latest
2409
- })
2410
- }];
2411
- });
2412
- }
2413
- /** Prints the installed extensions as a table. */
2414
- async function run$3() {
2415
- try {
2416
- const rows = await listExtensions({
2417
- cwd: process$1.cwd(),
2418
- registry: registryUrl()
2419
- });
2420
- if (!rows.length) {
2421
- logOutro("No installed extensions found.");
2422
- return;
2423
- }
2424
- const [head, ...body] = formatTable([[
2425
- "ID",
2426
- "PACKAGE",
2427
- "INSTALLED",
2428
- "REGISTRY",
2429
- "UPDATE"
2430
- ], ...rows.map((row) => [
2431
- row.id ?? "-",
2432
- row.package,
2433
- row.installed ?? "-",
2434
- row.registry ?? "-",
2435
- row.update ? "yes" : "no"
2436
- ])]);
2437
- logBlock([styleText("dim", head), ...body]);
2438
- } catch (error) {
2439
- logError(getErrorMessage(error));
2440
- process$1.exitCode = 1;
2441
- }
2442
- }
2443
- __name(run$3, "run");
2444
- const command$2 = define({
2445
- name: "list",
2446
- description: "List installed extensions, their versions, and available updates.",
2447
- examples: "kubb list",
2448
- run: () => run$3()
2449
- });
2450
- //#endregion
2451
- //#region src/runners/remove/run.ts
2452
- /** The lines of `before` that `after` no longer has, trimmed. */
2453
- function removedLines({ before, after }) {
2454
- const remaining = after.split("\n");
2455
- return before.split("\n").filter((line) => {
2456
- const index = remaining.indexOf(line);
2457
- if (index !== -1) remaining.splice(index, 1);
2458
- return index === -1 && line.trim() !== "";
2459
- }).map((line) => line.trim());
2460
- }
2461
- /**
2462
- * Removes a plugin or parser from `kubb.config.ts` and uninstalls its package. Refuses the adapter,
2463
- * which the config needs, and an extension another one in the config depends on. Previews the
2464
- * change before asking.
2465
- */
2466
- async function run$2({ id: input, configPath, yes, uninstall }) {
2467
- const cwd = process$1.cwd();
2468
- const hrStart = process$1.hrtime();
2469
- const summary = (title, status, lines) => logCommandSummary({
2470
- title,
2471
- status,
2472
- hrStart,
2473
- block: true,
2474
- lines
2475
- });
2476
- const fail = (title, lines) => {
2477
- summary(title, "failed", lines);
2478
- process$1.exitCode = 1;
2479
- };
2480
- logIntro({ title: isInteractiveRun({ yes }) ? void 0 : styleText("bgCyan", styleText("black", " Kubb Remove ")) });
2481
- try {
2482
- const id = toRegistryId(input);
2483
- const configFile = configPath ? path.resolve(configPath) : findConfigFile(cwd);
2484
- if (!configFile) throw new Diagnostics.Error({
2485
- code: Diagnostics.code.unknown,
2486
- severity: "error",
2487
- message: "No kubb.config.ts found. Pass its path with --config.",
2488
- help: "Run `kubb remove <id> --config <path>`."
2489
- });
2490
- const configName = path.relative(cwd, configFile) || configFile;
2491
- const [entries, installed, source] = await withSpinner({
2492
- message: "Reading the registry",
2493
- promise: Promise.all([
2494
- fetchRegistryIndex({ registry: registryUrl() }),
2495
- readInstalledPackages({ root: cwd }),
2496
- read(configFile)
2497
- ])
2498
- });
2499
- const versions = new Map(installed.map((item) => [item.package, item.version]));
2500
- const entry = entries.find((candidate) => candidate.id === id);
2501
- if (!entry) {
2502
- const suggestions = closestIds({
2503
- input: id,
2504
- ids: entries.map((candidate) => candidate.id)
2505
- }).map(toShortId);
2506
- return fail(`No extension named ${input}`, suggestions.map((suggestion) => ` kubb remove ${suggestion}`));
2507
- }
2508
- const { artifact, manifest } = entry;
2509
- const kind = manifest.kind.charAt(0).toUpperCase() + manifest.kind.slice(1);
2510
- if (manifest.kind === extensionKinds.adapter) return fail("The adapter cannot be removed", [` A config needs one adapter. Change it in ${configName} by hand.`]);
2511
- const view = readConfig(source);
2512
- const running = new Set(view.managed ? view.configs.flatMap((config) => [...config.plugins, ...config.parsers ?? []].filter((call) => !call.disabled).map((call) => call.packageName)) : []);
2513
- const dependents = entries.filter((other) => other.id !== entry.id && other.manifest.dependencies?.includes(manifest.name) && running.has(other.artifact.package));
2514
- if (dependents.length) {
2515
- const names = dependents.map((other) => other.manifest.name).join(", ");
2516
- return fail(`${names} ${dependents.length === 1 ? "needs" : "need"} ${manifest.name}`, [` Remove ${names} first.`]);
2517
- }
2518
- const edits = [{
2519
- operation: ConfigEditOperation.REMOVE_EXTENSION,
2520
- ...manifest.kind === extensionKinds.parser ? { kind: extensionKinds.parser } : {},
2521
- plugin: artifact.package
2522
- }];
2523
- const preview = editConfig({
2524
- source,
2525
- edits
2526
- });
2527
- const outcome = preview.outcomes[0];
2528
- if (!outcome?.applied) return fail(`Could not edit ${configName}`, [` ${outcome?.reason ?? "the config file was not changed"}`]);
2529
- const bundled = BUNDLED_PACKAGES.includes(artifact.package);
2530
- const stillImported = readImports(preview.source).includes(artifact.package);
2531
- const uninstalls = uninstall && !bundled && !stillImported && versions.has(artifact.package);
2532
- if (!preview.changed && !uninstalls) {
2533
- summary(`${manifest.name} is not in the project`, "success", [" No changes made"]);
2534
- return;
2535
- }
2536
- const packageLine = (() => {
2537
- if (uninstalls) return `Uninstalls ${artifact.package}`;
2538
- if (bundled) return `Keeps ${artifact.package}, which ships with kubb`;
2539
- if (stillImported) return `Keeps ${artifact.package}, which another config entry still imports`;
2540
- return "Uninstalls nothing";
2541
- })();
2542
- logStep(`${kind} ${styleText("cyan", manifest.name)} v${versions.get(artifact.package) ?? artifact.version}`);
2543
- logBlock([
2544
- `Config ${configName}`,
2545
- ...removedLines({
2546
- before: source,
2547
- after: preview.source
2548
- }).map((line) => styleText("red", ` - ${line}`)),
2549
- packageLine
2550
- ]);
2551
- const confirmed = yes || await clack.confirm({
2552
- message: "Remove it?",
2553
- initialValue: true
2554
- });
2555
- if (clack.isCancel(confirmed) || !confirmed) {
2556
- summary("Remove canceled", "canceled", [` ${kind} ${manifest.name}`, " No changes made"]);
2557
- return;
2558
- }
2559
- if (preview.changed) {
2560
- const written = await editConfigFile({
2561
- filePath: configFile,
2562
- edits
2563
- });
2564
- if (!written.outcomes[0]?.applied) return fail(`Could not edit ${configName}`, [` ${written.outcomes[0]?.reason ?? "the config file was not changed"}`]);
2565
- logStep(`${manifest.export}() removed from ${configName}`);
2566
- }
2567
- const packageManager = detectPackageManager(cwd);
2568
- if (uninstalls) await withSpinner({
2569
- message: `Uninstalling ${artifact.package} with ${packageManager.name}`,
2570
- promise: uninstallPackages([artifact.package], packageManager, cwd)
2571
- });
2572
- summary(`${kind} removed`, "success", [
2573
- ` Packages ${uninstalls ? `${artifact.package} uninstalled with ${packageManager.name}` : "none uninstalled"}`,
2574
- "",
2575
- "Next kubb generate"
2576
- ]);
2577
- } catch (error) {
2578
- logError(getErrorMessage(error));
2579
- fail("Remove failed", [" See the error above"]);
2580
- }
2581
- }
2582
- __name(run$2, "run");
2583
- const command$1 = define({
2584
- name: "remove",
2585
- description: "Remove a plugin or parser from kubb.config.ts and uninstall its package.",
2586
- examples: [
2587
- "kubb remove plugin-zod",
2588
- "kubb remove acme/plugin-x --yes",
2589
- "kubb remove plugin-zod --no-uninstall"
2590
- ].join("\n"),
2591
- toKebab: true,
2592
- args: {
2593
- id: {
2594
- type: "positional",
2595
- description: "Registry id: owner/slug, or a slug for an official extension",
2596
- required: true
2597
- },
2598
- ...configArg,
2599
- ...yesArg,
2600
- uninstall: {
2601
- type: "boolean",
2602
- description: "Uninstall the package. Pass --no-uninstall to only edit the config",
2603
- negatable: true,
2604
- default: true
2605
- }
2606
- },
2607
- run: ({ values }) => run$2({
2608
- id: values.id,
2609
- configPath: values.config,
2610
- yes: values.yes,
2611
- uninstall: values.uninstall
2612
- })
2613
- });
2614
- //#endregion
2615
- //#region src/runners/update/run.ts
2616
- /**
2617
- * Generates every config in this process, the way `kubb generate` does, and returns the output files
2618
- * it changed. Runs after the install, so the config loads the new versions. `undefined` when the
2619
- * config fails to load or a build fails.
2620
- */
2621
- async function regenerate({ cwd, configPath }) {
2622
- const spinner = createSpinner();
2623
- spinner.start("Generating with the new versions");
2624
- try {
2625
- const [{ getConfigs }, { generate }] = await Promise.all([import("./config-DWDpFSn1.js"), import("./run-YMSJnvYl.js")]);
2626
- const { configs } = await getConfigs({ configPath });
2627
- const dirs = configs.map((config) => path.resolve(config.root ?? cwd, config.output.path));
2628
- const before = new Map((await Promise.all(dirs.map(readOutputFiles))).flatMap((files) => [...files]));
2629
- const hooks = new Hookable();
2630
- for (const config of configs) if (!await generate({
2631
- config,
2632
- hooks
2633
- })) throw new Error("Generation failed");
2634
- const after = new Map((await Promise.all(dirs.map(readOutputFiles))).flatMap((files) => [...files]));
2635
- const diff = diffFiles(before, after);
2636
- spinner.stop(diff.files.length ? `Generated: ${pluralize(diff.files.length, "file")} changed` : "Generated: no files changed");
2637
- return {
2638
- ...diff,
2639
- output: dirs.map((dir) => path.relative(cwd, dir) || ".").join(", ")
2640
- };
2641
- } catch (error) {
2642
- spinner.error(`Generation failed: ${getErrorMessage(error)}`);
2643
- return;
2644
- }
2645
- }
2646
- /**
2647
- * Moves installed extensions to the version the registry pins. Shows each version change with its
2648
- * bump, asks which to install with majors left out, installs them, and regenerates to show which
2649
- * files changed. With `dryRun`, only lists the updates and installs nothing.
2650
- */
2651
- async function run$1({ ids, dryRun, yes }) {
2652
- const cwd = process$1.cwd();
2653
- const hrStart = process$1.hrtime();
2654
- const installedPackages = [];
2655
- const summary = (title, status, lines) => logCommandSummary({
2656
- title,
2657
- status,
2658
- hrStart,
2659
- block: true,
2660
- lines
2661
- });
2662
- logIntro({ title: isInteractiveRun({
2663
- yes,
2664
- dryRun
2665
- }) ? void 0 : styleText("bgCyan", styleText("black", " Kubb Update ")) });
2666
- try {
2667
- const wanted = new Set(ids.map(toRegistryId));
2668
- const [entries, installed] = await withSpinner({
2669
- message: "Checking the registry",
2670
- promise: Promise.all([fetchRegistryIndex({ registry: registryUrl() }), readInstalledPackages({ root: cwd })])
2671
- });
2672
- const versions = new Map(installed.map((item) => [item.package, item.version]));
2673
- const tracked = entries.filter((entry) => versions.has(entry.artifact.package) && (!wanted.size || wanted.has(entry.id)));
2674
- for (const id of wanted) {
2675
- if (tracked.some((entry) => entry.id === id)) continue;
2676
- logWarn(`${id} is not installed, or not in the registry.`);
2677
- process$1.exitCode = 1;
2678
- }
2679
- const current = ({ artifact }) => versions.get(artifact.package);
2680
- const isMajor = (entry) => bumpOf({
2681
- installed: current(entry),
2682
- latest: entry.artifact.version
2683
- }) === "major";
2684
- const outdated = tracked.filter((entry) => isOutdated({
2685
- installed: current(entry),
2686
- latest: entry.artifact.version
2687
- }));
2688
- const proposals = outdated.filter((entry) => {
2689
- const problem = incompatibility(entry.manifest);
2690
- if (problem) {
2691
- logWarn(`Skipped ${entry.id}: ${problem}`);
2692
- if (wanted.has(entry.id)) process$1.exitCode = 1;
2693
- }
2694
- return !problem;
2695
- });
2696
- if (!proposals.length) {
2697
- const title = outdated.length ? "No compatible updates available" : "Already up to date";
2698
- summary(tracked.length ? title : "No installed extensions to update", outdated.length ? "warning" : "success", [` ${tracked.length - outdated.length} of ${tracked.length} up to date`, ...ids.length ? ["", `No updates for ${ids.join(", ")}.`] : []]);
2699
- return;
2700
- }
2701
- if (tracked.length) {
2702
- const lines = formatTable([[
2703
- "Extension",
2704
- "Current",
2705
- "Latest",
2706
- ""
2707
- ], ...tracked.map((entry) => {
2708
- if (!outdated.includes(entry)) return [
2709
- entry.artifact.package,
2710
- current(entry) ?? "-",
2711
- "up to date",
2712
- ""
2713
- ];
2714
- const bump = bumpOf({
2715
- installed: current(entry),
2716
- latest: entry.artifact.version
2717
- }) ?? "";
2718
- return [
2719
- entry.artifact.package,
2720
- current(entry) ?? "not installed",
2721
- entry.artifact.version,
2722
- bump === "major" ? "major · breaking" : bump
2723
- ];
2724
- })]);
2725
- logStep(`Checked ${pluralize(tracked.length, "extension")} against the registry`);
2726
- logBlock([styleText("dim", lines[0]), ...lines.slice(1).map((line, index) => {
2727
- if (!outdated.includes(tracked[index])) return styleText("dim", line);
2728
- return line.endsWith("major · breaking") ? styleText("yellow", line) : line;
2729
- })]);
2730
- }
2731
- if (dryRun) {
2732
- summary("Updates available", "planned", [`Dry run: nothing was installed. Run kubb update to install ${proposals.length === 1 ? "it" : "them"}.`]);
2733
- return;
2734
- }
2735
- const defaults = proposals.filter((entry) => wanted.has(entry.id) || !isMajor(entry));
2736
- const chosen = yes ? defaults.map((entry) => entry.id) : await clack.multiselect({
2737
- message: "Select the updates to install",
2738
- options: proposals.map((entry) => ({
2739
- value: entry.id,
2740
- label: entry.artifact.package,
2741
- hint: `${current(entry) ?? "not installed"} → ${entry.artifact.version}`
2742
- })),
2743
- initialValues: defaults.map((entry) => entry.id),
2744
- required: false
2745
- });
2746
- if (clack.isCancel(chosen)) {
2747
- summary("Update canceled", "canceled", [" No changes made"]);
2748
- return;
2749
- }
2750
- const selected = proposals.filter((entry) => chosen.includes(entry.id));
2751
- const skipped = proposals.filter((entry) => !selected.includes(entry)).map((entry) => {
2752
- const kind = entry.manifest.kind.charAt(0).toUpperCase() + entry.manifest.kind.slice(1);
2753
- return ` ${styleText("dim", "Skipped")} ${kind} ${entry.artifact.package} ${entry.artifact.version} ${styleText("dim", `run kubb update ${toShortId(entry.id)} later`)}`;
2754
- });
2755
- if (!selected.length) {
2756
- summary("No updates installed", "canceled", [" No updates selected.", ...skipped]);
2757
- return;
2758
- }
2759
- const packageManager = detectPackageManager(cwd);
2760
- const names = selected.map(({ artifact }) => artifact.package).join(", ");
2761
- await installWithSpinner({
2762
- packages: selected.map(({ artifact }) => installSpec(artifact)),
2763
- names,
2764
- packageManager,
2765
- cwd
2766
- });
2767
- installedPackages.push(...selected.map((entry) => entry.artifact.package));
2768
- const foundConfig = findConfigFile(cwd);
2769
- const configPath = foundConfig && !yes && await clack.confirm({
2770
- message: "Regenerate now? This rewrites the generated files.",
2771
- initialValue: true
2772
- }) === true ? foundConfig : void 0;
2773
- const changed = configPath ? await regenerate({
2774
- cwd,
2775
- configPath
2776
- }) : void 0;
2777
- const generationFailed = Boolean(configPath && !changed);
2778
- const fileCounts = {
2779
- A: 0,
2780
- M: 0,
2781
- D: 0
2782
- };
2783
- for (const file of changed?.files ?? []) fileCounts[file.status]++;
2784
- summary(generationFailed ? "Extensions updated; generation failed" : "Update complete", generationFailed ? "warning" : "success", [
2785
- ` Packages ${selected.length} installed with ${packageManager.name}${skipped.length ? ` · ${skipped.length} skipped` : ""}`,
2786
- ...skipped,
2787
- ...changed ? [` Files ${fileCounts.A} added · ${fileCounts.M} modified · ${fileCounts.D} deleted`] : [],
2788
- ...!configPath ? [" Regeneration not run"] : [],
2789
- ...generationFailed ? ["", "Fix the generation error, then run kubb generate."] : []
2790
- ]);
2791
- } catch (error) {
2792
- logError(getErrorMessage(error));
2793
- summary("Update failed", "failed", installedPackages.length ? [` Packages ${installedPackages.join(", ")} installed`] : [" No changes made"]);
2794
- process$1.exitCode = 1;
2795
- }
2796
- }
2797
- __name(run$1, "run");
2798
- const command = defineWithTypes()({
2799
- name: "update",
2800
- description: "Update installed extensions, regenerate, and summarize file changes.",
2801
- examples: [
2802
- "kubb update",
2803
- "kubb update --dry-run",
2804
- "kubb update plugin-zod plugin-ts --yes"
2805
- ].join("\n"),
2806
- args: {
2807
- ids: {
2808
- type: "positional",
2809
- description: "Registry ids to update, all installed extensions when omitted",
2810
- multiple: true
2811
- },
2812
- ...yesArg
2813
- },
2814
- run: ({ values, extensions }) => run$1({
2815
- ids: values.ids ?? [],
2816
- dryRun: extensions[dryRunId].enabled,
2817
- yes: values.yes
2818
- })
2819
- });
2820
- //#endregion
2821
- //#region src/index.ts
144
+ /** Flags that print the root help, which lists every subcommand. */
145
+ const HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h"]);
2822
146
  /** Flags that short-circuit execution (help and version). The telemetry notice is suppressed for these. */
2823
147
  const QUIET_FLAGS = /* @__PURE__ */ new Set([
2824
148
  "--help",
@@ -2832,24 +156,23 @@ const QUIET_FLAGS = /* @__PURE__ */ new Set([
2832
156
  * quiet flag is passed, then runs the given command, or `generate` when none is given.
2833
157
  */
2834
158
  async function run(argv = process.argv.slice(2)) {
2835
- const args = routeStudioFlags(resolveDeprecatedFlags(argv));
159
+ const resolvedArgs = resolveDeprecatedFlags(argv);
160
+ const name = resolvedArgs.find((arg) => !arg.startsWith("-")) ?? "";
161
+ let names = [];
162
+ if (Object.hasOwn(commandLoaders, name)) names = [name];
163
+ if (!names.length && resolvedArgs.some((arg) => HELP_FLAGS.has(arg))) names = Object.keys(commandLoaders);
164
+ const modules = Object.fromEntries(await Promise.all(names.map(async (key) => [key, await commandLoaders[key]()])));
165
+ const args = name === "studio" ? (await import("./studio-mw6HaPkr.js")).routeStudioFlags(resolvedArgs) : resolvedArgs;
2836
166
  const isQuietFlag = args.some((arg) => QUIET_FLAGS.has(arg));
2837
167
  const isJsonOutput = args.includes("--json") || args.some((arg, index) => /^--reporter=.*\bjson\b/.test(arg) || arg === "--reporter" && /\bjson\b/.test(args[index + 1] ?? ""));
2838
168
  if (!isDisabled() && !isQuietFlag) console.error(`${styleText("yellow", "Notice:")} Kubb collects anonymous telemetry data to help improve the tool. No personal data or file contents are collected. \nTo disable, set ${styleText("cyan", "KUBB_DISABLE_TELEMETRY=1")}.\n`);
2839
- await cli(args, command$14, {
169
+ await cli(args, command, {
2840
170
  name: "kubb",
2841
171
  version,
2842
172
  description: "Generate code from an OpenAPI specification, manage Kubb extensions, or connect the project to Kubb Studio.",
2843
173
  subCommands: {
2844
- generate: command$14,
2845
- init: command$3,
2846
- validate: command$5,
2847
- mcp: command$13,
2848
- studio: command$6,
2849
- add: command$4,
2850
- update: command,
2851
- remove: command$1,
2852
- list: command$2
174
+ generate: command,
175
+ ...Object.fromEntries(Object.entries(modules).map(([key, module]) => [key, module.command]))
2853
176
  },
2854
177
  fallbackToEntry: true,
2855
178
  renderHeader: isJsonOutput ? null : async (ctx) => {
@@ -2868,7 +191,6 @@ async function run(argv = process.argv.slice(2)) {
2868
191
  }
2869
192
  });
2870
193
  }
2871
- //#endregion
2872
194
  export { run };
2873
195
 
2874
196
  //# sourceMappingURL=index.js.map