@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.
- package/bin/kubb.js +2 -1
- package/dist/{Telemetry-Dc79_cIA.js → Telemetry-B3edcFoB.js} +12 -86
- package/dist/Telemetry-B3edcFoB.js.map +1 -0
- package/dist/{Telemetry-Dc9DVxKQ.cjs → Telemetry-COWGqYmh.cjs} +14 -118
- package/dist/Telemetry-COWGqYmh.cjs.map +1 -0
- package/dist/{background-v5pDy3IJ.cjs → background-B24lIehn.cjs} +5 -6
- package/dist/{background-v5pDy3IJ.cjs.map → background-B24lIehn.cjs.map} +1 -1
- package/dist/{background-C877JFxE.js → background-D-7OGjvg.js} +1 -1
- package/dist/{background-3_KudPH-.js → background-_dKcGjM7.js} +5 -6
- package/dist/{background-3_KudPH-.js.map → background-_dKcGjM7.js.map} +1 -1
- package/dist/banner-BdTTDFkG.js +140 -0
- package/dist/banner-BdTTDFkG.js.map +1 -0
- package/dist/banner-wJFk-L-c.cjs +163 -0
- package/dist/banner-wJFk-L-c.cjs.map +1 -0
- package/dist/config-BL3HnFhb.js +1 -0
- package/dist/config-BWj9jBVj.cjs +62 -0
- package/dist/config-BWj9jBVj.cjs.map +1 -0
- package/dist/{config-CNqgDSu3.js → config-CQTxXvIG.js} +6 -52
- package/dist/config-CQTxXvIG.js.map +1 -0
- package/dist/{config-BG_Nr2CZ.cjs → config-CRrmj88c.cjs} +15 -62
- package/dist/config-CRrmj88c.cjs.map +1 -0
- package/dist/config-Uk3rbZfb.js +49 -0
- package/dist/config-Uk3rbZfb.js.map +1 -0
- package/dist/configFile-C0DIXNMD.js +993 -0
- package/dist/configFile-C0DIXNMD.js.map +1 -0
- package/dist/configFile-CoEpHdXG.cjs +1028 -0
- package/dist/configFile-CoEpHdXG.cjs.map +1 -0
- package/dist/{credentials-C8tjAhTo.js → credentials-B77iJx9H.js} +1 -1
- package/dist/{credentials-DDaKqtJn.cjs → credentials-DEsGwcGD.cjs} +3 -4
- package/dist/{credentials-DDaKqtJn.cjs.map → credentials-DEsGwcGD.cjs.map} +1 -1
- package/dist/{credentials-CZK6zPLv.js → credentials-osgjujE_.js} +3 -4
- package/dist/{credentials-CZK6zPLv.js.map → credentials-osgjujE_.js.map} +1 -1
- package/dist/env-C3xHRMez.js +1 -0
- package/dist/env-eu3XyKqJ.cjs +55 -0
- package/dist/env-eu3XyKqJ.cjs.map +1 -0
- package/dist/env-rv-VgSGN.js +34 -0
- package/dist/env-rv-VgSGN.js.map +1 -0
- package/dist/{errors-Cl69p_vI.cjs → errors-BponpdjN.cjs} +3 -4
- package/dist/{errors-Cl69p_vI.cjs.map → errors-BponpdjN.cjs.map} +1 -1
- package/dist/{errors-DmC83ALX.js → errors-wzqmMpMo.js} +3 -4
- package/dist/{errors-DmC83ALX.js.map → errors-wzqmMpMo.js.map} +1 -1
- package/dist/index.cjs +37 -2719
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +35 -2713
- package/dist/index.js.map +1 -1
- package/dist/installWithSpinner-DpnpxHq5.js +49 -0
- package/dist/installWithSpinner-DpnpxHq5.js.map +1 -0
- package/dist/installWithSpinner-ONGfIVUJ.cjs +56 -0
- package/dist/installWithSpinner-ONGfIVUJ.cjs.map +1 -0
- package/dist/{text-CEyJhBnh.js → intro-9PB8sE8z.js} +8 -52
- package/dist/intro-9PB8sE8z.js.map +1 -0
- package/dist/{text-CC2xdpGL.cjs → intro-YPaf1Imr.cjs} +12 -80
- package/dist/intro-YPaf1Imr.cjs.map +1 -0
- package/dist/isCIEnvironment-D6xtAAZ2.js +14 -0
- package/dist/isCIEnvironment-D6xtAAZ2.js.map +1 -0
- package/dist/isCIEnvironment-DiVOMI5Q.cjs +20 -0
- package/dist/isCIEnvironment-DiVOMI5Q.cjs.map +1 -0
- package/dist/mascot--q1xjKw1.js +63 -0
- package/dist/mascot--q1xjKw1.js.map +1 -0
- package/dist/mascot-JSA0j3Q_.cjs +80 -0
- package/dist/mascot-JSA0j3Q_.cjs.map +1 -0
- package/dist/mcp-CYPOFKnP.cjs +20 -0
- package/dist/mcp-CYPOFKnP.cjs.map +1 -0
- package/dist/mcp-CxeZR5vb.js +21 -0
- package/dist/mcp-CxeZR5vb.js.map +1 -0
- package/dist/package-D3Yss9Bb.js +5 -0
- package/dist/package-D3Yss9Bb.js.map +1 -0
- package/dist/package-aX0q9SZr.cjs +10 -0
- package/dist/package-aX0q9SZr.cjs.map +1 -0
- package/dist/{registry-ov1RYcD4.js → registry-B3eYk4LN.js} +7 -67
- package/dist/registry-B3eYk4LN.js.map +1 -0
- package/dist/{registry-BInbtBm7.cjs → registry-YE1pFWL-.cjs} +4 -82
- package/dist/registry-YE1pFWL-.cjs.map +1 -0
- package/dist/{reporters-DQz--AdE.cjs → reporters-2cgjvXwR.cjs} +32 -40
- package/dist/{reporters-DQz--AdE.cjs.map → reporters-2cgjvXwR.cjs.map} +1 -1
- package/dist/{reporters-BtD41K-C.js → reporters-BM430HdQ.js} +19 -27
- package/dist/{reporters-BtD41K-C.js.map → reporters-BM430HdQ.js.map} +1 -1
- package/dist/{rolldown-runtime-CPE0t6oh.cjs → rolldown-runtime-C3-Zg-KD.cjs} +1 -2
- package/dist/{rolldown-runtime-CRm0XQPb.js → rolldown-runtime-xo6NMXYd.js} +1 -2
- package/dist/run-1H9TGT8v.js +77 -0
- package/dist/run-1H9TGT8v.js.map +1 -0
- package/dist/run-B4zvF9WY.cjs +233 -0
- package/dist/run-B4zvF9WY.cjs.map +1 -0
- package/dist/run-BYP_t9dR.cjs +324 -0
- package/dist/run-BYP_t9dR.cjs.map +1 -0
- package/dist/{run-1oXPIkli.cjs → run-Bb19PlwV.cjs} +44 -37
- package/dist/run-Bb19PlwV.cjs.map +1 -0
- package/dist/{run-B03V0Poj.cjs → run-BoO7H8RA.cjs} +49 -41
- package/dist/run-BoO7H8RA.cjs.map +1 -0
- package/dist/run-BrM7-NSV.js +181 -0
- package/dist/run-BrM7-NSV.js.map +1 -0
- package/dist/{run-UsAMlUyy.js → run-C0KI-NE3.js} +9 -9
- package/dist/{run-UsAMlUyy.js.map → run-C0KI-NE3.js.map} +1 -1
- package/dist/run-CCLPt4hr.js +1 -0
- package/dist/run-CCsdLz3X.js +385 -0
- package/dist/run-CCsdLz3X.js.map +1 -0
- package/dist/{run-YMSJnvYl.js → run-CbX6tpqB.js} +31 -23
- package/dist/run-CbX6tpqB.js.map +1 -0
- package/dist/run-CrM4jsix.cjs +80 -0
- package/dist/run-CrM4jsix.cjs.map +1 -0
- package/dist/run-CvFakE3B.cjs +185 -0
- package/dist/run-CvFakE3B.cjs.map +1 -0
- package/dist/run-D-nn9-Wq.js +319 -0
- package/dist/run-D-nn9-Wq.js.map +1 -0
- package/dist/{run-cBSr2F1q.cjs → run-D5Z6nfzK.cjs} +5 -6
- package/dist/{run-cBSr2F1q.cjs.map → run-D5Z6nfzK.cjs.map} +1 -1
- package/dist/{run-BUzdEUaE.cjs → run-DCqmf5W1.cjs} +10 -10
- package/dist/{run-BUzdEUaE.cjs.map → run-DCqmf5W1.cjs.map} +1 -1
- package/dist/{run-BK-A9JDf.js → run-DFXhBS64.js} +5 -6
- package/dist/{run-BK-A9JDf.js.map → run-DFXhBS64.js.map} +1 -1
- package/dist/run-DgGZOC_x.cjs +389 -0
- package/dist/run-DgGZOC_x.cjs.map +1 -0
- package/dist/{run-DDwdeHgU.js → run-KqdudiWQ.js} +26 -20
- package/dist/run-KqdudiWQ.js.map +1 -0
- package/dist/run-RN_31Q4u.js +228 -0
- package/dist/run-RN_31Q4u.js.map +1 -0
- package/dist/shared-C0EtrdUm.cjs +123 -0
- package/dist/shared-C0EtrdUm.cjs.map +1 -0
- package/dist/shared-Ct9stKpp.js +94 -0
- package/dist/shared-Ct9stKpp.js.map +1 -0
- package/dist/{snapshot-BrwynBfC.cjs → snapshot-D7Ytw8S0.cjs} +13 -14
- package/dist/{snapshot-BrwynBfC.cjs.map → snapshot-D7Ytw8S0.cjs.map} +1 -1
- package/dist/{snapshot-D2E7nhEf.js → snapshot-n7dJGCWv.js} +10 -11
- package/dist/{snapshot-D2E7nhEf.js.map → snapshot-n7dJGCWv.js.map} +1 -1
- package/dist/spinner-BZaln7O8.js +160 -0
- package/dist/spinner-BZaln7O8.js.map +1 -0
- package/dist/spinner-CfB0sIPy.cjs +208 -0
- package/dist/spinner-CfB0sIPy.cjs.map +1 -0
- package/dist/{status-CxzhvjKs.cjs → status-Bc0_4T41.cjs} +7 -8
- package/dist/{status-CxzhvjKs.cjs.map → status-Bc0_4T41.cjs.map} +1 -1
- package/dist/{status-BHIOMCM0.js → status-DxSkp1cH.js} +6 -7
- package/dist/{status-BHIOMCM0.js.map → status-DxSkp1cH.js.map} +1 -1
- package/dist/studio-BpYwgFb0.cjs +218 -0
- package/dist/studio-BpYwgFb0.cjs.map +1 -0
- package/dist/studio-mw6HaPkr.js +215 -0
- package/dist/studio-mw6HaPkr.js.map +1 -0
- package/dist/studioWorker.js +4 -5
- package/dist/{summary-B219y888.js → summary-CbmQKA8l.js} +5 -6
- package/dist/{summary-B219y888.js.map → summary-CbmQKA8l.js.map} +1 -1
- package/dist/{summary-BOYsqub3.cjs → summary-Clvl5Hxs.cjs} +7 -8
- package/dist/{summary-BOYsqub3.cjs.map → summary-Clvl5Hxs.cjs.map} +1 -1
- package/dist/table-BlRe9YTZ.cjs +15 -0
- package/dist/table-BlRe9YTZ.cjs.map +1 -0
- package/dist/table-CR7iZJGo.js +10 -0
- package/dist/table-CR7iZJGo.js.map +1 -0
- package/dist/text-BmiI4s6N.js +13 -0
- package/dist/text-BmiI4s6N.js.map +1 -0
- package/dist/text-Ep3AulAS.cjs +24 -0
- package/dist/text-Ep3AulAS.cjs.map +1 -0
- package/dist/time-3BKoci3G.cjs +82 -0
- package/dist/time-3BKoci3G.cjs.map +1 -0
- package/dist/time-C-wU7VhC.js +65 -0
- package/dist/time-C-wU7VhC.js.map +1 -0
- package/dist/utils-BdibacYq.js +244 -0
- package/dist/utils-BdibacYq.js.map +1 -0
- package/dist/utils-CO89mxRU.cjs +290 -0
- package/dist/utils-CO89mxRU.cjs.map +1 -0
- package/dist/utils-CbJ6hS_t.js +222 -0
- package/dist/utils-CbJ6hS_t.js.map +1 -0
- package/dist/utils-CcARPB45.cjs +286 -0
- package/dist/utils-CcARPB45.cjs.map +1 -0
- package/dist/validate-8ZcWXfMt.js +28 -0
- package/dist/validate-8ZcWXfMt.js.map +1 -0
- package/dist/validate-CMSeDpGT.cjs +27 -0
- package/dist/validate-CMSeDpGT.cjs.map +1 -0
- package/package.json +7 -7
- package/dist/Telemetry-Dc79_cIA.js.map +0 -1
- package/dist/Telemetry-Dc9DVxKQ.cjs.map +0 -1
- package/dist/config-BG_Nr2CZ.cjs.map +0 -1
- package/dist/config-CNqgDSu3.js.map +0 -1
- package/dist/config-DWDpFSn1.js +0 -1
- package/dist/registry-BInbtBm7.cjs.map +0 -1
- package/dist/registry-ov1RYcD4.js.map +0 -1
- package/dist/run-1oXPIkli.cjs.map +0 -1
- package/dist/run-B03V0Poj.cjs.map +0 -1
- package/dist/run-DDwdeHgU.js.map +0 -1
- package/dist/run-YMSJnvYl.js.map +0 -1
- package/dist/run-v5aW1jjQ.js +0 -1
- package/dist/text-CC2xdpGL.cjs.map +0 -1
- package/dist/text-CEyJhBnh.js.map +0 -1
- package/dist/utils-8E1nF4C1.cjs +0 -645
- package/dist/utils-8E1nF4C1.cjs.map +0 -1
- package/dist/utils-D8VN40bN.js +0 -536
- package/dist/utils-D8VN40bN.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,151 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
|
11
|
-
import { cli,
|
|
12
|
-
import
|
|
13
|
-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
759
|
-
|
|
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
|
|
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
|
|
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
|
|
2845
|
-
|
|
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
|