@kubb/cli 5.14.6 → 5.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kubb.js +2 -1
- package/dist/{Telemetry-B5V_ufmv.cjs → Telemetry-B52pKz9i.cjs} +14 -118
- package/dist/Telemetry-B52pKz9i.cjs.map +1 -0
- package/dist/{Telemetry-DrnGartf.js → Telemetry-C7Qwy2s7.js} +12 -86
- package/dist/Telemetry-C7Qwy2s7.js.map +1 -0
- package/dist/{background-DlK0by5c.js → background-BJCuXYuB.js} +5 -6
- package/dist/{background-DlK0by5c.js.map → background-BJCuXYuB.js.map} +1 -1
- package/dist/{background-CdPT1eaq.cjs → background-CK2gQtTU.cjs} +5 -6
- package/dist/{background-CdPT1eaq.cjs.map → background-CK2gQtTU.cjs.map} +1 -1
- package/dist/{background-C6XHpEo7.js → background-D4mZCg-4.js} +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-CL6N6yPS.js → intro-9PB8sE8z.js} +8 -52
- package/dist/intro-9PB8sE8z.js.map +1 -0
- package/dist/{text-DrsG1EDN.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-CcseJJwh.cjs +20 -0
- package/dist/mcp-CcseJJwh.cjs.map +1 -0
- package/dist/mcp-DUTsS8tR.js +21 -0
- package/dist/mcp-DUTsS8tR.js.map +1 -0
- package/dist/package-B1hUUjio.cjs +10 -0
- package/dist/package-B1hUUjio.cjs.map +1 -0
- package/dist/package-MYPMnnTX.js +5 -0
- package/dist/package-MYPMnnTX.js.map +1 -0
- package/dist/{registry-DOGpflct.js → registry-B3eYk4LN.js} +7 -67
- package/dist/registry-B3eYk4LN.js.map +1 -0
- package/dist/{registry-BzBp6aZI.cjs → registry-YE1pFWL-.cjs} +4 -82
- package/dist/registry-YE1pFWL-.cjs.map +1 -0
- package/dist/{reporters-GzXlc4CK.cjs → reporters-2cgjvXwR.cjs} +32 -40
- package/dist/{reporters-GzXlc4CK.cjs.map → reporters-2cgjvXwR.cjs.map} +1 -1
- package/dist/{reporters-mYHIUZSx.js → reporters-BM430HdQ.js} +19 -27
- package/dist/{reporters-mYHIUZSx.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-BYP_t9dR.cjs +324 -0
- package/dist/run-BYP_t9dR.cjs.map +1 -0
- package/dist/run-Bq6gcfkS.js +1 -0
- package/dist/run-BrM7-NSV.js +181 -0
- package/dist/run-BrM7-NSV.js.map +1 -0
- package/dist/run-BsrNfFIl.js +228 -0
- package/dist/run-BsrNfFIl.js.map +1 -0
- package/dist/{run-l8IiOR_c.js → run-CLiHI99L.js} +9 -9
- package/dist/{run-l8IiOR_c.js.map → run-CLiHI99L.js.map} +1 -1
- package/dist/{run-C8Prej7s.cjs → run-CQORv7Gq.cjs} +44 -37
- package/dist/run-CQORv7Gq.cjs.map +1 -0
- package/dist/{run-EJtgdIin.js → run-CXVhxFfC.js} +31 -23
- package/dist/run-CXVhxFfC.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-DC_xH5H3.cjs → run-CyzKy-0p.cjs} +49 -41
- package/dist/run-CyzKy-0p.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-DBnSbwtU.cjs +389 -0
- package/dist/run-DBnSbwtU.cjs.map +1 -0
- package/dist/{run-j1g5si5R.js → run-DTqMrPS9.js} +5 -6
- package/dist/{run-j1g5si5R.js.map → run-DTqMrPS9.js.map} +1 -1
- package/dist/{run-9fs2T2Je.js → run-DUwX6Icx.js} +26 -20
- package/dist/run-DUwX6Icx.js.map +1 -0
- package/dist/run-FEiyv6_U.js +385 -0
- package/dist/run-FEiyv6_U.js.map +1 -0
- package/dist/run-LY_UAYyM.cjs +233 -0
- package/dist/run-LY_UAYyM.cjs.map +1 -0
- package/dist/{run-sjzj0ir-.cjs → run-dMTJfs4_.cjs} +10 -10
- package/dist/{run-sjzj0ir-.cjs.map → run-dMTJfs4_.cjs.map} +1 -1
- package/dist/{run-CypFtkVE.cjs → run-uPA5s3rI.cjs} +5 -6
- package/dist/{run-CypFtkVE.cjs.map → run-uPA5s3rI.cjs.map} +1 -1
- 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-laSRD0z1.cjs → snapshot-CPF9zyZj.cjs} +13 -14
- package/dist/{snapshot-laSRD0z1.cjs.map → snapshot-CPF9zyZj.cjs.map} +1 -1
- package/dist/{snapshot-BJWnL6hE.js → snapshot-CiV08NJr.js} +10 -11
- package/dist/{snapshot-BJWnL6hE.js.map → snapshot-CiV08NJr.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-DFEdbBPu.cjs → status-C066YA-4.cjs} +7 -8
- package/dist/{status-DFEdbBPu.cjs.map → status-C066YA-4.cjs.map} +1 -1
- package/dist/{status-BjmXLRm2.js → status-Dj8tivag.js} +6 -7
- package/dist/{status-BjmXLRm2.js.map → status-Dj8tivag.js.map} +1 -1
- package/dist/studio-B4XsXgKj.js +215 -0
- package/dist/studio-B4XsXgKj.js.map +1 -0
- package/dist/studio-D9_-mKee.cjs +218 -0
- package/dist/studio-D9_-mKee.cjs.map +1 -0
- package/dist/studioWorker.js +4 -5
- package/dist/{summary-DDnI70tn.js → summary-CbmQKA8l.js} +5 -6
- package/dist/{summary-DDnI70tn.js.map → summary-CbmQKA8l.js.map} +1 -1
- package/dist/{summary-1DCwzWEU.cjs → summary-Clvl5Hxs.cjs} +7 -8
- package/dist/{summary-1DCwzWEU.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-B3tLWYJU.cjs +27 -0
- package/dist/validate-B3tLWYJU.cjs.map +1 -0
- package/dist/validate-C5fyNF4v.js +28 -0
- package/dist/validate-C5fyNF4v.js.map +1 -0
- package/package.json +7 -7
- package/dist/Telemetry-B5V_ufmv.cjs.map +0 -1
- package/dist/Telemetry-DrnGartf.js.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-BzBp6aZI.cjs.map +0 -1
- package/dist/registry-DOGpflct.js.map +0 -1
- package/dist/run-9fs2T2Je.js.map +0 -1
- package/dist/run-C8Prej7s.cjs.map +0 -1
- package/dist/run-CXVme-aS.js +0 -1
- package/dist/run-DC_xH5H3.cjs.map +0 -1
- package/dist/run-EJtgdIin.js.map +0 -1
- package/dist/text-CL6N6yPS.js.map +0 -1
- package/dist/text-DrsG1EDN.cjs.map +0 -1
- package/dist/utils-C3vTpUje.cjs +0 -645
- package/dist/utils-C3vTpUje.cjs.map +0 -1
- package/dist/utils-CIc8YFtr.js +0 -536
- package/dist/utils-CIc8YFtr.js.map +0 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import "./rolldown-runtime-xo6NMXYd.js";
|
|
2
|
+
import { a as matchesStored, o as read } from "./spinner-BZaln7O8.js";
|
|
3
|
+
import { styleText } from "node:util";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { readdir } from "node:fs/promises";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
// ../../internals/utils/src/promise.ts
|
|
8
|
+
/**
|
|
9
|
+
* Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each
|
|
10
|
+
* takes the next item the moment it frees up instead of waiting for a batch to drain.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
async function inParallel({ items, limit, run }) {
|
|
18
|
+
const queue = items.entries();
|
|
19
|
+
const worker = async () => {
|
|
20
|
+
for (const [index, item] of queue) await run(item, index);
|
|
21
|
+
};
|
|
22
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
23
|
+
}
|
|
24
|
+
// src/runners/generate/utils.ts
|
|
25
|
+
/** Glob pattern for paths the file watcher ignores. */
|
|
26
|
+
const WATCHER_IGNORED_PATHS = "**/{.git,node_modules}/**";
|
|
27
|
+
/**
|
|
28
|
+
* Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.
|
|
29
|
+
* It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.
|
|
30
|
+
*/
|
|
31
|
+
const WATCHER_DEBOUNCE_MS = 250;
|
|
32
|
+
/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */
|
|
33
|
+
function formatChange({ target, removed = false, now = /* @__PURE__ */ new Date() }) {
|
|
34
|
+
return `${styleText("dim", now.toTimeString().slice(0, 8))} ${styleText("bold", target)} ${removed ? "removed" : "changed"}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Starts a file watcher on the given paths and calls `cb` on any change.
|
|
38
|
+
* Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)
|
|
39
|
+
* are debounced into one call. `cb` must never reject: its result is not awaited, so the caller
|
|
40
|
+
* serializes and reports its own builds. Resolves to a function that stops watching.
|
|
41
|
+
*/
|
|
42
|
+
async function startWatcher(path, cb, log = {
|
|
43
|
+
info: console.log,
|
|
44
|
+
error: console.log
|
|
45
|
+
}) {
|
|
46
|
+
const { watch } = await import("chokidar");
|
|
47
|
+
const watcher = watch(path, {
|
|
48
|
+
ignorePermissionErrors: true,
|
|
49
|
+
ignored: WATCHER_IGNORED_PATHS,
|
|
50
|
+
ignoreInitial: true
|
|
51
|
+
});
|
|
52
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => void watcher.close());
|
|
53
|
+
let debounceTimer = null;
|
|
54
|
+
watcher.on("all", (type, file) => {
|
|
55
|
+
log.info(formatChange({
|
|
56
|
+
target: relative(process.cwd(), file),
|
|
57
|
+
removed: type === "unlink"
|
|
58
|
+
}));
|
|
59
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
60
|
+
debounceTimer = setTimeout(() => {
|
|
61
|
+
debounceTimer = null;
|
|
62
|
+
cb(path);
|
|
63
|
+
}, WATCHER_DEBOUNCE_MS);
|
|
64
|
+
});
|
|
65
|
+
return async () => {
|
|
66
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
67
|
+
await watcher.close();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function createSnapshot() {
|
|
71
|
+
return {
|
|
72
|
+
schemas: /* @__PURE__ */ new Set(),
|
|
73
|
+
operations: /* @__PURE__ */ new Set()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished
|
|
78
|
+
* build's snapshot and starts the next one.
|
|
79
|
+
*/
|
|
80
|
+
function trackBuild(hooks) {
|
|
81
|
+
let current = createSnapshot();
|
|
82
|
+
hooks.hook("kubb:generate:schema", (node) => {
|
|
83
|
+
if (node.name) current.schemas.add(node.name);
|
|
84
|
+
});
|
|
85
|
+
hooks.hook("kubb:generate:operation", (node) => {
|
|
86
|
+
current.operations.add(node.operationId);
|
|
87
|
+
});
|
|
88
|
+
return { take() {
|
|
89
|
+
const snapshot = current;
|
|
90
|
+
current = createSnapshot();
|
|
91
|
+
return snapshot;
|
|
92
|
+
} };
|
|
93
|
+
}
|
|
94
|
+
function diffNames(previous, next) {
|
|
95
|
+
return {
|
|
96
|
+
added: [...next].filter((name) => !previous.has(name)).length,
|
|
97
|
+
removed: [...previous].filter((name) => !next.has(name)).length
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compares two {@link BuildSnapshot}s by name.
|
|
102
|
+
*/
|
|
103
|
+
function diffBuilds(previous, next) {
|
|
104
|
+
return {
|
|
105
|
+
schemas: diffNames(previous.schemas, next.schemas),
|
|
106
|
+
operations: diffNames(previous.operations, next.operations)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compares file contents or fingerprints by path.
|
|
111
|
+
*/
|
|
112
|
+
function diffFiles(previous, next) {
|
|
113
|
+
const files = [];
|
|
114
|
+
let unchanged = 0;
|
|
115
|
+
for (const [path, fingerprint] of next) {
|
|
116
|
+
const before = previous.get(path);
|
|
117
|
+
if (before === fingerprint) unchanged++;
|
|
118
|
+
else files.push({
|
|
119
|
+
status: before === void 0 ? "A" : "M",
|
|
120
|
+
path
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const path of previous.keys()) if (!next.has(path)) files.push({
|
|
124
|
+
status: "D",
|
|
125
|
+
path
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
files,
|
|
129
|
+
unchanged
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function countLineChanges(before, after) {
|
|
133
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const line of before ? before.split("\n") : []) remaining.set(line, (remaining.get(line) ?? 0) + 1);
|
|
135
|
+
let added = 0;
|
|
136
|
+
for (const line of after ? after.split("\n") : []) {
|
|
137
|
+
const count = remaining.get(line) ?? 0;
|
|
138
|
+
if (count > 0) remaining.set(line, count - 1);
|
|
139
|
+
else added++;
|
|
140
|
+
}
|
|
141
|
+
const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0);
|
|
142
|
+
return {
|
|
143
|
+
added,
|
|
144
|
+
removed
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Reads the output before generation, so cleaning the directory cannot erase the diff baseline.
|
|
149
|
+
* A missing directory or an unreadable file is skipped.
|
|
150
|
+
*/
|
|
151
|
+
async function readOutputFiles(outputPath) {
|
|
152
|
+
const entries = await readdir(outputPath, {
|
|
153
|
+
recursive: true,
|
|
154
|
+
withFileTypes: true
|
|
155
|
+
}).catch(() => []);
|
|
156
|
+
const files = /* @__PURE__ */ new Map();
|
|
157
|
+
await inParallel({
|
|
158
|
+
items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),
|
|
159
|
+
limit: 50,
|
|
160
|
+
run: async (filePath) => {
|
|
161
|
+
const source = await read(filePath).catch(() => null);
|
|
162
|
+
if (source !== null) files.set(filePath, source);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return files;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Compares generated output with the files captured before generation. The manifest recognizes
|
|
169
|
+
* output rewritten by a formatter, and `clean` includes files the run would delete.
|
|
170
|
+
*/
|
|
171
|
+
async function diffOutput({ storage, paths, clean, manifest, previousFiles }) {
|
|
172
|
+
const generated = new Set(paths);
|
|
173
|
+
const changes = [];
|
|
174
|
+
let unchanged = 0;
|
|
175
|
+
await inParallel({
|
|
176
|
+
items: paths,
|
|
177
|
+
limit: 50,
|
|
178
|
+
run: async (filePath, index) => {
|
|
179
|
+
const next = await storage.readItem(filePath);
|
|
180
|
+
if (next === null) return;
|
|
181
|
+
const previous = previousFiles.get(filePath) ?? null;
|
|
182
|
+
if (previous !== null && (matchesStored({
|
|
183
|
+
stored: previous,
|
|
184
|
+
source: next
|
|
185
|
+
}) || manifest?.isUpToDate({
|
|
186
|
+
key: filePath,
|
|
187
|
+
source: next,
|
|
188
|
+
disk: previous
|
|
189
|
+
}))) {
|
|
190
|
+
unchanged++;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
changes[index] = {
|
|
194
|
+
status: previous === null ? "A" : "M",
|
|
195
|
+
path: filePath,
|
|
196
|
+
...countLineChanges(previous?.trim() ?? "", previous === null ? next.trimEnd() : next.trim())
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
const files = changes.filter((file) => file !== void 0);
|
|
201
|
+
if (clean) {
|
|
202
|
+
for (const [filePath, source] of previousFiles) if (!generated.has(filePath)) files.push({
|
|
203
|
+
status: "D",
|
|
204
|
+
path: filePath,
|
|
205
|
+
...countLineChanges(source.trimEnd(), "")
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
files,
|
|
210
|
+
unchanged
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c
|
|
215
|
+
* working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw
|
|
216
|
+
* mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode
|
|
217
|
+
* is restored on exit.
|
|
218
|
+
*/
|
|
219
|
+
function listenForKeys({ rebuild, quit, input = process.stdin }) {
|
|
220
|
+
if (!input.isTTY) return void 0;
|
|
221
|
+
const arm = () => {
|
|
222
|
+
input.setRawMode(true);
|
|
223
|
+
input.resume();
|
|
224
|
+
};
|
|
225
|
+
input.setEncoding("utf8");
|
|
226
|
+
input.on("data", (data) => {
|
|
227
|
+
const key = data.trim();
|
|
228
|
+
if (key === "r") {
|
|
229
|
+
rebuild();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key === "q" || data === "") {
|
|
233
|
+
input.setRawMode(false);
|
|
234
|
+
input.pause();
|
|
235
|
+
quit(key === "q" ? 0 : 130);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
process.once("exit", () => input.setRawMode(false));
|
|
239
|
+
arm();
|
|
240
|
+
return arm;
|
|
241
|
+
}
|
|
242
|
+
export { readOutputFiles as a, listenForKeys as i, diffFiles as n, startWatcher as o, diffOutput as r, trackBuild as s, diffBuilds as t };
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=utils-BdibacYq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-BdibacYq.js","names":[],"sources":["../../../internals/utils/src/promise.ts","../src/runners/generate/utils.ts"],"sourcesContent":["/** A value that may already be resolved or still pending.\n *\n * @example\n * ```ts\n * function load(id: string): PossiblePromise<string> {\n * return cache.get(id) ?? fetchRemote(id)\n * }\n * ```\n */\nexport type PossiblePromise<T> = Promise<T> | T\n\ntype Store<TKey, TValue> = {\n has(key: TKey): boolean\n get(key: TKey): TValue | undefined\n set(key: TKey, value: TValue): unknown\n}\n\n/**\n * Wraps `factory` with a keyed cache backed by the provided store.\n *\n * Pass a `WeakMap` for object keys (results are GC-eligible when the key is\n * collected) or a `Map` for primitive keys. For multi-argument functions,\n * nest two `memoize` calls — the outer keyed by the first argument, the\n * inner (created once per outer miss) keyed by the second.\n *\n * Because the cache is owned by the caller, it can be shared, inspected, or\n * cleared independently of the memoized function.\n *\n * @example Single WeakMap key\n * ```ts\n * const cache = new WeakMap<SchemaNode, Set<string>>()\n * const getRefs = memoize(cache, (node) => collectRefs(node))\n * ```\n *\n * @example Single Map key (primitive)\n * ```ts\n * const cache = new Map<string, Resolver>()\n * const getResolver = memoize(cache, (name) => buildResolver(name))\n * ```\n *\n * @example Two-level (object + primitive)\n * ```ts\n * const outer = new WeakMap<Params[], Map<string, Params[]>>()\n * const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))\n * fn(params)('camelcase')\n * ```\n */\nexport function memoize<TKey, TValue>(store: Store<TKey, TValue>, factory: (key: TKey) => TValue): (key: TKey) => TValue {\n return (key: TKey): TValue => {\n if (store.has(key)) return store.get(key)!\n const value = factory(key)\n store.set(key, value)\n return value\n }\n}\n\ntype ParallelOptions<TItem> = {\n /**\n * Items to work through, handed out in order.\n */\n items: ReadonlyArray<TItem>\n /**\n * How many items may be in flight at once.\n */\n limit: number\n /**\n * Runs once per item, with the item's position so a caller can report progress.\n */\n run(item: TItem, index: number): Promise<void>\n}\n\n/**\n * Runs `run` over every item with at most `limit` in flight. Workers share one iterator, so each\n * takes the next item the moment it frees up instead of waiting for a batch to drain.\n *\n * @example\n * ```ts\n * await inParallel({ items: files, limit: 50, run: (file) => storage.writeItem(file.path, file.source) })\n * ```\n */\nexport async function inParallel<TItem>({ items, limit, run }: ParallelOptions<TItem>): Promise<void> {\n const queue = items.entries()\n\n const worker = async (): Promise<void> => {\n for (const [index, item] of queue) await run(item, index)\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()))\n}\n","import { readdir } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport { inParallel, matchesStored, read } from '@internals/utils'\nimport type { Hookable, KubbHooks, OutputManifest, Storage } from '@kubb/core'\n\n/** Glob pattern for paths the file watcher ignores. */\nconst WATCHER_IGNORED_PATHS = '**/{.git,node_modules}/**' as const\n\n/**\n * Quiet window in milliseconds that collapses a burst of watcher events (an editor save emits several) into one rebuild.\n * It outlasts chokidar's 100 ms atomic-save window, which holds back the event of a write-then-rename save.\n */\nconst WATCHER_DEBOUNCE_MS = 250\n\n/** `14:02:11 petStore.yaml changed`, the line a watcher prints when its input changes. */\nexport function formatChange({ target, removed = false, now = new Date() }: { target: string; removed?: boolean; now?: Date }): string {\n return `${styleText('dim', now.toTimeString().slice(0, 8))} ${styleText('bold', target)} ${removed ? 'removed' : 'changed'}`\n}\n\ntype WatcherLog = {\n info: (message: string) => void\n error: (message: string) => void\n}\n\n/**\n * Starts a file watcher on the given paths and calls `cb` on any change.\n * Ignores `.git` and `node_modules` directories. Event bursts (an editor save emits several)\n * are debounced into one call. `cb` must never reject: its result is not awaited, so the caller\n * serializes and reports its own builds. Resolves to a function that stops watching.\n */\nexport async function startWatcher(\n path: Array<string>,\n cb: (path: Array<string>) => Promise<void>,\n log: WatcherLog = { info: console.log, error: console.log },\n): Promise<() => Promise<void>> {\n const { watch } = await import('chokidar')\n // `ignoreInitial` skips the `add` events chokidar fires for existing files at startup, which\n // would otherwise rebuild right after the initial run.\n const watcher = watch(path, { ignorePermissionErrors: true, ignored: WATCHER_IGNORED_PATHS, ignoreInitial: true })\n\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => void watcher.close())\n }\n\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n watcher.on('all', (type, file) => {\n log.info(formatChange({ target: relative(process.cwd(), file), removed: type === 'unlink' }))\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounceTimer = null\n void cb(path)\n }, WATCHER_DEBOUNCE_MS)\n })\n\n return async () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n await watcher.close()\n }\n}\n\n/**\n * What one build produced: the schema and operation names the plugins generated.\n */\nexport type BuildSnapshot = {\n schemas: Set<string>\n operations: Set<string>\n}\n\nfunction createSnapshot(): BuildSnapshot {\n return { schemas: new Set(), operations: new Set() }\n}\n\n/**\n * Collects a {@link BuildSnapshot} from the hooks while a build runs. `take` hands back the finished\n * build's snapshot and starts the next one.\n */\nexport function trackBuild(hooks: Hookable<KubbHooks>): { take(): BuildSnapshot } {\n let current = createSnapshot()\n\n hooks.hook('kubb:generate:schema', (node) => {\n if (node.name) current.schemas.add(node.name)\n })\n hooks.hook('kubb:generate:operation', (node) => {\n current.operations.add(node.operationId)\n })\n\n return {\n take() {\n const snapshot = current\n current = createSnapshot()\n return snapshot\n },\n }\n}\n\ntype NameChange = { added: number; removed: number }\n\n/**\n * How a rebuild differs from the build before it: the spec's added and removed schemas and operations.\n */\nexport type BuildDiff = {\n schemas: NameChange\n operations: NameChange\n}\n\nfunction diffNames(previous: Set<string>, next: Set<string>): NameChange {\n return { added: [...next].filter((name) => !previous.has(name)).length, removed: [...previous].filter((name) => !next.has(name)).length }\n}\n\n/**\n * Compares two {@link BuildSnapshot}s by name.\n */\nexport function diffBuilds(previous: BuildSnapshot, next: BuildSnapshot): BuildDiff {\n return {\n schemas: diffNames(previous.schemas, next.schemas),\n operations: diffNames(previous.operations, next.operations),\n }\n}\n\n/**\n * Compares file contents or fingerprints by path.\n */\nexport function diffFiles(previous: ReadonlyMap<string, string>, next: ReadonlyMap<string, string>): { files: Array<FileChange>; unchanged: number } {\n const files: Array<FileChange> = []\n let unchanged = 0\n for (const [path, fingerprint] of next) {\n const before = previous.get(path)\n if (before === fingerprint) unchanged++\n else files.push({ status: before === undefined ? 'A' : 'M', path })\n }\n for (const path of previous.keys()) {\n if (!next.has(path)) files.push({ status: 'D', path })\n }\n\n return { files, unchanged }\n}\n\n/** A changed file with how many lines it gains and loses, counted as a multiset, which is cheap and close enough for a plan. */\nexport type FileChange = { status: 'A' | 'M' | 'D'; path: string; added?: number; removed?: number }\n\nfunction countLineChanges(before: string, after: string): { added: number; removed: number } {\n const remaining = new Map<string, number>()\n for (const line of before ? before.split('\\n') : []) remaining.set(line, (remaining.get(line) ?? 0) + 1)\n let added = 0\n for (const line of after ? after.split('\\n') : []) {\n const count = remaining.get(line) ?? 0\n if (count > 0) remaining.set(line, count - 1)\n else added++\n }\n const removed = [...remaining.values()].reduce((sum, count) => sum + count, 0)\n return { added, removed }\n}\n\n/**\n * Reads the output before generation, so cleaning the directory cannot erase the diff baseline.\n * A missing directory or an unreadable file is skipped.\n */\nexport async function readOutputFiles(outputPath: string): Promise<Map<string, string>> {\n const entries = await readdir(outputPath, { recursive: true, withFileTypes: true }).catch(() => [])\n const files = new Map<string, string>()\n await inParallel({\n items: entries.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)),\n limit: 50,\n run: async (filePath) => {\n const source = await read(filePath).catch(() => null)\n if (source !== null) files.set(filePath, source)\n },\n })\n return files\n}\n\n/**\n * Compares generated output with the files captured before generation. The manifest recognizes\n * output rewritten by a formatter, and `clean` includes files the run would delete.\n */\nexport async function diffOutput({\n storage,\n paths,\n clean,\n manifest,\n previousFiles,\n}: {\n storage: Storage\n paths: ReadonlyArray<string>\n clean: boolean\n manifest?: Pick<OutputManifest, 'isUpToDate'>\n /**\n * The output captured before a real run, including files removed by cleaning.\n */\n previousFiles: ReadonlyMap<string, string>\n}): Promise<{ files: Array<FileChange>; unchanged: number }> {\n const generated = new Set(paths)\n const changes: Array<FileChange | undefined> = []\n let unchanged = 0\n await inParallel({\n items: paths,\n limit: 50,\n run: async (filePath, index) => {\n const next = await storage.readItem(filePath)\n if (next === null) return\n const previous = previousFiles.get(filePath) ?? null\n if (previous !== null && (matchesStored({ stored: previous, source: next }) || manifest?.isUpToDate({ key: filePath, source: next, disk: previous }))) {\n unchanged++\n return\n }\n changes[index] = {\n status: previous === null ? 'A' : 'M',\n path: filePath,\n ...countLineChanges(previous?.trim() ?? '', previous === null ? next.trimEnd() : next.trim()),\n }\n },\n })\n const files = changes.filter((file) => file !== undefined)\n if (clean) {\n for (const [filePath, source] of previousFiles) {\n if (!generated.has(filePath)) files.push({ status: 'D', path: filePath, ...countLineChanges(source.trimEnd(), '') })\n }\n }\n\n return { files, unchanged }\n}\n\ntype KeyInput = Pick<NodeJS.ReadStream, 'isTTY' | 'setRawMode' | 'resume' | 'pause' | 'setEncoding' | 'on'>\n\ntype KeyOptions = {\n rebuild: () => void\n /**\n * Ends the process with this exit code: `0` for `q`, `130` for ctrl+c, which raw mode turns into a key.\n */\n quit: (code: number) => void\n input?: KeyInput\n}\n\n/**\n * Reads `r` (rebuild) and `q` (quit) from an interactive stdin in raw mode, and keeps ctrl+c\n * working. Returns `undefined` when stdin is not a TTY. Otherwise returns `arm`, which turns raw\n * mode back on: a spinner restores the terminal when it stops, so call it after each build. Raw mode\n * is restored on exit.\n */\nexport function listenForKeys({ rebuild, quit, input = process.stdin }: KeyOptions): (() => void) | undefined {\n if (!input.isTTY) return undefined\n\n const arm = () => {\n input.setRawMode(true)\n input.resume()\n }\n input.setEncoding('utf8')\n input.on('data', (data: string) => {\n const key = data.trim()\n if (key === 'r') {\n rebuild()\n return\n }\n if (key === 'q' || data === '\\u0003') {\n input.setRawMode(false)\n input.pause()\n quit(key === 'q' ? 0 : 130)\n }\n })\n process.once('exit', () => input.setRawMode(false))\n arm()\n\n return arm\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-C3-Zg-KD.cjs");
|
|
2
|
+
const require_spinner = require("./spinner-CfB0sIPy.cjs");
|
|
3
|
+
let node_process = require("node:process");
|
|
4
|
+
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
5
|
+
let tinyexec = require("tinyexec");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
8
|
+
let node_fs = require("node:fs");
|
|
9
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
10
|
+
let _kubb_core = require("@kubb/core");
|
|
11
|
+
// src/runners/init/utils.ts
|
|
12
|
+
/** Metadata for each supported package manager, keyed by its short name. */
|
|
13
|
+
const packageManagers = {
|
|
14
|
+
pnpm: {
|
|
15
|
+
name: "pnpm",
|
|
16
|
+
lockFiles: ["pnpm-lock.yaml"],
|
|
17
|
+
initCommand: ["init"],
|
|
18
|
+
installCommand: ["add", "-D"],
|
|
19
|
+
removeCommand: ["remove"],
|
|
20
|
+
runCommand: []
|
|
21
|
+
},
|
|
22
|
+
yarn: {
|
|
23
|
+
name: "yarn",
|
|
24
|
+
lockFiles: ["yarn.lock"],
|
|
25
|
+
initCommand: ["init", "-y"],
|
|
26
|
+
installCommand: ["add", "-D"],
|
|
27
|
+
removeCommand: ["remove"],
|
|
28
|
+
runCommand: []
|
|
29
|
+
},
|
|
30
|
+
bun: {
|
|
31
|
+
name: "bun",
|
|
32
|
+
lockFiles: ["bun.lock", "bun.lockb"],
|
|
33
|
+
initCommand: ["init", "-y"],
|
|
34
|
+
installCommand: ["add", "-d"],
|
|
35
|
+
removeCommand: ["remove"],
|
|
36
|
+
runCommand: ["run"]
|
|
37
|
+
},
|
|
38
|
+
npm: {
|
|
39
|
+
name: "npm",
|
|
40
|
+
lockFiles: ["package-lock.json"],
|
|
41
|
+
initCommand: ["init", "-y"],
|
|
42
|
+
installCommand: ["install", "--save-dev"],
|
|
43
|
+
removeCommand: ["uninstall"],
|
|
44
|
+
runCommand: ["run"]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** Detects the package manager for `cwd`: the `packageManager` field in `package.json`, then a lock file, then `npm`. */
|
|
48
|
+
function detectPackageManager(cwd = node_process.default.cwd()) {
|
|
49
|
+
const packageJsonPath = node_path.default.join(cwd, "package.json");
|
|
50
|
+
if (node_fs.default.existsSync(packageJsonPath)) try {
|
|
51
|
+
const pmField = JSON.parse(node_fs.default.readFileSync(packageJsonPath, "utf-8")).packageManager;
|
|
52
|
+
if (typeof pmField === "string") {
|
|
53
|
+
const name = pmField.split("@")[0];
|
|
54
|
+
if (name && name in packageManagers) return packageManagers[name];
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
for (const pm of Object.values(packageManagers)) if (pm.lockFiles.some((lockFile) => node_fs.default.existsSync(node_path.default.join(cwd, lockFile)))) return pm;
|
|
58
|
+
return packageManagers.npm;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Initializes a new `package.json` at `cwd` using the detected package manager.
|
|
62
|
+
*/
|
|
63
|
+
async function initPackageJson(cwd, packageManager) {
|
|
64
|
+
await (0, tinyexec.x)(packageManager.name, [...packageManager.initCommand], {
|
|
65
|
+
nodeOptions: {
|
|
66
|
+
cwd,
|
|
67
|
+
stdio: "inherit"
|
|
68
|
+
},
|
|
69
|
+
throwOnError: true
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const INSTALL_TIMEOUT_MS = 3e5;
|
|
73
|
+
/**
|
|
74
|
+
* Installs the given packages at `cwd` using the detected package manager. Each output line goes
|
|
75
|
+
* to `onLine`, so the caller can show it while the install runs; a failed install also keeps the
|
|
76
|
+
* end of the output in the error.
|
|
77
|
+
*/
|
|
78
|
+
async function installPackages(packages, packageManager, cwd = node_process.default.cwd(), onLine, command = packageManager.installCommand) {
|
|
79
|
+
const args = [...command, ...packages];
|
|
80
|
+
const result = (0, tinyexec.x)(packageManager.name, args, {
|
|
81
|
+
nodeOptions: {
|
|
82
|
+
cwd,
|
|
83
|
+
stdio: [
|
|
84
|
+
"ignore",
|
|
85
|
+
"pipe",
|
|
86
|
+
"pipe"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
90
|
+
throwOnError: false
|
|
91
|
+
});
|
|
92
|
+
const lines = [];
|
|
93
|
+
for await (const line of result) {
|
|
94
|
+
lines.push(line);
|
|
95
|
+
onLine?.(line);
|
|
96
|
+
}
|
|
97
|
+
if (result.exitCode === 0) return;
|
|
98
|
+
if (result.exitCode === void 0) throw new Error(`\`${packageManager.name} ${args.join(" ")}\` timed out after ${INSTALL_TIMEOUT_MS / 6e4} minutes`);
|
|
99
|
+
const output = lines.slice(-20).join("\n");
|
|
100
|
+
throw new Error(`\`${packageManager.name} ${args.join(" ")}\` failed with exit code ${result.exitCode}${output ? `:\n${output}` : ""}`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Uninstalls the given packages at `cwd` with the package manager's remove command, like {@link installPackages}.
|
|
104
|
+
*/
|
|
105
|
+
function uninstallPackages(packages, packageManager, cwd = node_process.default.cwd()) {
|
|
106
|
+
return installPackages(packages, packageManager, cwd, void 0, packageManager.removeCommand);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* What the selected plugins' `manifests` say is missing from the selection: a `dependencies`
|
|
110
|
+
* entry, or a `client` plugin next to a `consumer`. Empty when nothing is missing.
|
|
111
|
+
* `optionalDependencies` are skipped, since `kubb init` writes the default options.
|
|
112
|
+
*/
|
|
113
|
+
function missingPluginDependencies(manifests) {
|
|
114
|
+
const selected = manifests.map((manifest) => manifest.name);
|
|
115
|
+
const hasClient = manifests.some((manifest) => manifest.role === "client");
|
|
116
|
+
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.`] : []]);
|
|
117
|
+
}
|
|
118
|
+
async function readPackageJson(cwd) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(await require_spinner.read(node_path.default.join(cwd, "package.json")));
|
|
121
|
+
} catch {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const SPEC_FILE = /^(openapi|swagger)[\w.-]*\.(ya?ml|json)$/i;
|
|
126
|
+
const SKIPPED_DIRS = /* @__PURE__ */ new Set([
|
|
127
|
+
"node_modules",
|
|
128
|
+
"dist",
|
|
129
|
+
"build",
|
|
130
|
+
"coverage"
|
|
131
|
+
]);
|
|
132
|
+
/**
|
|
133
|
+
* Finds OpenAPI spec files up to `depth` folders below `cwd`, shallowest first, as `./`-relative
|
|
134
|
+
* paths. Walks one level at a time and stops after `limit` files, so a large repo stays fast.
|
|
135
|
+
*/
|
|
136
|
+
async function findSpecFiles(cwd, { depth = 3, limit = 20 } = {}) {
|
|
137
|
+
const found = [];
|
|
138
|
+
let level = [""];
|
|
139
|
+
for (let current = 0; current <= depth && level.length && found.length < limit; current++) {
|
|
140
|
+
const listings = await Promise.all(level.map(async (dir) => ({
|
|
141
|
+
dir,
|
|
142
|
+
entries: await node_fs.default.promises.readdir(node_path.default.join(cwd, dir), { withFileTypes: true }).catch(() => [])
|
|
143
|
+
})));
|
|
144
|
+
level = [];
|
|
145
|
+
for (const { dir, entries } of listings) for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
146
|
+
if (entry.isFile() && SPEC_FILE.test(entry.name)) found.push(`./${node_path.default.posix.join(dir, entry.name)}`);
|
|
147
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIPPED_DIRS.has(entry.name)) level.push(node_path.default.posix.join(dir, entry.name));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return found.slice(0, limit);
|
|
151
|
+
}
|
|
152
|
+
const SPEC_HEAD_BYTES = 262144;
|
|
153
|
+
const METHOD = "(?:get|put|post|delete|options|head|patch|trace)";
|
|
154
|
+
/**
|
|
155
|
+
* Reads the OpenAPI or Swagger version and counts the operations from the first 256 KB of the
|
|
156
|
+
* file, so a large spec is never parsed. The count is left out when the file is longer.
|
|
157
|
+
*/
|
|
158
|
+
async function readSpecInfo(file) {
|
|
159
|
+
const handle = await node_fs.default.promises.open(file);
|
|
160
|
+
try {
|
|
161
|
+
const buffer = Buffer.alloc(SPEC_HEAD_BYTES);
|
|
162
|
+
const { bytesRead } = await handle.read(buffer, 0, SPEC_HEAD_BYTES, 0);
|
|
163
|
+
const head = buffer.toString("utf-8", 0, bytesRead);
|
|
164
|
+
const complete = bytesRead < SPEC_HEAD_BYTES;
|
|
165
|
+
const version = head.match(/^\s*["']?(?:openapi|swagger)["']?\s*:\s*["']?(\d[\d.]*)/m)?.[1];
|
|
166
|
+
const methods = new RegExp(`^\\s*["']?${METHOD}["']?\\s*:\\s*(?:\\{|$)`, "gm");
|
|
167
|
+
return {
|
|
168
|
+
version,
|
|
169
|
+
operations: complete ? head.match(methods)?.length ?? 0 : void 0
|
|
170
|
+
};
|
|
171
|
+
} finally {
|
|
172
|
+
await handle.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Finds a plugin by registry id, slug, or short name, so `ts`, `plugin-ts`, and `kubb-labs/plugin-ts` all match. */
|
|
176
|
+
function findPlugin({ plugins, id }) {
|
|
177
|
+
const ids = id.includes("/") ? [id] : [`kubb-labs/${id}`, `kubb-labs/plugin-${id}`];
|
|
178
|
+
return plugins.find((entry) => ids.includes(entry.id));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Turns the comma-separated `--plugins` value into registry entries.
|
|
182
|
+
*
|
|
183
|
+
* @throws when an id is not in `plugins`.
|
|
184
|
+
*/
|
|
185
|
+
function parsePluginsFlag({ plugins, value }) {
|
|
186
|
+
const ids = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
187
|
+
const unknown = ids.filter((id) => !findPlugin({
|
|
188
|
+
plugins,
|
|
189
|
+
id
|
|
190
|
+
}));
|
|
191
|
+
if (unknown.length) throw new _kubb_core.Diagnostics.Error({
|
|
192
|
+
code: _kubb_core.Diagnostics.code.pluginNotFound,
|
|
193
|
+
severity: "error",
|
|
194
|
+
message: `Not in the Kubb registry: ${unknown.join(", ")}. Browse the plugins at https://kubb.dev/extensions.`,
|
|
195
|
+
help: "Use plugin ids from https://kubb.dev/extensions, such as plugin-ts or ts."
|
|
196
|
+
});
|
|
197
|
+
return ids.map((id) => findPlugin({
|
|
198
|
+
plugins,
|
|
199
|
+
id
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
/** Adds `"generate": "kubb generate"` to the `package.json` scripts at `cwd`. Returns `false`, and changes nothing, when a `generate` script exists. */
|
|
203
|
+
async function addGenerateScript(cwd) {
|
|
204
|
+
const file = node_path.default.join(cwd, "package.json");
|
|
205
|
+
const source = await require_spinner.read(file);
|
|
206
|
+
const packageJson = JSON.parse(source);
|
|
207
|
+
if (packageJson.scripts?.generate !== void 0) return false;
|
|
208
|
+
const indent = source.match(/^[ \t]+/m)?.[0] ?? " ";
|
|
209
|
+
const updated = {
|
|
210
|
+
...packageJson,
|
|
211
|
+
scripts: {
|
|
212
|
+
...packageJson.scripts,
|
|
213
|
+
generate: "kubb generate"
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
await require_spinner.write(file, JSON.stringify(updated, null, indent));
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
/** Reads the `generate` script from the `package.json` at `cwd`. */
|
|
220
|
+
async function generateScript(cwd) {
|
|
221
|
+
return (await readPackageJson(cwd)).scripts?.generate;
|
|
222
|
+
}
|
|
223
|
+
Object.defineProperty(exports, "addGenerateScript", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function() {
|
|
226
|
+
return addGenerateScript;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
Object.defineProperty(exports, "detectPackageManager", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get: function() {
|
|
232
|
+
return detectPackageManager;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
Object.defineProperty(exports, "findPlugin", {
|
|
236
|
+
enumerable: true,
|
|
237
|
+
get: function() {
|
|
238
|
+
return findPlugin;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
Object.defineProperty(exports, "findSpecFiles", {
|
|
242
|
+
enumerable: true,
|
|
243
|
+
get: function() {
|
|
244
|
+
return findSpecFiles;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(exports, "generateScript", {
|
|
248
|
+
enumerable: true,
|
|
249
|
+
get: function() {
|
|
250
|
+
return generateScript;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
Object.defineProperty(exports, "initPackageJson", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function() {
|
|
256
|
+
return initPackageJson;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
Object.defineProperty(exports, "installPackages", {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
get: function() {
|
|
262
|
+
return installPackages;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
Object.defineProperty(exports, "missingPluginDependencies", {
|
|
266
|
+
enumerable: true,
|
|
267
|
+
get: function() {
|
|
268
|
+
return missingPluginDependencies;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
Object.defineProperty(exports, "parsePluginsFlag", {
|
|
272
|
+
enumerable: true,
|
|
273
|
+
get: function() {
|
|
274
|
+
return parsePluginsFlag;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
Object.defineProperty(exports, "readSpecInfo", {
|
|
278
|
+
enumerable: true,
|
|
279
|
+
get: function() {
|
|
280
|
+
return readSpecInfo;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
Object.defineProperty(exports, "uninstallPackages", {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
get: function() {
|
|
286
|
+
return uninstallPackages;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
//# sourceMappingURL=utils-CO89mxRU.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-CO89mxRU.cjs","names":["process","path","fs","x","read","Diagnostics","write"],"sources":["../src/runners/init/utils.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { read, write } from '@internals/utils'\nimport { Diagnostics, type ExtensionManifest, type RegistryEntry } from '@kubb/core'\nimport { x } from 'tinyexec'\n\nexport type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun'\n\n/** Metadata describing a package manager's lock file and commands. */\nexport type PackageManagerInfo = {\n /** Identifier used in CLI commands, e.g. `pnpm`, `yarn`. */\n name: PackageManagerName\n /** Lock file names that identify this package manager in a project root. */\n lockFiles: ReadonlyArray<string>\n /** Subcommands passed to the package manager binary to create a `package.json`. */\n initCommand: ReadonlyArray<string>\n /** Subcommands passed to the package manager binary to install a dev dependency. */\n installCommand: ReadonlyArray<string>\n /** Subcommands passed to the package manager binary to uninstall a dependency. */\n removeCommand: ReadonlyArray<string>\n /** Subcommands passed to the package manager binary before a `package.json` script name. */\n runCommand: ReadonlyArray<string>\n}\n\n/** Metadata for each supported package manager, keyed by its short name. */\nexport const packageManagers: Record<PackageManagerName, PackageManagerInfo> = {\n pnpm: {\n name: 'pnpm',\n lockFiles: ['pnpm-lock.yaml'],\n initCommand: ['init'],\n installCommand: ['add', '-D'],\n removeCommand: ['remove'],\n runCommand: [],\n },\n yarn: {\n name: 'yarn',\n lockFiles: ['yarn.lock'],\n initCommand: ['init', '-y'],\n installCommand: ['add', '-D'],\n removeCommand: ['remove'],\n runCommand: [],\n },\n bun: {\n name: 'bun',\n lockFiles: ['bun.lock', 'bun.lockb'],\n initCommand: ['init', '-y'],\n installCommand: ['add', '-d'],\n removeCommand: ['remove'],\n runCommand: ['run'],\n },\n npm: {\n name: 'npm',\n lockFiles: ['package-lock.json'],\n initCommand: ['init', '-y'],\n installCommand: ['install', '--save-dev'],\n removeCommand: ['uninstall'],\n runCommand: ['run'],\n },\n}\n\n/** Minimal shape of `package.json` fields read during detection. */\ntype PackageJson = {\n /** The `packageManager` field from `package.json` (e.g. `\"pnpm@9.0.0\"`). */\n packageManager?: string\n scripts?: Record<string, string>\n}\n\n/** Detects the package manager for `cwd`: the `packageManager` field in `package.json`, then a lock file, then `npm`. */\nexport function detectPackageManager(cwd: string = process.cwd()): PackageManagerInfo {\n const packageJsonPath = path.join(cwd, 'package.json')\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageJson\n const pmField = packageJson.packageManager\n if (typeof pmField === 'string') {\n const name = pmField.split('@')[0]\n if (name && name in packageManagers) {\n return packageManagers[name as PackageManagerName]\n }\n }\n } catch {\n // Continue to lock file detection\n }\n }\n\n for (const pm of Object.values(packageManagers)) {\n if (pm.lockFiles.some((lockFile) => fs.existsSync(path.join(cwd, lockFile)))) {\n return pm\n }\n }\n\n return packageManagers.npm\n}\n\n/**\n * Initializes a new `package.json` at `cwd` using the detected package manager.\n */\nexport async function initPackageJson(cwd: string, packageManager: PackageManagerInfo): Promise<void> {\n await x(packageManager.name, [...packageManager.initCommand], {\n nodeOptions: { cwd, stdio: 'inherit' },\n throwOnError: true,\n })\n}\n\n/** How many trailing lines of a failed install's output the error keeps. */\nconst INSTALL_OUTPUT_LINES = 20\nconst INSTALL_TIMEOUT_MS = 5 * 60_000\n\n/**\n * Installs the given packages at `cwd` using the detected package manager. Each output line goes\n * to `onLine`, so the caller can show it while the install runs; a failed install also keeps the\n * end of the output in the error.\n */\nexport async function installPackages(\n packages: Array<string>,\n packageManager: PackageManagerInfo,\n cwd: string = process.cwd(),\n onLine?: (line: string) => void,\n command: ReadonlyArray<string> = packageManager.installCommand,\n): Promise<void> {\n const args = [...command, ...packages]\n // No stdin, so a package manager that wants input fails instead of waiting. The timeout ends a stalled install.\n const result = x(packageManager.name, args, {\n nodeOptions: { cwd, stdio: ['ignore', 'pipe', 'pipe'] },\n timeout: INSTALL_TIMEOUT_MS,\n throwOnError: false,\n })\n const lines: Array<string> = []\n for await (const line of result) {\n lines.push(line)\n onLine?.(line)\n }\n if (result.exitCode === 0) return\n if (result.exitCode === undefined) throw new Error(`\\`${packageManager.name} ${args.join(' ')}\\` timed out after ${INSTALL_TIMEOUT_MS / 60_000} minutes`)\n\n const output = lines.slice(-INSTALL_OUTPUT_LINES).join('\\n')\n throw new Error(`\\`${packageManager.name} ${args.join(' ')}\\` failed with exit code ${result.exitCode}${output ? `:\\n${output}` : ''}`)\n}\n\n/**\n * Uninstalls the given packages at `cwd` with the package manager's remove command, like {@link installPackages}.\n */\nexport function uninstallPackages(packages: Array<string>, packageManager: PackageManagerInfo, cwd: string = process.cwd()): Promise<void> {\n return installPackages(packages, packageManager, cwd, undefined, packageManager.removeCommand)\n}\n\n/**\n * What the selected plugins' `manifests` say is missing from the selection: a `dependencies`\n * entry, or a `client` plugin next to a `consumer`. Empty when nothing is missing.\n * `optionalDependencies` are skipped, since `kubb init` writes the default options.\n */\nexport function missingPluginDependencies(manifests: Array<ExtensionManifest>): Array<string> {\n const selected = manifests.map((manifest) => manifest.name)\n const hasClient = manifests.some((manifest) => manifest.role === 'client')\n return manifests.flatMap((manifest) => [\n ...(manifest.dependencies ?? []).filter((name) => !selected.includes(name)).map((name) => `${manifest.name} needs ${name}.`),\n ...(manifest.role === 'consumer' && !hasClient ? [`${manifest.name} needs a client plugin, such as plugin-axios or plugin-fetch.`] : []),\n ])\n}\n\nasync function readPackageJson(cwd: string): Promise<PackageJson> {\n try {\n return JSON.parse(await read(path.join(cwd, 'package.json'))) as PackageJson\n } catch {\n return {}\n }\n}\n\nconst SPEC_FILE = /^(openapi|swagger)[\\w.-]*\\.(ya?ml|json)$/i\nconst SKIPPED_DIRS = new Set(['node_modules', 'dist', 'build', 'coverage'])\n\n/**\n * Finds OpenAPI spec files up to `depth` folders below `cwd`, shallowest first, as `./`-relative\n * paths. Walks one level at a time and stops after `limit` files, so a large repo stays fast.\n */\nexport async function findSpecFiles(cwd: string, { depth = 3, limit = 20 }: { depth?: number; limit?: number } = {}): Promise<Array<string>> {\n const found: Array<string> = []\n let level: Array<string> = ['']\n for (let current = 0; current <= depth && level.length && found.length < limit; current++) {\n const listings = await Promise.all(\n level.map(async (dir) => ({ dir, entries: await fs.promises.readdir(path.join(cwd, dir), { withFileTypes: true }).catch(() => []) })),\n )\n level = []\n for (const { dir, entries } of listings) {\n for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {\n if (entry.isFile() && SPEC_FILE.test(entry.name)) found.push(`./${path.posix.join(dir, entry.name)}`)\n if (entry.isDirectory() && !entry.name.startsWith('.') && !SKIPPED_DIRS.has(entry.name)) level.push(path.posix.join(dir, entry.name))\n }\n }\n }\n return found.slice(0, limit)\n}\n\n/** The spec version and operation count, when the file states them. */\nexport type SpecInfo = { version?: string; operations?: number }\n\nconst SPEC_HEAD_BYTES = 256 * 1024\nconst METHOD = '(?:get|put|post|delete|options|head|patch|trace)'\n\n/**\n * Reads the OpenAPI or Swagger version and counts the operations from the first 256 KB of the\n * file, so a large spec is never parsed. The count is left out when the file is longer.\n */\nexport async function readSpecInfo(file: string): Promise<SpecInfo> {\n const handle = await fs.promises.open(file)\n try {\n const buffer = Buffer.alloc(SPEC_HEAD_BYTES)\n const { bytesRead } = await handle.read(buffer, 0, SPEC_HEAD_BYTES, 0)\n const head = buffer.toString('utf-8', 0, bytesRead)\n const complete = bytesRead < SPEC_HEAD_BYTES\n const version = head.match(/^\\s*[\"']?(?:openapi|swagger)[\"']?\\s*:\\s*[\"']?(\\d[\\d.]*)/m)?.[1]\n // shortcut: counts method keys at any depth, so a schema property named `get` counts too; use the adapter when that matters.\n const methods = new RegExp(`^\\\\s*[\"']?${METHOD}[\"']?\\\\s*:\\\\s*(?:\\\\{|$)`, 'gm')\n return { version, operations: complete ? (head.match(methods)?.length ?? 0) : undefined }\n } finally {\n await handle.close()\n }\n}\n\n/** Finds a plugin by registry id, slug, or short name, so `ts`, `plugin-ts`, and `kubb-labs/plugin-ts` all match. */\nexport function findPlugin({ plugins, id }: { plugins: Array<RegistryEntry>; id: string }): RegistryEntry | undefined {\n const ids = id.includes('/') ? [id] : [`kubb-labs/${id}`, `kubb-labs/plugin-${id}`]\n return plugins.find((entry) => ids.includes(entry.id))\n}\n\n/**\n * Turns the comma-separated `--plugins` value into registry entries.\n *\n * @throws when an id is not in `plugins`.\n */\nexport function parsePluginsFlag({ plugins, value }: { plugins: Array<RegistryEntry>; value: string }): Array<RegistryEntry> {\n const ids = value\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean)\n const unknown = ids.filter((id) => !findPlugin({ plugins, id }))\n if (unknown.length)\n throw new Diagnostics.Error({\n code: Diagnostics.code.pluginNotFound,\n severity: 'error',\n message: `Not in the Kubb registry: ${unknown.join(', ')}. Browse the plugins at https://kubb.dev/extensions.`,\n help: 'Use plugin ids from https://kubb.dev/extensions, such as plugin-ts or ts.',\n })\n return ids.map((id) => findPlugin({ plugins, id })!)\n}\n\n/** Adds `\"generate\": \"kubb generate\"` to the `package.json` scripts at `cwd`. Returns `false`, and changes nothing, when a `generate` script exists. */\nexport async function addGenerateScript(cwd: string): Promise<boolean> {\n const file = path.join(cwd, 'package.json')\n const source = await read(file)\n const packageJson = JSON.parse(source) as PackageJson\n if (packageJson.scripts?.generate !== undefined) return false\n const indent = source.match(/^[ \\t]+/m)?.[0] ?? ' '\n const updated = { ...packageJson, scripts: { ...packageJson.scripts, generate: 'kubb generate' } }\n await write(file, JSON.stringify(updated, null, indent))\n return true\n}\n\n/** Reads the `generate` script from the `package.json` at `cwd`. */\nexport async function generateScript(cwd: string): Promise<string | undefined> {\n return (await readPackageJson(cwd)).scripts?.generate\n}\n"],"mappings":"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"}
|