create-krispya 0.7.0 → 0.8.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/README.md +16 -5
- package/dist/chunks/index.cjs +3266 -0
- package/dist/chunks/index.mjs +3226 -0
- package/dist/cli.cjs +2826 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +2805 -0
- package/dist/index.cjs +21 -2969
- package/dist/index.d.cts +38 -32
- package/dist/index.d.mts +38 -32
- package/dist/index.d.ts +38 -32
- package/dist/index.mjs +5 -2953
- package/package.json +4 -1
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,2826 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const p = require('@clack/prompts');
|
|
5
|
+
const color = require('chalk');
|
|
6
|
+
const commander = require('commander');
|
|
7
|
+
const node_fs = require('node:fs');
|
|
8
|
+
const promises$1 = require('node:fs/promises');
|
|
9
|
+
const node_module = require('node:module');
|
|
10
|
+
const node_path = require('node:path');
|
|
11
|
+
const node_process = require('node:process');
|
|
12
|
+
const undici = require('undici');
|
|
13
|
+
const child_process = require('child_process');
|
|
14
|
+
const index = require('./chunks/index.cjs');
|
|
15
|
+
const Conf = require('conf');
|
|
16
|
+
const promises = require('fs/promises');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
21
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
22
|
+
|
|
23
|
+
function _interopNamespaceCompat(e) {
|
|
24
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
25
|
+
const n = Object.create(null);
|
|
26
|
+
if (e) {
|
|
27
|
+
for (const k in e) {
|
|
28
|
+
n[k] = e[k];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
n.default = e;
|
|
32
|
+
return n;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const p__namespace = /*#__PURE__*/_interopNamespaceCompat(p);
|
|
36
|
+
const color__default = /*#__PURE__*/_interopDefaultCompat(color);
|
|
37
|
+
const Conf__default = /*#__PURE__*/_interopDefaultCompat(Conf);
|
|
38
|
+
|
|
39
|
+
const editorNames = {
|
|
40
|
+
cursor: "Cursor",
|
|
41
|
+
code: "VS Code",
|
|
42
|
+
webstorm: "WebStorm",
|
|
43
|
+
skip: "Skip"
|
|
44
|
+
};
|
|
45
|
+
function openInEditor(editor, path, reuseWindow) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const isWindows = process.platform === "win32";
|
|
48
|
+
const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
|
|
49
|
+
const args = useReuseFlag ? ["-r", path] : [path];
|
|
50
|
+
const child = isWindows ? child_process.spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
|
|
51
|
+
detached: true,
|
|
52
|
+
stdio: "ignore",
|
|
53
|
+
shell: true
|
|
54
|
+
}) : child_process.spawn(editor, args, {
|
|
55
|
+
detached: true,
|
|
56
|
+
stdio: "ignore"
|
|
57
|
+
});
|
|
58
|
+
child.on("error", reject);
|
|
59
|
+
child.unref();
|
|
60
|
+
setTimeout(resolve, 100);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatConfigSummary(options, inherited) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
const VALUE_COL = 27;
|
|
67
|
+
const formatRow = (label, value, isInherited = false, indent = "") => {
|
|
68
|
+
const fullLabel = indent + label;
|
|
69
|
+
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
70
|
+
const dots = color__default.gray(".".repeat(dotCount));
|
|
71
|
+
const displayValue = isInherited ? `${value} \u{1F512}` : value;
|
|
72
|
+
return `${indent}${label} ${dots} ${displayValue}`;
|
|
73
|
+
};
|
|
74
|
+
const formatLanguage = (lang) => {
|
|
75
|
+
return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
|
|
76
|
+
};
|
|
77
|
+
const projectType = options.projectType ?? "app";
|
|
78
|
+
const baseTemplate = options.template ? index.getBaseTemplate(options.template) : "vanilla";
|
|
79
|
+
if (baseTemplate === "react") {
|
|
80
|
+
lines.push(formatRow("Framework", "React"));
|
|
81
|
+
} else if (baseTemplate === "r3f") {
|
|
82
|
+
lines.push(formatRow("Framework", "React Three Fiber"));
|
|
83
|
+
}
|
|
84
|
+
const language = options.template ? index.getLanguageFromTemplate(options.template) : "typescript";
|
|
85
|
+
lines.push(formatRow("Language", formatLanguage(language)));
|
|
86
|
+
if (projectType === "library") {
|
|
87
|
+
lines.push(formatRow("Bundler", options.libraryBundler ?? "unbuild"));
|
|
88
|
+
} else {
|
|
89
|
+
lines.push(formatRow("Bundler", "vite"));
|
|
90
|
+
}
|
|
91
|
+
const engineInherited = inherited?.engine !== void 0;
|
|
92
|
+
lines.push(
|
|
93
|
+
formatRow(
|
|
94
|
+
"Engine",
|
|
95
|
+
`${index.getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
|
|
96
|
+
engineInherited
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
const pmInherited = inherited?.packageManager !== void 0;
|
|
100
|
+
lines.push(
|
|
101
|
+
formatRow("Package manager", index.getPackageManagerName(options.packageManager), pmInherited)
|
|
102
|
+
);
|
|
103
|
+
if (index.getPackageManagerName(options.packageManager) === "pnpm") {
|
|
104
|
+
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
105
|
+
const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
|
|
106
|
+
lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
|
|
107
|
+
}
|
|
108
|
+
if (options.linter) {
|
|
109
|
+
const linterInherited = inherited?.linter !== void 0;
|
|
110
|
+
lines.push(formatRow("Linter", options.linter, linterInherited));
|
|
111
|
+
}
|
|
112
|
+
if (options.formatter) {
|
|
113
|
+
const formatterInherited = inherited?.formatter !== void 0;
|
|
114
|
+
lines.push(formatRow("Formatter", options.formatter, formatterInherited));
|
|
115
|
+
}
|
|
116
|
+
const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
|
|
117
|
+
lines.push(formatRow("Testing", testing));
|
|
118
|
+
if (!inherited) {
|
|
119
|
+
const configStrategy = options.configStrategy ?? "stealth";
|
|
120
|
+
lines.push(formatRow("Config strategy", configStrategy));
|
|
121
|
+
}
|
|
122
|
+
if (options.template && index.getBaseTemplate(options.template) === "r3f") {
|
|
123
|
+
const integrationNames = [
|
|
124
|
+
options.drei && "drei",
|
|
125
|
+
options.handle && "handle",
|
|
126
|
+
options.leva && "leva",
|
|
127
|
+
options.postprocessing && "postproc",
|
|
128
|
+
options.rapier && "rapier",
|
|
129
|
+
options.xr && "xr",
|
|
130
|
+
options.uikit && "uikit",
|
|
131
|
+
options.offscreen && "offscreen",
|
|
132
|
+
options.zustand && "zustand",
|
|
133
|
+
options.koota && "koota",
|
|
134
|
+
options.triplex && "triplex",
|
|
135
|
+
options.viverse && "viverse"
|
|
136
|
+
].filter(Boolean);
|
|
137
|
+
lines.push("");
|
|
138
|
+
lines.push(color__default.dim("Integrations"));
|
|
139
|
+
for (let i = 0; i < integrationNames.length; i += 2) {
|
|
140
|
+
const left = `${color__default.green("\u25CF")} ${integrationNames[i]}`;
|
|
141
|
+
const right = integrationNames[i + 1] ? `${color__default.green("\u25CF")} ${integrationNames[i + 1]}` : "";
|
|
142
|
+
const spacing = " ".repeat(Math.max(1, 16 - integrationNames[i].length));
|
|
143
|
+
lines.push(` ${left}${spacing}${right}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
function formatMonorepoConfigSummary(options) {
|
|
149
|
+
const lines = [];
|
|
150
|
+
const VALUE_COL = 27;
|
|
151
|
+
const formatRow = (label, value, indent = "") => {
|
|
152
|
+
const fullLabel = indent + label;
|
|
153
|
+
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
154
|
+
const dots = color__default.gray(".".repeat(dotCount));
|
|
155
|
+
return `${indent}${label} ${dots} ${value}`;
|
|
156
|
+
};
|
|
157
|
+
lines.push(
|
|
158
|
+
formatRow("Engine", `${index.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
|
|
159
|
+
);
|
|
160
|
+
lines.push(formatRow("Package manager", options.packageManager));
|
|
161
|
+
if (options.packageManager === "pnpm") {
|
|
162
|
+
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
163
|
+
lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
|
|
164
|
+
}
|
|
165
|
+
lines.push(formatRow("Linter", options.linter));
|
|
166
|
+
lines.push(formatRow("Formatter", options.formatter));
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const config = new Conf__default({
|
|
171
|
+
projectName: "create-krispya"
|
|
172
|
+
});
|
|
173
|
+
function getPreferredEditor() {
|
|
174
|
+
return config.get("preferredEditor");
|
|
175
|
+
}
|
|
176
|
+
function setPreferredEditor(editor) {
|
|
177
|
+
config.set("preferredEditor", editor);
|
|
178
|
+
}
|
|
179
|
+
function getReuseWindow() {
|
|
180
|
+
return config.get("reuseWindow") ?? false;
|
|
181
|
+
}
|
|
182
|
+
function setReuseWindow(reuse) {
|
|
183
|
+
config.set("reuseWindow", reuse);
|
|
184
|
+
}
|
|
185
|
+
function getAiPlatforms() {
|
|
186
|
+
return config.get("aiPlatforms");
|
|
187
|
+
}
|
|
188
|
+
function getConfigStrategy() {
|
|
189
|
+
return config.get("configStrategy") ?? "stealth";
|
|
190
|
+
}
|
|
191
|
+
function clearConfig() {
|
|
192
|
+
config.clear();
|
|
193
|
+
}
|
|
194
|
+
function getConfigPath() {
|
|
195
|
+
return config.path;
|
|
196
|
+
}
|
|
197
|
+
function getCustomTemplates() {
|
|
198
|
+
return config.get("customTemplates") ?? {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
|
|
202
|
+
const baseTemplate = index.getBaseTemplate(template);
|
|
203
|
+
const base = {
|
|
204
|
+
name,
|
|
205
|
+
template,
|
|
206
|
+
projectType,
|
|
207
|
+
libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
|
|
208
|
+
packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
|
|
209
|
+
pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
|
|
210
|
+
engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
|
|
211
|
+
linter: inheritedSettings?.linter ?? "oxlint",
|
|
212
|
+
formatter: inheritedSettings?.formatter ?? "prettier",
|
|
213
|
+
// Libraries get vitest by default, apps don't
|
|
214
|
+
testing: projectType === "library" ? "vitest" : "none",
|
|
215
|
+
configStrategy: getConfigStrategy()
|
|
216
|
+
};
|
|
217
|
+
if (baseTemplate === "r3f" && integrations) {
|
|
218
|
+
return {
|
|
219
|
+
...base,
|
|
220
|
+
drei: integrations.includes("drei") ? {} : void 0,
|
|
221
|
+
handle: integrations.includes("handle") ? {} : void 0,
|
|
222
|
+
leva: integrations.includes("leva") ? {} : void 0,
|
|
223
|
+
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
224
|
+
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
225
|
+
xr: integrations.includes("xr") ? {} : void 0,
|
|
226
|
+
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
227
|
+
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
228
|
+
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
229
|
+
koota: integrations.includes("koota") ? {} : void 0,
|
|
230
|
+
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
231
|
+
viverse: integrations.includes("viverse") ? {} : void 0
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return base;
|
|
235
|
+
}
|
|
236
|
+
function getDefaultProjectName(template) {
|
|
237
|
+
const base = index.getBaseTemplate(template);
|
|
238
|
+
switch (base) {
|
|
239
|
+
case "vanilla":
|
|
240
|
+
return `vanilla-${index.generateRandomName()}`;
|
|
241
|
+
case "react":
|
|
242
|
+
return `react-${index.generateRandomName()}`;
|
|
243
|
+
case "r3f":
|
|
244
|
+
return `react-three-${index.generateRandomName()}`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function promptForR3fIntegrations(presets) {
|
|
248
|
+
const initialValues = [];
|
|
249
|
+
if (presets) {
|
|
250
|
+
if (presets.drei) initialValues.push("drei");
|
|
251
|
+
if (presets.handle) initialValues.push("handle");
|
|
252
|
+
if (presets.leva) initialValues.push("leva");
|
|
253
|
+
if (presets.postprocessing) initialValues.push("postprocessing");
|
|
254
|
+
if (presets.rapier) initialValues.push("rapier");
|
|
255
|
+
if (presets.xr) initialValues.push("xr");
|
|
256
|
+
if (presets.uikit) initialValues.push("uikit");
|
|
257
|
+
if (presets.offscreen) initialValues.push("offscreen");
|
|
258
|
+
if (presets.zustand) initialValues.push("zustand");
|
|
259
|
+
if (presets.koota) initialValues.push("koota");
|
|
260
|
+
if (presets.triplex) initialValues.push("triplex");
|
|
261
|
+
if (presets.viverse) initialValues.push("viverse");
|
|
262
|
+
}
|
|
263
|
+
const selected = await p__namespace.multiselect({
|
|
264
|
+
message: "R3F integrations",
|
|
265
|
+
options: [
|
|
266
|
+
{ value: "drei", label: "Drei" },
|
|
267
|
+
{ value: "handle", label: "Handle" },
|
|
268
|
+
{ value: "leva", label: "Leva" },
|
|
269
|
+
{ value: "postprocessing", label: "Postprocessing" },
|
|
270
|
+
{ value: "rapier", label: "Rapier" },
|
|
271
|
+
{ value: "xr", label: "XR" },
|
|
272
|
+
{ value: "uikit", label: "UIKit" },
|
|
273
|
+
{ value: "offscreen", label: "Offscreen" },
|
|
274
|
+
{ value: "zustand", label: "Zustand" },
|
|
275
|
+
{ value: "koota", label: "Koota" },
|
|
276
|
+
{ value: "triplex", label: "Triplex" },
|
|
277
|
+
{ value: "viverse", label: "Viverse" }
|
|
278
|
+
],
|
|
279
|
+
initialValues: initialValues.length > 0 ? initialValues : ["drei"],
|
|
280
|
+
required: false
|
|
281
|
+
});
|
|
282
|
+
if (p__namespace.isCancel(selected)) {
|
|
283
|
+
p__namespace.cancel("Operation cancelled.");
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
286
|
+
return selected;
|
|
287
|
+
}
|
|
288
|
+
async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
|
|
289
|
+
let libraryBundler;
|
|
290
|
+
if (projectType === "library") {
|
|
291
|
+
const bundler = await p__namespace.select({
|
|
292
|
+
message: "Library bundler",
|
|
293
|
+
options: [
|
|
294
|
+
{ value: "unbuild", label: "unbuild", hint: "unjs, simple config" },
|
|
295
|
+
{ value: "tsdown", label: "tsdown", hint: "fast, esbuild-based" }
|
|
296
|
+
],
|
|
297
|
+
initialValue: presets?.bundler ?? "unbuild"
|
|
298
|
+
});
|
|
299
|
+
if (p__namespace.isCancel(bundler)) {
|
|
300
|
+
p__namespace.cancel("Operation cancelled.");
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
libraryBundler = bundler;
|
|
304
|
+
}
|
|
305
|
+
let engine = inheritedSettings?.engine ?? presets?.engine ?? { name: "node", version: "latest" };
|
|
306
|
+
let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager ?? "pnpm";
|
|
307
|
+
let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
|
|
308
|
+
if (!inheritedSettings?.engine?.version) {
|
|
309
|
+
const nodeVersionInput = await p__namespace.text({
|
|
310
|
+
message: "Node.js version",
|
|
311
|
+
placeholder: presets?.engine?.version ?? "latest",
|
|
312
|
+
defaultValue: presets?.engine?.version ?? "latest",
|
|
313
|
+
validate: (value) => {
|
|
314
|
+
if (!value.length) return "Required";
|
|
315
|
+
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
316
|
+
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
if (p__namespace.isCancel(nodeVersionInput)) {
|
|
321
|
+
p__namespace.cancel("Operation cancelled.");
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
engine = { name: "node", version: nodeVersionInput };
|
|
325
|
+
}
|
|
326
|
+
if (!inheritedSettings?.packageManager) {
|
|
327
|
+
const packageManager = await p__namespace.select({
|
|
328
|
+
message: "Package manager",
|
|
329
|
+
options: [
|
|
330
|
+
{ value: "pnpm", label: "pnpm" },
|
|
331
|
+
{ value: "npm", label: "npm" },
|
|
332
|
+
{ value: "yarn", label: "yarn" }
|
|
333
|
+
],
|
|
334
|
+
initialValue: presets?.packageManager ?? "pnpm"
|
|
335
|
+
});
|
|
336
|
+
if (p__namespace.isCancel(packageManager)) {
|
|
337
|
+
p__namespace.cancel("Operation cancelled.");
|
|
338
|
+
process.exit(0);
|
|
339
|
+
}
|
|
340
|
+
finalPackageManager = packageManager;
|
|
341
|
+
if (packageManager === "pnpm") {
|
|
342
|
+
const managePnpm = await p__namespace.confirm({
|
|
343
|
+
message: "Enable manage-package-manager-versions?",
|
|
344
|
+
initialValue: presets?.pnpmManageVersions ?? true
|
|
345
|
+
});
|
|
346
|
+
if (p__namespace.isCancel(managePnpm)) {
|
|
347
|
+
p__namespace.cancel("Operation cancelled.");
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
pnpmManageVersions = managePnpm;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
let linter = inheritedSettings?.linter ?? presets?.linter ?? "oxlint";
|
|
354
|
+
let formatter = inheritedSettings?.formatter ?? presets?.formatter ?? "prettier";
|
|
355
|
+
if (!inheritedSettings?.linter) {
|
|
356
|
+
const linterChoice = await p__namespace.select({
|
|
357
|
+
message: "Linter",
|
|
358
|
+
options: [
|
|
359
|
+
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
360
|
+
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
361
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
362
|
+
],
|
|
363
|
+
initialValue: presets?.linter ?? "oxlint"
|
|
364
|
+
});
|
|
365
|
+
if (p__namespace.isCancel(linterChoice)) {
|
|
366
|
+
p__namespace.cancel("Operation cancelled.");
|
|
367
|
+
process.exit(0);
|
|
368
|
+
}
|
|
369
|
+
linter = linterChoice;
|
|
370
|
+
}
|
|
371
|
+
if (!inheritedSettings?.formatter) {
|
|
372
|
+
const formatterChoice = await p__namespace.select({
|
|
373
|
+
message: "Formatter",
|
|
374
|
+
options: [
|
|
375
|
+
{ value: "prettier", label: "Prettier", hint: "widely adopted" },
|
|
376
|
+
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
377
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
378
|
+
],
|
|
379
|
+
initialValue: presets?.formatter ?? "prettier"
|
|
380
|
+
});
|
|
381
|
+
if (p__namespace.isCancel(formatterChoice)) {
|
|
382
|
+
p__namespace.cancel("Operation cancelled.");
|
|
383
|
+
process.exit(0);
|
|
384
|
+
}
|
|
385
|
+
formatter = formatterChoice;
|
|
386
|
+
}
|
|
387
|
+
const testing = await p__namespace.select({
|
|
388
|
+
message: "Testing",
|
|
389
|
+
options: [
|
|
390
|
+
{ value: "vitest", label: "Vitest", hint: "fast, Vite-native" },
|
|
391
|
+
{ value: "none", label: "None" }
|
|
392
|
+
],
|
|
393
|
+
initialValue: projectType === "library" ? "vitest" : "none"
|
|
394
|
+
});
|
|
395
|
+
if (p__namespace.isCancel(testing)) {
|
|
396
|
+
p__namespace.cancel("Operation cancelled.");
|
|
397
|
+
process.exit(0);
|
|
398
|
+
}
|
|
399
|
+
const language = await p__namespace.select({
|
|
400
|
+
message: "Language",
|
|
401
|
+
options: [
|
|
402
|
+
{ value: "typescript", label: "TypeScript" },
|
|
403
|
+
{ value: "javascript", label: "JavaScript" }
|
|
404
|
+
],
|
|
405
|
+
initialValue: "typescript"
|
|
406
|
+
});
|
|
407
|
+
if (p__namespace.isCancel(language)) {
|
|
408
|
+
p__namespace.cancel("Operation cancelled.");
|
|
409
|
+
process.exit(0);
|
|
410
|
+
}
|
|
411
|
+
const configStrategyChoice = await p__namespace.select({
|
|
412
|
+
message: "Config strategy",
|
|
413
|
+
options: [
|
|
414
|
+
{ value: "stealth", label: "stealth", hint: "configs in .config/" },
|
|
415
|
+
{ value: "root", label: "root", hint: "configs at project root" }
|
|
416
|
+
],
|
|
417
|
+
initialValue: getConfigStrategy()
|
|
418
|
+
});
|
|
419
|
+
if (p__namespace.isCancel(configStrategyChoice)) {
|
|
420
|
+
p__namespace.cancel("Operation cancelled.");
|
|
421
|
+
process.exit(0);
|
|
422
|
+
}
|
|
423
|
+
const baseTemplate = index.getBaseTemplate(template);
|
|
424
|
+
const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
|
|
425
|
+
const base = {
|
|
426
|
+
name,
|
|
427
|
+
template: finalTemplate,
|
|
428
|
+
projectType,
|
|
429
|
+
libraryBundler: projectType === "library" ? libraryBundler : void 0,
|
|
430
|
+
engine,
|
|
431
|
+
packageManager: { name: finalPackageManager },
|
|
432
|
+
pnpmManageVersions,
|
|
433
|
+
linter,
|
|
434
|
+
formatter,
|
|
435
|
+
testing,
|
|
436
|
+
configStrategy: configStrategyChoice
|
|
437
|
+
};
|
|
438
|
+
if (baseTemplate === "r3f" && integrations) {
|
|
439
|
+
return {
|
|
440
|
+
...base,
|
|
441
|
+
drei: integrations.includes("drei") ? {} : void 0,
|
|
442
|
+
handle: integrations.includes("handle") ? {} : void 0,
|
|
443
|
+
leva: integrations.includes("leva") ? {} : void 0,
|
|
444
|
+
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
445
|
+
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
446
|
+
xr: integrations.includes("xr") ? {} : void 0,
|
|
447
|
+
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
448
|
+
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
449
|
+
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
450
|
+
koota: integrations.includes("koota") ? {} : void 0,
|
|
451
|
+
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
452
|
+
viverse: integrations.includes("viverse") ? {} : void 0
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return base;
|
|
456
|
+
}
|
|
457
|
+
async function promptForInitialPackage() {
|
|
458
|
+
const choice = await p__namespace.select({
|
|
459
|
+
message: "Add an initial package?",
|
|
460
|
+
options: [
|
|
461
|
+
{ value: "app", label: "Application" },
|
|
462
|
+
{ value: "library", label: "Library" },
|
|
463
|
+
{ value: "skip", label: "Skip" }
|
|
464
|
+
],
|
|
465
|
+
initialValue: "app"
|
|
466
|
+
});
|
|
467
|
+
if (p__namespace.isCancel(choice)) {
|
|
468
|
+
p__namespace.cancel("Operation cancelled.");
|
|
469
|
+
process.exit(0);
|
|
470
|
+
}
|
|
471
|
+
return choice;
|
|
472
|
+
}
|
|
473
|
+
function getDefaultMonorepoOptions(name) {
|
|
474
|
+
return {
|
|
475
|
+
name,
|
|
476
|
+
projectType: "monorepo",
|
|
477
|
+
packageManager: { name: "pnpm" },
|
|
478
|
+
pnpmManageVersions: true,
|
|
479
|
+
engine: { name: "node", version: "latest" },
|
|
480
|
+
linter: "oxlint",
|
|
481
|
+
formatter: "prettier"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function promptForMonorepoCustomization(name, presets) {
|
|
485
|
+
const nodeVersion = await p__namespace.text({
|
|
486
|
+
message: "Node.js version",
|
|
487
|
+
placeholder: presets?.engine?.version ?? "latest",
|
|
488
|
+
defaultValue: presets?.engine?.version ?? "latest",
|
|
489
|
+
validate: (value) => {
|
|
490
|
+
if (!value.length) return "Required";
|
|
491
|
+
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
492
|
+
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
if (p__namespace.isCancel(nodeVersion)) {
|
|
497
|
+
p__namespace.cancel("Operation cancelled.");
|
|
498
|
+
process.exit(0);
|
|
499
|
+
}
|
|
500
|
+
const managePnpm = await p__namespace.confirm({
|
|
501
|
+
message: "Enable manage-package-manager-versions?",
|
|
502
|
+
initialValue: presets?.pnpmManageVersions ?? true
|
|
503
|
+
});
|
|
504
|
+
if (p__namespace.isCancel(managePnpm)) {
|
|
505
|
+
p__namespace.cancel("Operation cancelled.");
|
|
506
|
+
process.exit(0);
|
|
507
|
+
}
|
|
508
|
+
const linter = await p__namespace.select({
|
|
509
|
+
message: "Linter",
|
|
510
|
+
options: [
|
|
511
|
+
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
512
|
+
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
513
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
514
|
+
],
|
|
515
|
+
initialValue: presets?.linter ?? "oxlint"
|
|
516
|
+
});
|
|
517
|
+
if (p__namespace.isCancel(linter)) {
|
|
518
|
+
p__namespace.cancel("Operation cancelled.");
|
|
519
|
+
process.exit(0);
|
|
520
|
+
}
|
|
521
|
+
const formatter = await p__namespace.select({
|
|
522
|
+
message: "Formatter",
|
|
523
|
+
options: [
|
|
524
|
+
{ value: "prettier", label: "Prettier", hint: "widely adopted" },
|
|
525
|
+
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
526
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
527
|
+
],
|
|
528
|
+
initialValue: presets?.formatter ?? "prettier"
|
|
529
|
+
});
|
|
530
|
+
if (p__namespace.isCancel(formatter)) {
|
|
531
|
+
p__namespace.cancel("Operation cancelled.");
|
|
532
|
+
process.exit(0);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
name,
|
|
536
|
+
projectType: "monorepo",
|
|
537
|
+
engine: { name: "node", version: nodeVersion },
|
|
538
|
+
packageManager: { name: "pnpm" },
|
|
539
|
+
pnpmManageVersions: managePnpm,
|
|
540
|
+
linter,
|
|
541
|
+
formatter
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async function promptForMonorepo(workspaceName, presets) {
|
|
545
|
+
const defaultOptions = getDefaultMonorepoOptions(workspaceName);
|
|
546
|
+
if (presets) {
|
|
547
|
+
if (presets.linter) defaultOptions.linter = presets.linter;
|
|
548
|
+
if (presets.formatter) defaultOptions.formatter = presets.formatter;
|
|
549
|
+
if (presets.engine) defaultOptions.engine = presets.engine;
|
|
550
|
+
if (presets.pnpmManageVersions !== void 0)
|
|
551
|
+
defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
|
|
552
|
+
}
|
|
553
|
+
p__namespace.note(
|
|
554
|
+
formatMonorepoConfigSummary({
|
|
555
|
+
name: defaultOptions.name,
|
|
556
|
+
engine: defaultOptions.engine ?? { name: "node", version: "latest" },
|
|
557
|
+
packageManager: index.getPackageManagerName(defaultOptions.packageManager),
|
|
558
|
+
pnpmManageVersions: defaultOptions.pnpmManageVersions,
|
|
559
|
+
linter: defaultOptions.linter ?? "oxlint",
|
|
560
|
+
formatter: defaultOptions.formatter ?? "prettier"
|
|
561
|
+
}),
|
|
562
|
+
"Workspace Configuration"
|
|
563
|
+
);
|
|
564
|
+
const proceed = await p__namespace.select({
|
|
565
|
+
message: "Proceed with these settings?",
|
|
566
|
+
options: [
|
|
567
|
+
{ value: "continue", label: "Yes, continue" },
|
|
568
|
+
{ value: "customize", label: "No, customize settings" }
|
|
569
|
+
],
|
|
570
|
+
initialValue: "continue"
|
|
571
|
+
});
|
|
572
|
+
if (p__namespace.isCancel(proceed)) {
|
|
573
|
+
p__namespace.cancel("Operation cancelled.");
|
|
574
|
+
process.exit(0);
|
|
575
|
+
}
|
|
576
|
+
if (proceed === "continue") {
|
|
577
|
+
return defaultOptions;
|
|
578
|
+
}
|
|
579
|
+
return promptForMonorepoCustomization(workspaceName, presets);
|
|
580
|
+
}
|
|
581
|
+
async function promptForOptions(name, presets) {
|
|
582
|
+
let projectName = name;
|
|
583
|
+
if (!projectName) {
|
|
584
|
+
const nameResult = await p__namespace.text({
|
|
585
|
+
message: "What is your project named?",
|
|
586
|
+
placeholder: index.generateRandomName(),
|
|
587
|
+
defaultValue: index.generateRandomName(),
|
|
588
|
+
validate: (value) => {
|
|
589
|
+
if (!value.length) return "Project name is required";
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
if (p__namespace.isCancel(nameResult)) {
|
|
593
|
+
p__namespace.cancel("Operation cancelled.");
|
|
594
|
+
process.exit(0);
|
|
595
|
+
}
|
|
596
|
+
projectName = nameResult;
|
|
597
|
+
}
|
|
598
|
+
const projectType = await p__namespace.select({
|
|
599
|
+
message: "Project type",
|
|
600
|
+
options: [
|
|
601
|
+
{ value: "app", label: "Application" },
|
|
602
|
+
{ value: "library", label: "Library" },
|
|
603
|
+
{ value: "monorepo", label: "Monorepo" }
|
|
604
|
+
],
|
|
605
|
+
initialValue: presets?.type ?? "app"
|
|
606
|
+
});
|
|
607
|
+
if (p__namespace.isCancel(projectType)) {
|
|
608
|
+
p__namespace.cancel("Operation cancelled.");
|
|
609
|
+
process.exit(0);
|
|
610
|
+
}
|
|
611
|
+
if (projectType === "monorepo") {
|
|
612
|
+
return promptForMonorepo(projectName, presets);
|
|
613
|
+
}
|
|
614
|
+
return promptForPackageOptions(projectName, projectType, void 0, presets);
|
|
615
|
+
}
|
|
616
|
+
function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
|
|
617
|
+
const baseTemplate = customTemplate.baseTemplate;
|
|
618
|
+
const template = baseTemplate;
|
|
619
|
+
const base = {
|
|
620
|
+
name,
|
|
621
|
+
template,
|
|
622
|
+
projectType,
|
|
623
|
+
packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
|
|
624
|
+
pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
|
|
625
|
+
engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
|
|
626
|
+
linter: inheritedSettings?.linter ?? customTemplate.linter,
|
|
627
|
+
formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
|
|
628
|
+
testing: customTemplate.testing,
|
|
629
|
+
configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
|
|
630
|
+
};
|
|
631
|
+
if (baseTemplate === "r3f" && customTemplate.integrations) {
|
|
632
|
+
const integrations = customTemplate.integrations;
|
|
633
|
+
return {
|
|
634
|
+
...base,
|
|
635
|
+
drei: integrations.includes("drei") ? {} : void 0,
|
|
636
|
+
handle: integrations.includes("handle") ? {} : void 0,
|
|
637
|
+
leva: integrations.includes("leva") ? {} : void 0,
|
|
638
|
+
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
639
|
+
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
640
|
+
xr: integrations.includes("xr") ? {} : void 0,
|
|
641
|
+
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
642
|
+
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
643
|
+
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
644
|
+
koota: integrations.includes("koota") ? {} : void 0,
|
|
645
|
+
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
646
|
+
viverse: integrations.includes("viverse") ? {} : void 0
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return base;
|
|
650
|
+
}
|
|
651
|
+
function presetsToInheritedSettings(presets) {
|
|
652
|
+
if (!presets) return void 0;
|
|
653
|
+
return {
|
|
654
|
+
linter: presets.linter,
|
|
655
|
+
formatter: presets.formatter,
|
|
656
|
+
packageManager: presets.packageManager ? { name: presets.packageManager } : void 0,
|
|
657
|
+
engine: presets.engine,
|
|
658
|
+
pnpmManageVersions: presets.pnpmManageVersions
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
|
|
662
|
+
const builtInOptions = [
|
|
663
|
+
{ value: "vanilla", label: "Vanilla" },
|
|
664
|
+
{ value: "react", label: "React" },
|
|
665
|
+
{ value: "r3f", label: "React Three Fiber" }
|
|
666
|
+
];
|
|
667
|
+
const customTemplates = getCustomTemplates();
|
|
668
|
+
const customOptions = Object.keys(customTemplates).map((name) => ({
|
|
669
|
+
value: `custom:${name}`,
|
|
670
|
+
label: name,
|
|
671
|
+
hint: "saved template"
|
|
672
|
+
}));
|
|
673
|
+
const allOptions = [...builtInOptions, ...customOptions];
|
|
674
|
+
const templateSelection = await p__namespace.select({
|
|
675
|
+
message: "Select a template",
|
|
676
|
+
options: allOptions,
|
|
677
|
+
initialValue: presets?.template ?? "vanilla"
|
|
678
|
+
});
|
|
679
|
+
if (p__namespace.isCancel(templateSelection)) {
|
|
680
|
+
p__namespace.cancel("Operation cancelled.");
|
|
681
|
+
process.exit(0);
|
|
682
|
+
}
|
|
683
|
+
const selection = templateSelection;
|
|
684
|
+
if (selection.startsWith("custom:")) {
|
|
685
|
+
const customName = selection.slice(7);
|
|
686
|
+
const customTemplate = customTemplates[customName];
|
|
687
|
+
const defaultOptions2 = customTemplateToOptions(
|
|
688
|
+
customTemplate,
|
|
689
|
+
projectName,
|
|
690
|
+
projectType,
|
|
691
|
+
inheritedSettings
|
|
692
|
+
);
|
|
693
|
+
const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
|
|
694
|
+
p__namespace.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
|
|
695
|
+
const proceed2 = await p__namespace.select({
|
|
696
|
+
message: "Proceed with these settings?",
|
|
697
|
+
options: [
|
|
698
|
+
{ value: "continue", label: "Yes, continue" },
|
|
699
|
+
{ value: "customize", label: "No, customize settings" }
|
|
700
|
+
],
|
|
701
|
+
initialValue: "continue"
|
|
702
|
+
});
|
|
703
|
+
if (p__namespace.isCancel(proceed2)) {
|
|
704
|
+
p__namespace.cancel("Operation cancelled.");
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
if (proceed2 === "continue") {
|
|
708
|
+
return defaultOptions2;
|
|
709
|
+
}
|
|
710
|
+
return promptForCustomization(
|
|
711
|
+
customTemplate.baseTemplate,
|
|
712
|
+
projectName,
|
|
713
|
+
projectType,
|
|
714
|
+
customTemplate.integrations,
|
|
715
|
+
inheritedSettings
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const template = selection;
|
|
719
|
+
const baseTemplate = index.getBaseTemplate(template);
|
|
720
|
+
let integrations;
|
|
721
|
+
if (baseTemplate === "r3f") {
|
|
722
|
+
integrations = await promptForR3fIntegrations(presets);
|
|
723
|
+
}
|
|
724
|
+
const defaultOptions = getDefaultOptions(
|
|
725
|
+
template,
|
|
726
|
+
projectName,
|
|
727
|
+
projectType,
|
|
728
|
+
presets?.bundler,
|
|
729
|
+
integrations,
|
|
730
|
+
inheritedSettings ?? presetsToInheritedSettings(presets)
|
|
731
|
+
);
|
|
732
|
+
const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
|
|
733
|
+
p__namespace.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
|
|
734
|
+
const proceed = await p__namespace.select({
|
|
735
|
+
message: "Proceed with these settings?",
|
|
736
|
+
options: [
|
|
737
|
+
{ value: "continue", label: "Yes, continue" },
|
|
738
|
+
{ value: "customize", label: "No, customize settings" }
|
|
739
|
+
],
|
|
740
|
+
initialValue: "continue"
|
|
741
|
+
});
|
|
742
|
+
if (p__namespace.isCancel(proceed)) {
|
|
743
|
+
p__namespace.cancel("Operation cancelled.");
|
|
744
|
+
process.exit(0);
|
|
745
|
+
}
|
|
746
|
+
if (proceed === "continue") {
|
|
747
|
+
return defaultOptions;
|
|
748
|
+
}
|
|
749
|
+
return promptForCustomization(
|
|
750
|
+
template,
|
|
751
|
+
projectName,
|
|
752
|
+
projectType,
|
|
753
|
+
integrations,
|
|
754
|
+
inheritedSettings,
|
|
755
|
+
presets
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async function detectCurrentConfig(root, isMonorepo = true) {
|
|
760
|
+
let name = root.split(/[/\\]/).pop() ?? "workspace";
|
|
761
|
+
let packageManager = "pnpm";
|
|
762
|
+
try {
|
|
763
|
+
const pkgPath = path.join(root, "package.json");
|
|
764
|
+
const content = await promises.readFile(pkgPath, "utf-8");
|
|
765
|
+
const pkgJson = JSON.parse(content);
|
|
766
|
+
if (pkgJson.name) {
|
|
767
|
+
name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
|
|
768
|
+
}
|
|
769
|
+
if (pkgJson.packageManager) {
|
|
770
|
+
packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
|
|
771
|
+
}
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
const tooling = await index.detectTooling(root);
|
|
775
|
+
const configStrategy = isMonorepo ? void 0 : await detectStandaloneConfigStrategy(root);
|
|
776
|
+
return {
|
|
777
|
+
name,
|
|
778
|
+
linter: tooling.linter ?? "oxlint",
|
|
779
|
+
formatter: tooling.formatter ?? "prettier",
|
|
780
|
+
packageManager,
|
|
781
|
+
isMonorepo,
|
|
782
|
+
configStrategy
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
async function detectStandaloneConfigStrategy(root) {
|
|
786
|
+
const hasStealthConfig = await Promise.all([
|
|
787
|
+
fileExists$1(path.join(root, ".config/tsconfig.app.json")),
|
|
788
|
+
fileExists$1(path.join(root, ".config/tsconfig.node.json")),
|
|
789
|
+
fileExists$1(path.join(root, ".config/prettier.json")),
|
|
790
|
+
fileExists$1(path.join(root, ".config/oxlint.json"))
|
|
791
|
+
]).then((matches) => matches.some(Boolean));
|
|
792
|
+
return hasStealthConfig ? "stealth" : "root";
|
|
793
|
+
}
|
|
794
|
+
async function generateExpectedFiles(config) {
|
|
795
|
+
const { name, linter, formatter, packageManager, isMonorepo, configStrategy } = config;
|
|
796
|
+
const versions = linter === "biome" || formatter === "biome" ? await index.resolveMonorepoRootPackageVersions({ linter, formatter }) : {};
|
|
797
|
+
const aiFilesMap = {};
|
|
798
|
+
index.generateAiFiles(aiFilesMap, {
|
|
799
|
+
name,
|
|
800
|
+
packageManager,
|
|
801
|
+
linter,
|
|
802
|
+
formatter,
|
|
803
|
+
isMonorepo,
|
|
804
|
+
configStrategy,
|
|
805
|
+
platforms: index.ALL_AI_PLATFORMS
|
|
806
|
+
});
|
|
807
|
+
const vscodeFiles = {};
|
|
808
|
+
index.generateVscodeFiles(vscodeFiles, linter, formatter);
|
|
809
|
+
const configPackages = {};
|
|
810
|
+
if (isMonorepo) {
|
|
811
|
+
index.generateTypescriptConfigPackage(configPackages);
|
|
812
|
+
if (linter === "oxlint") {
|
|
813
|
+
index.generateOxlintConfigPackage(configPackages);
|
|
814
|
+
} else if (linter === "eslint") {
|
|
815
|
+
index.generateEslintConfigPackage(configPackages);
|
|
816
|
+
}
|
|
817
|
+
if (formatter === "oxfmt") {
|
|
818
|
+
index.generateOxfmtConfigPackage(configPackages);
|
|
819
|
+
} else if (formatter === "prettier") {
|
|
820
|
+
index.generatePrettierConfigPackage(configPackages);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const workspaceConfig = {};
|
|
824
|
+
const rootConfig = {};
|
|
825
|
+
rootConfig[".gitignore"] = index.generateGitignore(isMonorepo ? "workspace-root" : "standalone");
|
|
826
|
+
rootConfig[".gitattributes"] = {
|
|
827
|
+
type: "text",
|
|
828
|
+
content: `* text=auto eol=lf
|
|
829
|
+
*.{cmd,[cC][mM][dD]} text eol=crlf
|
|
830
|
+
*.{bat,[bB][aA][tT]} text eol=crlf
|
|
831
|
+
`
|
|
832
|
+
};
|
|
833
|
+
if (linter === "biome" || formatter === "biome") {
|
|
834
|
+
const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
|
|
835
|
+
const biomeConfig = {
|
|
836
|
+
$schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
|
|
837
|
+
vcs: {
|
|
838
|
+
enabled: true,
|
|
839
|
+
clientKind: "git",
|
|
840
|
+
useIgnoreFile: true
|
|
841
|
+
},
|
|
842
|
+
linter: {
|
|
843
|
+
enabled: linter === "biome",
|
|
844
|
+
rules: {
|
|
845
|
+
recommended: true
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
formatter: {
|
|
849
|
+
enabled: formatter === "biome"
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
rootConfig["biome.json"] = {
|
|
853
|
+
type: "text",
|
|
854
|
+
content: JSON.stringify(biomeConfig, null, 2)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
"ai-files": aiFilesMap,
|
|
859
|
+
vscode: vscodeFiles,
|
|
860
|
+
"config-packages": configPackages,
|
|
861
|
+
"workspace-config": workspaceConfig,
|
|
862
|
+
"root-config": rootConfig
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
async function fileExists$1(path) {
|
|
866
|
+
try {
|
|
867
|
+
await promises.access(path, fs.constants.F_OK);
|
|
868
|
+
return true;
|
|
869
|
+
} catch {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async function compareWithDisk(expected, root) {
|
|
874
|
+
const categoryLabels = {
|
|
875
|
+
"ai-files": "AI Files",
|
|
876
|
+
vscode: "VS Code",
|
|
877
|
+
"config-packages": "Config Packages",
|
|
878
|
+
"workspace-config": "Workspace Config",
|
|
879
|
+
"root-config": "Root Config"
|
|
880
|
+
};
|
|
881
|
+
const categories = [];
|
|
882
|
+
for (const [category, files] of Object.entries(expected)) {
|
|
883
|
+
const changes = [];
|
|
884
|
+
for (const [filePath, file] of Object.entries(files)) {
|
|
885
|
+
if (file.type !== "text") continue;
|
|
886
|
+
const fullPath = path.join(root, filePath);
|
|
887
|
+
const newContent = file.content;
|
|
888
|
+
if (await fileExists$1(fullPath)) {
|
|
889
|
+
const currentContent = await promises.readFile(fullPath, "utf-8");
|
|
890
|
+
if (currentContent === newContent) {
|
|
891
|
+
changes.push({
|
|
892
|
+
path: filePath,
|
|
893
|
+
status: "unchanged",
|
|
894
|
+
currentContent,
|
|
895
|
+
newContent
|
|
896
|
+
});
|
|
897
|
+
} else {
|
|
898
|
+
changes.push({
|
|
899
|
+
path: filePath,
|
|
900
|
+
status: "modified",
|
|
901
|
+
currentContent,
|
|
902
|
+
newContent
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
changes.push({
|
|
907
|
+
path: filePath,
|
|
908
|
+
status: "added",
|
|
909
|
+
newContent
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (changes.length === 0) continue;
|
|
914
|
+
const hasUserModifications = changes.some((c) => c.status === "modified");
|
|
915
|
+
categories.push({
|
|
916
|
+
category,
|
|
917
|
+
label: categoryLabels[category],
|
|
918
|
+
changes,
|
|
919
|
+
hasUserModifications
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
return categories;
|
|
923
|
+
}
|
|
924
|
+
async function getWorkspaceConfigUpdates(root) {
|
|
925
|
+
const workspacePath = path.join(root, "pnpm-workspace.yaml");
|
|
926
|
+
const changes = [];
|
|
927
|
+
let currentContent = "";
|
|
928
|
+
let exists = false;
|
|
929
|
+
try {
|
|
930
|
+
currentContent = await promises.readFile(workspacePath, "utf-8");
|
|
931
|
+
exists = true;
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
if (!exists) {
|
|
935
|
+
const newContent = `manage-package-manager-versions: true
|
|
936
|
+
|
|
937
|
+
packages:
|
|
938
|
+
- ".config/*"
|
|
939
|
+
- "apps/*"
|
|
940
|
+
- "packages/*"
|
|
941
|
+
|
|
942
|
+
onlyBuiltDependencies:
|
|
943
|
+
- esbuild
|
|
944
|
+
`;
|
|
945
|
+
changes.push({
|
|
946
|
+
path: "pnpm-workspace.yaml",
|
|
947
|
+
status: "added",
|
|
948
|
+
newContent
|
|
949
|
+
});
|
|
950
|
+
return changes;
|
|
951
|
+
}
|
|
952
|
+
let updatedContent = currentContent;
|
|
953
|
+
let needsUpdate = false;
|
|
954
|
+
if (!currentContent.includes("manage-package-manager-versions")) {
|
|
955
|
+
updatedContent = `manage-package-manager-versions: true
|
|
956
|
+
|
|
957
|
+
${updatedContent}`;
|
|
958
|
+
needsUpdate = true;
|
|
959
|
+
}
|
|
960
|
+
if (!currentContent.includes("onlyBuiltDependencies")) {
|
|
961
|
+
updatedContent = `${updatedContent.trimEnd()}
|
|
962
|
+
|
|
963
|
+
onlyBuiltDependencies:
|
|
964
|
+
- esbuild
|
|
965
|
+
`;
|
|
966
|
+
needsUpdate = true;
|
|
967
|
+
}
|
|
968
|
+
if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
|
|
969
|
+
const lines = updatedContent.split("\n");
|
|
970
|
+
const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
|
|
971
|
+
if (packagesIndex !== -1) {
|
|
972
|
+
lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
|
|
973
|
+
updatedContent = lines.join("\n");
|
|
974
|
+
needsUpdate = true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (needsUpdate) {
|
|
978
|
+
changes.push({
|
|
979
|
+
path: "pnpm-workspace.yaml",
|
|
980
|
+
status: "modified",
|
|
981
|
+
currentContent,
|
|
982
|
+
newContent: updatedContent
|
|
983
|
+
});
|
|
984
|
+
} else {
|
|
985
|
+
changes.push({
|
|
986
|
+
path: "pnpm-workspace.yaml",
|
|
987
|
+
status: "unchanged",
|
|
988
|
+
currentContent,
|
|
989
|
+
newContent: currentContent
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
return changes;
|
|
993
|
+
}
|
|
994
|
+
async function applyUpdates(changes, root) {
|
|
995
|
+
for (const change of changes) {
|
|
996
|
+
if (change.status === "unchanged") continue;
|
|
997
|
+
const fullPath = path.join(root, change.path);
|
|
998
|
+
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
999
|
+
await promises.writeFile(fullPath, change.newContent);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
function formatFileChange(change) {
|
|
1003
|
+
const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
|
|
1004
|
+
return ` ${icon} ${change.path}`;
|
|
1005
|
+
}
|
|
1006
|
+
const LINTER_DEPS = {
|
|
1007
|
+
oxlint: "oxlint",
|
|
1008
|
+
eslint: "eslint",
|
|
1009
|
+
biome: "@biomejs/biome"
|
|
1010
|
+
};
|
|
1011
|
+
const FORMATTER_DEPS = {
|
|
1012
|
+
oxfmt: "oxfmt",
|
|
1013
|
+
prettier: "prettier",
|
|
1014
|
+
biome: "@biomejs/biome"
|
|
1015
|
+
};
|
|
1016
|
+
const LINTER_CONFIG_PACKAGES = {
|
|
1017
|
+
oxlint: "@config/oxlint",
|
|
1018
|
+
eslint: "@config/eslint",
|
|
1019
|
+
biome: null
|
|
1020
|
+
// biome uses root biome.json
|
|
1021
|
+
};
|
|
1022
|
+
const FORMATTER_CONFIG_PACKAGES = {
|
|
1023
|
+
oxfmt: "@config/oxfmt",
|
|
1024
|
+
prettier: "@config/prettier",
|
|
1025
|
+
biome: null
|
|
1026
|
+
// biome uses root biome.json
|
|
1027
|
+
};
|
|
1028
|
+
function needsMigration(current, target) {
|
|
1029
|
+
const linterChange = target.linter && target.linter !== current.linter;
|
|
1030
|
+
const formatterChange = target.formatter && target.formatter !== current.formatter;
|
|
1031
|
+
return linterChange || formatterChange || false;
|
|
1032
|
+
}
|
|
1033
|
+
async function getMigrationPlan(current, target, root) {
|
|
1034
|
+
const toLinter = target.linter ?? current.linter;
|
|
1035
|
+
const toFormatter = target.formatter ?? current.formatter;
|
|
1036
|
+
const targetVersions = toLinter === "biome" || toFormatter === "biome" ? await index.resolveMonorepoRootPackageVersions({
|
|
1037
|
+
linter: toLinter,
|
|
1038
|
+
formatter: toFormatter
|
|
1039
|
+
}) : {};
|
|
1040
|
+
const biomeSchemaUrl = toLinter === "biome" || toFormatter === "biome" ? `https://biomejs.dev/schemas/${index.getResolvedPackageVersion(
|
|
1041
|
+
targetVersions,
|
|
1042
|
+
"@biomejs/biome"
|
|
1043
|
+
)}/schema.json` : "";
|
|
1044
|
+
const changes = [];
|
|
1045
|
+
if (toLinter !== current.linter) {
|
|
1046
|
+
if (current.linter !== "biome") {
|
|
1047
|
+
changes.push({
|
|
1048
|
+
type: "remove-dir",
|
|
1049
|
+
path: `.config/${current.linter}`,
|
|
1050
|
+
description: `Remove @config/${current.linter} package`
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
if (toLinter !== "biome") {
|
|
1054
|
+
const files = {};
|
|
1055
|
+
if (toLinter === "oxlint") {
|
|
1056
|
+
index.generateOxlintConfigPackage(files);
|
|
1057
|
+
} else if (toLinter === "eslint") {
|
|
1058
|
+
index.generateEslintConfigPackage(files);
|
|
1059
|
+
}
|
|
1060
|
+
for (const [path, file] of Object.entries(files)) {
|
|
1061
|
+
if (file.type === "text") {
|
|
1062
|
+
changes.push({
|
|
1063
|
+
type: "add-file",
|
|
1064
|
+
path,
|
|
1065
|
+
description: `Add ${path}`,
|
|
1066
|
+
content: file.content
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (toLinter === "biome" && toFormatter === "biome") {
|
|
1072
|
+
changes.push({
|
|
1073
|
+
type: "add-file",
|
|
1074
|
+
path: "biome.json",
|
|
1075
|
+
description: "Add biome.json config",
|
|
1076
|
+
content: JSON.stringify(
|
|
1077
|
+
{
|
|
1078
|
+
$schema: biomeSchemaUrl,
|
|
1079
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1080
|
+
linter: { enabled: true, rules: { recommended: true } },
|
|
1081
|
+
formatter: { enabled: true }
|
|
1082
|
+
},
|
|
1083
|
+
null,
|
|
1084
|
+
2
|
|
1085
|
+
)
|
|
1086
|
+
});
|
|
1087
|
+
} else if (toLinter === "biome" && toFormatter !== "biome") {
|
|
1088
|
+
changes.push({
|
|
1089
|
+
type: "add-file",
|
|
1090
|
+
path: "biome.json",
|
|
1091
|
+
description: "Add biome.json config (linter only)",
|
|
1092
|
+
content: JSON.stringify(
|
|
1093
|
+
{
|
|
1094
|
+
$schema: biomeSchemaUrl,
|
|
1095
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1096
|
+
linter: { enabled: true, rules: { recommended: true } },
|
|
1097
|
+
formatter: { enabled: false }
|
|
1098
|
+
},
|
|
1099
|
+
null,
|
|
1100
|
+
2
|
|
1101
|
+
)
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
|
|
1105
|
+
changes.push({
|
|
1106
|
+
type: "remove-file",
|
|
1107
|
+
path: "biome.json",
|
|
1108
|
+
description: "Remove biome.json"
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (toFormatter !== current.formatter) {
|
|
1113
|
+
const formatterSameAsLinter = current.formatter === current.linter;
|
|
1114
|
+
if (current.formatter !== "biome" && !formatterSameAsLinter) {
|
|
1115
|
+
changes.push({
|
|
1116
|
+
type: "remove-dir",
|
|
1117
|
+
path: `.config/${current.formatter}`,
|
|
1118
|
+
description: `Remove @config/${current.formatter} package`
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
const newFormatterSameAsLinter = toFormatter === toLinter;
|
|
1122
|
+
if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
|
|
1123
|
+
const files = {};
|
|
1124
|
+
if (toFormatter === "oxfmt") {
|
|
1125
|
+
index.generateOxfmtConfigPackage(files);
|
|
1126
|
+
} else if (toFormatter === "prettier") {
|
|
1127
|
+
index.generatePrettierConfigPackage(files);
|
|
1128
|
+
}
|
|
1129
|
+
for (const [path, file] of Object.entries(files)) {
|
|
1130
|
+
if (file.type === "text") {
|
|
1131
|
+
changes.push({
|
|
1132
|
+
type: "add-file",
|
|
1133
|
+
path,
|
|
1134
|
+
description: `Add ${path}`,
|
|
1135
|
+
content: file.content
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (toFormatter === "biome" && toLinter !== "biome") {
|
|
1141
|
+
changes.push({
|
|
1142
|
+
type: "add-file",
|
|
1143
|
+
path: "biome.json",
|
|
1144
|
+
description: "Add biome.json config (formatter only)",
|
|
1145
|
+
content: JSON.stringify(
|
|
1146
|
+
{
|
|
1147
|
+
$schema: biomeSchemaUrl,
|
|
1148
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1149
|
+
linter: { enabled: false },
|
|
1150
|
+
formatter: { enabled: true }
|
|
1151
|
+
},
|
|
1152
|
+
null,
|
|
1153
|
+
2
|
|
1154
|
+
)
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
|
|
1158
|
+
changes.push({
|
|
1159
|
+
type: "remove-file",
|
|
1160
|
+
path: "biome.json",
|
|
1161
|
+
description: "Remove biome.json"
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
changes.push({
|
|
1166
|
+
type: "update-package-json",
|
|
1167
|
+
path: "package.json",
|
|
1168
|
+
description: "Update root package.json (devDependencies, scripts)"
|
|
1169
|
+
});
|
|
1170
|
+
const subPackageUpdates = await getSubPackageUpdates(root, current, toLinter, toFormatter);
|
|
1171
|
+
return {
|
|
1172
|
+
fromLinter: current.linter,
|
|
1173
|
+
toLinter,
|
|
1174
|
+
fromFormatter: current.formatter,
|
|
1175
|
+
toFormatter,
|
|
1176
|
+
changes,
|
|
1177
|
+
subPackageUpdates
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
|
|
1181
|
+
const updates = [];
|
|
1182
|
+
const workspacePath = path.join(root, "pnpm-workspace.yaml");
|
|
1183
|
+
let workspaceContent;
|
|
1184
|
+
try {
|
|
1185
|
+
workspaceContent = await promises.readFile(workspacePath, "utf-8");
|
|
1186
|
+
} catch {
|
|
1187
|
+
return updates;
|
|
1188
|
+
}
|
|
1189
|
+
const packageGlobs = index.parseWorkspaceYamlContent(workspaceContent);
|
|
1190
|
+
for (const glob of packageGlobs) {
|
|
1191
|
+
if (glob.includes(".config")) continue;
|
|
1192
|
+
const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
|
|
1193
|
+
const basePath = path.join(root, baseDir);
|
|
1194
|
+
try {
|
|
1195
|
+
const entries = await promises.readdir(basePath, { withFileTypes: true });
|
|
1196
|
+
for (const entry of entries) {
|
|
1197
|
+
if (!entry.isDirectory()) continue;
|
|
1198
|
+
const pkgJsonPath = path.join(basePath, entry.name, "package.json");
|
|
1199
|
+
try {
|
|
1200
|
+
const content = await promises.readFile(pkgJsonPath, "utf-8");
|
|
1201
|
+
const pkg = JSON.parse(content);
|
|
1202
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1203
|
+
const remove = [];
|
|
1204
|
+
const add = [];
|
|
1205
|
+
const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
|
|
1206
|
+
const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
|
|
1207
|
+
if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
|
|
1208
|
+
remove.push(oldLinterPkg);
|
|
1209
|
+
}
|
|
1210
|
+
if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
|
|
1211
|
+
add.push(newLinterPkg);
|
|
1212
|
+
}
|
|
1213
|
+
if (current.formatter !== current.linter) {
|
|
1214
|
+
const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
|
|
1215
|
+
const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
|
|
1216
|
+
if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
|
|
1217
|
+
remove.push(oldFormatterPkg);
|
|
1218
|
+
}
|
|
1219
|
+
if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
|
|
1220
|
+
add.push(newFormatterPkg);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (remove.length > 0 || add.length > 0) {
|
|
1224
|
+
updates.push({
|
|
1225
|
+
path: path.join(baseDir, entry.name, "package.json"),
|
|
1226
|
+
remove,
|
|
1227
|
+
add
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return updates;
|
|
1237
|
+
}
|
|
1238
|
+
async function applyMigration(plan, root) {
|
|
1239
|
+
for (const change of plan.changes) {
|
|
1240
|
+
if (change.type === "remove-dir") {
|
|
1241
|
+
const fullPath = path.join(root, change.path);
|
|
1242
|
+
try {
|
|
1243
|
+
await promises.rm(fullPath, { recursive: true });
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
for (const change of plan.changes) {
|
|
1249
|
+
if (change.type === "remove-file") {
|
|
1250
|
+
const fullPath = path.join(root, change.path);
|
|
1251
|
+
try {
|
|
1252
|
+
await promises.rm(fullPath);
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
for (const change of plan.changes) {
|
|
1258
|
+
if (change.type === "add-file" && change.content) {
|
|
1259
|
+
const fullPath = path.join(root, change.path);
|
|
1260
|
+
await promises.mkdir(path.dirname(fullPath), { recursive: true });
|
|
1261
|
+
await promises.writeFile(fullPath, change.content);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
await updateRootPackageJson(root, plan);
|
|
1265
|
+
for (const update of plan.subPackageUpdates) {
|
|
1266
|
+
await updateSubPackageJson(root, update);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function updateRootPackageJson(root, plan) {
|
|
1270
|
+
const pkgPath = path.join(root, "package.json");
|
|
1271
|
+
const content = await promises.readFile(pkgPath, "utf-8");
|
|
1272
|
+
const pkg = JSON.parse(content);
|
|
1273
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1274
|
+
const oldLinterDep = LINTER_DEPS[plan.fromLinter];
|
|
1275
|
+
delete devDeps[oldLinterDep];
|
|
1276
|
+
if (plan.fromFormatter !== plan.fromLinter) {
|
|
1277
|
+
const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
|
|
1278
|
+
delete devDeps[oldFormatterDep];
|
|
1279
|
+
}
|
|
1280
|
+
const resolvedVersions = await index.resolveMonorepoRootPackageVersions({
|
|
1281
|
+
linter: plan.toLinter,
|
|
1282
|
+
formatter: plan.toFormatter
|
|
1283
|
+
});
|
|
1284
|
+
const newLinterDep = LINTER_DEPS[plan.toLinter];
|
|
1285
|
+
devDeps[newLinterDep] = index.formatResolvedPackageVersion(resolvedVersions, newLinterDep);
|
|
1286
|
+
if (plan.toFormatter !== plan.toLinter) {
|
|
1287
|
+
const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
|
|
1288
|
+
devDeps[newFormatterDep] = index.formatResolvedPackageVersion(resolvedVersions, newFormatterDep);
|
|
1289
|
+
}
|
|
1290
|
+
pkg.devDependencies = Object.fromEntries(
|
|
1291
|
+
Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
|
|
1292
|
+
);
|
|
1293
|
+
const scripts = pkg.scripts ?? {};
|
|
1294
|
+
if (plan.toLinter === "oxlint") {
|
|
1295
|
+
scripts.lint = "oxlint .";
|
|
1296
|
+
} else if (plan.toLinter === "eslint") {
|
|
1297
|
+
scripts.lint = "eslint .";
|
|
1298
|
+
} else if (plan.toLinter === "biome") {
|
|
1299
|
+
scripts.lint = "biome check .";
|
|
1300
|
+
}
|
|
1301
|
+
if (plan.toFormatter === "oxfmt") {
|
|
1302
|
+
scripts.format = "oxfmt .";
|
|
1303
|
+
} else if (plan.toFormatter === "prettier") {
|
|
1304
|
+
scripts.format = "prettier --write .";
|
|
1305
|
+
} else if (plan.toFormatter === "biome") {
|
|
1306
|
+
scripts.format = "biome format . --write";
|
|
1307
|
+
}
|
|
1308
|
+
pkg.scripts = scripts;
|
|
1309
|
+
await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1310
|
+
}
|
|
1311
|
+
async function updateSubPackageJson(root, update) {
|
|
1312
|
+
const pkgPath = path.join(root, update.path);
|
|
1313
|
+
const content = await promises.readFile(pkgPath, "utf-8");
|
|
1314
|
+
const pkg = JSON.parse(content);
|
|
1315
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1316
|
+
for (const dep of update.remove) {
|
|
1317
|
+
delete devDeps[dep];
|
|
1318
|
+
}
|
|
1319
|
+
for (const dep of update.add) {
|
|
1320
|
+
devDeps[dep] = "workspace:*";
|
|
1321
|
+
}
|
|
1322
|
+
pkg.devDependencies = Object.fromEntries(
|
|
1323
|
+
Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
|
|
1324
|
+
);
|
|
1325
|
+
await promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1326
|
+
}
|
|
1327
|
+
function formatMigrationChange(change) {
|
|
1328
|
+
const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
|
|
1329
|
+
return ` ${icon} ${change.description}`;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
async function checkAnyExists(paths) {
|
|
1333
|
+
for (const path of paths) {
|
|
1334
|
+
try {
|
|
1335
|
+
await promises.access(path, promises.constants.F_OK);
|
|
1336
|
+
return true;
|
|
1337
|
+
} catch {
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
async function validateWorkspace(monorepoRoot) {
|
|
1343
|
+
const errors = [];
|
|
1344
|
+
const tsConfigPath = path.join(monorepoRoot, ".config/typescript/package.json");
|
|
1345
|
+
try {
|
|
1346
|
+
await promises.access(tsConfigPath, promises.constants.F_OK);
|
|
1347
|
+
} catch {
|
|
1348
|
+
errors.push("Missing .config/typescript package");
|
|
1349
|
+
}
|
|
1350
|
+
const linterPaths = [
|
|
1351
|
+
path.join(monorepoRoot, ".config/oxlint/package.json"),
|
|
1352
|
+
path.join(monorepoRoot, ".config/eslint/package.json"),
|
|
1353
|
+
path.join(monorepoRoot, "eslint.config.js"),
|
|
1354
|
+
path.join(monorepoRoot, "biome.json")
|
|
1355
|
+
];
|
|
1356
|
+
const hasLinter = await checkAnyExists(linterPaths);
|
|
1357
|
+
if (!hasLinter) {
|
|
1358
|
+
errors.push(
|
|
1359
|
+
"Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
const formatterPaths = [
|
|
1363
|
+
path.join(monorepoRoot, ".config/oxfmt/package.json"),
|
|
1364
|
+
path.join(monorepoRoot, ".config/prettier/package.json"),
|
|
1365
|
+
path.join(monorepoRoot, ".prettierrc.json"),
|
|
1366
|
+
path.join(monorepoRoot, "biome.json")
|
|
1367
|
+
];
|
|
1368
|
+
const hasFormatter = await checkAnyExists(formatterPaths);
|
|
1369
|
+
if (!hasFormatter) {
|
|
1370
|
+
errors.push(
|
|
1371
|
+
"Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
return { valid: errors.length === 0, errors };
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
|
|
1378
|
+
const pkg = require$1("../package.json");
|
|
1379
|
+
const META_OPTIONS = [
|
|
1380
|
+
"clearConfig",
|
|
1381
|
+
"configPath",
|
|
1382
|
+
"check",
|
|
1383
|
+
"fix",
|
|
1384
|
+
"update",
|
|
1385
|
+
"yes",
|
|
1386
|
+
"workspace",
|
|
1387
|
+
"path",
|
|
1388
|
+
"dir"
|
|
1389
|
+
];
|
|
1390
|
+
function hasConfigOptions(options) {
|
|
1391
|
+
return Object.keys(options).some(
|
|
1392
|
+
(key) => !META_OPTIONS.includes(key)
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
async function fileExists(path) {
|
|
1396
|
+
try {
|
|
1397
|
+
await promises$1.access(path, node_fs.constants.F_OK);
|
|
1398
|
+
return true;
|
|
1399
|
+
} catch {
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
async function promptForAiPlatforms(isNonInteractive) {
|
|
1404
|
+
const savedPlatforms = getAiPlatforms();
|
|
1405
|
+
if (isNonInteractive) {
|
|
1406
|
+
return savedPlatforms ?? index.ALL_AI_PLATFORMS;
|
|
1407
|
+
}
|
|
1408
|
+
if (savedPlatforms && savedPlatforms.length > 0) {
|
|
1409
|
+
const savedLabels = savedPlatforms.map((plat) => index.AI_PLATFORM_LABELS[plat]).join(", ");
|
|
1410
|
+
const useDefault = await p__namespace.confirm({
|
|
1411
|
+
message: `Add AI rules? ${color__default.dim(`(${savedLabels})`)}`,
|
|
1412
|
+
initialValue: true
|
|
1413
|
+
});
|
|
1414
|
+
if (p__namespace.isCancel(useDefault)) {
|
|
1415
|
+
return [];
|
|
1416
|
+
}
|
|
1417
|
+
if (useDefault) {
|
|
1418
|
+
return savedPlatforms;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const selected = await p__namespace.multiselect({
|
|
1422
|
+
message: "Add AI rules?",
|
|
1423
|
+
options: index.ALL_AI_PLATFORMS.map((platform) => ({
|
|
1424
|
+
value: platform,
|
|
1425
|
+
label: index.AI_PLATFORM_LABELS[platform],
|
|
1426
|
+
hint: index.AI_PLATFORM_HINTS[platform]
|
|
1427
|
+
})),
|
|
1428
|
+
initialValues: ["agents"],
|
|
1429
|
+
required: false
|
|
1430
|
+
});
|
|
1431
|
+
if (p__namespace.isCancel(selected)) {
|
|
1432
|
+
return [];
|
|
1433
|
+
}
|
|
1434
|
+
const platforms = selected;
|
|
1435
|
+
if (platforms.length === 0) {
|
|
1436
|
+
return [];
|
|
1437
|
+
}
|
|
1438
|
+
return platforms;
|
|
1439
|
+
}
|
|
1440
|
+
async function writeGeneratedFiles(basePath, files) {
|
|
1441
|
+
const filePaths = Object.keys(files).sort();
|
|
1442
|
+
for (const filePath of filePaths) {
|
|
1443
|
+
const fullFilePath = node_path.join(basePath, filePath);
|
|
1444
|
+
await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
|
|
1445
|
+
const file = files[filePath];
|
|
1446
|
+
if (file.type === "text") {
|
|
1447
|
+
await promises$1.writeFile(fullFilePath, file.content);
|
|
1448
|
+
} else {
|
|
1449
|
+
const response = await undici.fetch(file.url);
|
|
1450
|
+
await promises$1.writeFile(fullFilePath, response.body);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function calculateWorkspaceRoot(packagePath) {
|
|
1455
|
+
const segments = packagePath.split(/[/\\]/).filter(Boolean);
|
|
1456
|
+
return segments.map(() => "..").join("/");
|
|
1457
|
+
}
|
|
1458
|
+
async function detectMonorepoRoot() {
|
|
1459
|
+
let currentDir = node_process.cwd();
|
|
1460
|
+
const root = node_path.resolve("/");
|
|
1461
|
+
while (currentDir !== root) {
|
|
1462
|
+
const workspaceFile = node_path.join(currentDir, "pnpm-workspace.yaml");
|
|
1463
|
+
try {
|
|
1464
|
+
await promises$1.access(workspaceFile, node_fs.constants.F_OK);
|
|
1465
|
+
const content = await promises$1.readFile(workspaceFile, "utf-8");
|
|
1466
|
+
if (content.includes("packages:")) {
|
|
1467
|
+
return currentDir;
|
|
1468
|
+
}
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
currentDir = node_path.dirname(currentDir);
|
|
1472
|
+
}
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
async function detectPackageRoot() {
|
|
1476
|
+
let currentDir = node_process.cwd();
|
|
1477
|
+
const root = node_path.resolve("/");
|
|
1478
|
+
while (currentDir !== root) {
|
|
1479
|
+
if (await fileExists(node_path.join(currentDir, "package.json"))) {
|
|
1480
|
+
return currentDir;
|
|
1481
|
+
}
|
|
1482
|
+
currentDir = node_path.dirname(currentDir);
|
|
1483
|
+
}
|
|
1484
|
+
return await fileExists(node_path.join(root, "package.json")) ? root : null;
|
|
1485
|
+
}
|
|
1486
|
+
async function parseWorkspaceDirectories(monorepoRoot) {
|
|
1487
|
+
try {
|
|
1488
|
+
const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
|
|
1489
|
+
const content = await promises$1.readFile(workspaceFile, "utf-8");
|
|
1490
|
+
return index.parseWorkspaceYamlContent(content);
|
|
1491
|
+
} catch {
|
|
1492
|
+
return [];
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
async function detectWorkspaceSettings(monorepoRoot) {
|
|
1496
|
+
try {
|
|
1497
|
+
const tooling = await index.detectTooling(monorepoRoot);
|
|
1498
|
+
const pkgPath = node_path.join(monorepoRoot, "package.json");
|
|
1499
|
+
const content = await promises$1.readFile(pkgPath, "utf-8");
|
|
1500
|
+
const pkgJson = JSON.parse(content);
|
|
1501
|
+
const packageManager = index.parsePackageManager(pkgJson.packageManager);
|
|
1502
|
+
const engine = index.parseEngine(pkgJson.engines);
|
|
1503
|
+
let pnpmManageVersions;
|
|
1504
|
+
try {
|
|
1505
|
+
const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
|
|
1506
|
+
const workspaceContent = await promises$1.readFile(workspaceFile, "utf-8");
|
|
1507
|
+
pnpmManageVersions = workspaceContent.includes("manage-package-manager-versions: true");
|
|
1508
|
+
} catch {
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
linter: tooling.linter,
|
|
1512
|
+
formatter: tooling.formatter,
|
|
1513
|
+
packageManager,
|
|
1514
|
+
engine,
|
|
1515
|
+
pnpmManageVersions
|
|
1516
|
+
};
|
|
1517
|
+
} catch {
|
|
1518
|
+
return {};
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
async function detectExistingConfigs(monorepoRoot) {
|
|
1522
|
+
const configs = {};
|
|
1523
|
+
const eslintPath = node_path.join(monorepoRoot, "eslint.config.js");
|
|
1524
|
+
if (await fileExists(eslintPath)) {
|
|
1525
|
+
configs.linter = "eslint";
|
|
1526
|
+
configs.eslintConfigPath = eslintPath;
|
|
1527
|
+
}
|
|
1528
|
+
const prettierPath = node_path.join(monorepoRoot, ".prettierrc.json");
|
|
1529
|
+
if (await fileExists(prettierPath)) {
|
|
1530
|
+
configs.formatter = "prettier";
|
|
1531
|
+
configs.prettierConfigPath = prettierPath;
|
|
1532
|
+
}
|
|
1533
|
+
const biomePath = node_path.join(monorepoRoot, "biome.json");
|
|
1534
|
+
if (await fileExists(biomePath)) {
|
|
1535
|
+
configs.biomeConfigPath = biomePath;
|
|
1536
|
+
if (!configs.linter) configs.linter = "biome";
|
|
1537
|
+
if (!configs.formatter) configs.formatter = "biome";
|
|
1538
|
+
}
|
|
1539
|
+
return configs;
|
|
1540
|
+
}
|
|
1541
|
+
async function getMonorepoScope(monorepoRoot) {
|
|
1542
|
+
try {
|
|
1543
|
+
const pkgPath = node_path.join(monorepoRoot, "package.json");
|
|
1544
|
+
const content = await promises$1.readFile(pkgPath, "utf-8");
|
|
1545
|
+
const pkgJson = JSON.parse(content);
|
|
1546
|
+
if (pkgJson.name) {
|
|
1547
|
+
return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
|
|
1548
|
+
}
|
|
1549
|
+
} catch {
|
|
1550
|
+
}
|
|
1551
|
+
return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
|
|
1552
|
+
}
|
|
1553
|
+
async function getWorkspacePackages(monorepoRoot) {
|
|
1554
|
+
const packagesDir = node_path.join(monorepoRoot, "packages");
|
|
1555
|
+
try {
|
|
1556
|
+
const { readdir } = await import('fs/promises');
|
|
1557
|
+
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
1558
|
+
const names = [];
|
|
1559
|
+
for (const entry of entries) {
|
|
1560
|
+
if (!entry.isDirectory()) continue;
|
|
1561
|
+
try {
|
|
1562
|
+
const content = await promises$1.readFile(
|
|
1563
|
+
node_path.join(packagesDir, entry.name, "package.json"),
|
|
1564
|
+
"utf-8"
|
|
1565
|
+
);
|
|
1566
|
+
const pkg2 = JSON.parse(content);
|
|
1567
|
+
if (pkg2.name) names.push(pkg2.name);
|
|
1568
|
+
} catch {
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return names;
|
|
1572
|
+
} catch {
|
|
1573
|
+
return [];
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
async function ensureConfigInWorkspace(monorepoRoot) {
|
|
1577
|
+
const workspacePath = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
|
|
1578
|
+
let content;
|
|
1579
|
+
try {
|
|
1580
|
+
content = await promises$1.readFile(workspacePath, "utf-8");
|
|
1581
|
+
} catch {
|
|
1582
|
+
content = `packages:
|
|
1583
|
+
- ".config/*"
|
|
1584
|
+
- "packages/*"
|
|
1585
|
+
`;
|
|
1586
|
+
await promises$1.writeFile(workspacePath, content);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (content.includes(".config/*") || content.includes('".config/*"')) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const lines = content.split("\n");
|
|
1593
|
+
const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
|
|
1594
|
+
if (packagesIndex === -1) {
|
|
1595
|
+
content = `packages:
|
|
1596
|
+
- ".config/*"
|
|
1597
|
+
${content}`;
|
|
1598
|
+
} else {
|
|
1599
|
+
lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
|
|
1600
|
+
content = lines.join("\n");
|
|
1601
|
+
}
|
|
1602
|
+
await promises$1.writeFile(workspacePath, content);
|
|
1603
|
+
}
|
|
1604
|
+
async function migrateEslintConfig(monorepoRoot, files) {
|
|
1605
|
+
const configBasePath = ".config/eslint";
|
|
1606
|
+
const existingConfigPath = node_path.join(monorepoRoot, "eslint.config.js");
|
|
1607
|
+
let existingContent;
|
|
1608
|
+
try {
|
|
1609
|
+
existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
|
|
1610
|
+
} catch {
|
|
1611
|
+
index.generateEslintConfigPackage(files);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
files[`${configBasePath}/package.json`] = {
|
|
1615
|
+
type: "text",
|
|
1616
|
+
content: JSON.stringify(
|
|
1617
|
+
{
|
|
1618
|
+
name: "@config/eslint",
|
|
1619
|
+
version: "0.1.0",
|
|
1620
|
+
private: true,
|
|
1621
|
+
type: "module",
|
|
1622
|
+
exports: {
|
|
1623
|
+
"./base": "./base.js",
|
|
1624
|
+
"./react": "./react.js"
|
|
1625
|
+
}
|
|
1626
|
+
},
|
|
1627
|
+
null,
|
|
1628
|
+
2
|
|
1629
|
+
)
|
|
1630
|
+
};
|
|
1631
|
+
files[`${configBasePath}/README.md`] = {
|
|
1632
|
+
type: "text",
|
|
1633
|
+
content: `# \`@config/eslint\`
|
|
1634
|
+
|
|
1635
|
+
Shared ESLint configurations.
|
|
1636
|
+
|
|
1637
|
+
## Usage
|
|
1638
|
+
|
|
1639
|
+
In your package's \`eslint.config.js\`:
|
|
1640
|
+
|
|
1641
|
+
\`\`\`js
|
|
1642
|
+
import base from "@config/eslint/base";
|
|
1643
|
+
|
|
1644
|
+
export default [...base];
|
|
1645
|
+
\`\`\`
|
|
1646
|
+
|
|
1647
|
+
## Available Configs
|
|
1648
|
+
|
|
1649
|
+
- \`base\` - Base ESLint rules (migrated from root)
|
|
1650
|
+
- \`react\` - React-specific rules
|
|
1651
|
+
`
|
|
1652
|
+
};
|
|
1653
|
+
files[`${configBasePath}/base.js`] = {
|
|
1654
|
+
type: "text",
|
|
1655
|
+
content: existingContent
|
|
1656
|
+
};
|
|
1657
|
+
files[`${configBasePath}/react.js`] = {
|
|
1658
|
+
type: "text",
|
|
1659
|
+
content: `import react from "eslint-plugin-react";
|
|
1660
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
1661
|
+
|
|
1662
|
+
export default [
|
|
1663
|
+
{
|
|
1664
|
+
plugins: {
|
|
1665
|
+
react,
|
|
1666
|
+
"react-hooks": reactHooks,
|
|
1667
|
+
},
|
|
1668
|
+
rules: {
|
|
1669
|
+
...react.configs.recommended.rules,
|
|
1670
|
+
...reactHooks.configs.recommended.rules,
|
|
1671
|
+
"react/react-in-jsx-scope": "off",
|
|
1672
|
+
},
|
|
1673
|
+
settings: {
|
|
1674
|
+
react: {
|
|
1675
|
+
version: "detect",
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1679
|
+
];
|
|
1680
|
+
`
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
async function migratePrettierConfig(monorepoRoot, files) {
|
|
1684
|
+
const configBasePath = ".config/prettier";
|
|
1685
|
+
const existingConfigPath = node_path.join(monorepoRoot, ".prettierrc.json");
|
|
1686
|
+
let existingContent;
|
|
1687
|
+
try {
|
|
1688
|
+
existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
|
|
1689
|
+
} catch {
|
|
1690
|
+
index.generatePrettierConfigPackage(files);
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
files[`${configBasePath}/package.json`] = {
|
|
1694
|
+
type: "text",
|
|
1695
|
+
content: JSON.stringify(
|
|
1696
|
+
{
|
|
1697
|
+
name: "@config/prettier",
|
|
1698
|
+
version: "0.1.0",
|
|
1699
|
+
private: true,
|
|
1700
|
+
exports: {
|
|
1701
|
+
"./base": "./base.json"
|
|
1702
|
+
}
|
|
1703
|
+
},
|
|
1704
|
+
null,
|
|
1705
|
+
2
|
|
1706
|
+
)
|
|
1707
|
+
};
|
|
1708
|
+
files[`${configBasePath}/README.md`] = {
|
|
1709
|
+
type: "text",
|
|
1710
|
+
content: `# \`@config/prettier\`
|
|
1711
|
+
|
|
1712
|
+
Shared Prettier configurations.
|
|
1713
|
+
|
|
1714
|
+
## Usage
|
|
1715
|
+
|
|
1716
|
+
In your package's \`.prettierrc\`:
|
|
1717
|
+
|
|
1718
|
+
\`\`\`json
|
|
1719
|
+
"@config/prettier/base"
|
|
1720
|
+
\`\`\`
|
|
1721
|
+
|
|
1722
|
+
Or in \`package.json\`:
|
|
1723
|
+
|
|
1724
|
+
\`\`\`json
|
|
1725
|
+
{
|
|
1726
|
+
"prettier": "@config/prettier/base"
|
|
1727
|
+
}
|
|
1728
|
+
\`\`\`
|
|
1729
|
+
|
|
1730
|
+
## Available Configs
|
|
1731
|
+
|
|
1732
|
+
- \`base\` - Base Prettier rules (migrated from root)
|
|
1733
|
+
`
|
|
1734
|
+
};
|
|
1735
|
+
files[`${configBasePath}/base.json`] = {
|
|
1736
|
+
type: "text",
|
|
1737
|
+
content: existingContent
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
|
|
1741
|
+
const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
|
|
1742
|
+
const defaultDirectories = ["apps", "packages"];
|
|
1743
|
+
const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
|
|
1744
|
+
const packageType = await promptForInitialPackage();
|
|
1745
|
+
if (packageType === "skip") {
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
const defaultDir = packageType === "app" ? "apps" : "packages";
|
|
1749
|
+
const packageNameInput = await p__namespace.text({
|
|
1750
|
+
message: "Package name?",
|
|
1751
|
+
initialValue: `@${scope}/`,
|
|
1752
|
+
validate: (value) => {
|
|
1753
|
+
const validationError = index.validatePackageName(value);
|
|
1754
|
+
if (validationError) return validationError;
|
|
1755
|
+
const dirName = value.includes("/") ? value.split("/").pop() : value;
|
|
1756
|
+
if (!dirName) return "Package name is required";
|
|
1757
|
+
if (!hasCustomDirectories) {
|
|
1758
|
+
const targetPath = node_path.join(monorepoRoot, defaultDir, dirName);
|
|
1759
|
+
try {
|
|
1760
|
+
const { statSync } = require$1("fs");
|
|
1761
|
+
statSync(targetPath);
|
|
1762
|
+
return `Directory ${defaultDir}/${dirName} already exists`;
|
|
1763
|
+
} catch {
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
if (p__namespace.isCancel(packageNameInput)) {
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
const scopedName = packageNameInput;
|
|
1772
|
+
const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
|
|
1773
|
+
const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
|
|
1774
|
+
let targetDir = defaultDir;
|
|
1775
|
+
if (hasCustomDirectories && workspaceDirectories.length > 0) {
|
|
1776
|
+
const dirChoice = await p__namespace.select({
|
|
1777
|
+
message: "Target directory",
|
|
1778
|
+
options: workspaceDirectories.map((dir) => ({
|
|
1779
|
+
value: dir,
|
|
1780
|
+
label: dir
|
|
1781
|
+
})),
|
|
1782
|
+
initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
|
|
1783
|
+
});
|
|
1784
|
+
if (p__namespace.isCancel(dirChoice)) {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
targetDir = dirChoice;
|
|
1788
|
+
const targetPath = node_path.join(monorepoRoot, targetDir, shortName);
|
|
1789
|
+
try {
|
|
1790
|
+
const { statSync } = require$1("fs");
|
|
1791
|
+
statSync(targetPath);
|
|
1792
|
+
p__namespace.log.error(`Directory ${targetDir}/${shortName} already exists`);
|
|
1793
|
+
return false;
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const relativePkgPath = node_path.join(targetDir, shortName);
|
|
1798
|
+
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
1799
|
+
packageOptions.workspaceRoot = workspaceRoot;
|
|
1800
|
+
packageOptions.name = scopedName;
|
|
1801
|
+
packageOptions.packageManager = await index.resolvePackageManager(packageOptions);
|
|
1802
|
+
packageOptions.engine = await index.resolveEngine(packageOptions);
|
|
1803
|
+
packageOptions.versions = await index.resolveProjectPackageVersions(packageOptions);
|
|
1804
|
+
const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
|
|
1805
|
+
if (workspacePackages.length > 0) {
|
|
1806
|
+
const selectedDeps = await p__namespace.multiselect({
|
|
1807
|
+
message: "Add workspace dependencies?",
|
|
1808
|
+
options: workspacePackages.map((name) => ({ value: name, label: name })),
|
|
1809
|
+
required: false
|
|
1810
|
+
});
|
|
1811
|
+
if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
|
|
1812
|
+
packageOptions.workspaceDependencies = selectedDeps;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
const outputPath = node_path.join(monorepoRoot, relativePkgPath);
|
|
1816
|
+
const spinner = p__namespace.spinner();
|
|
1817
|
+
spinner.start("Creating package...");
|
|
1818
|
+
try {
|
|
1819
|
+
const files = index.generate(packageOptions);
|
|
1820
|
+
await writeGeneratedFiles(outputPath, files);
|
|
1821
|
+
spinner.stop(color__default.green.inverse(` \u2713 Package created at ${relativePkgPath}! `));
|
|
1822
|
+
const addAnother = await p__namespace.select({
|
|
1823
|
+
message: "Add another package?",
|
|
1824
|
+
options: [
|
|
1825
|
+
{ value: "no", label: "No, I'm done" },
|
|
1826
|
+
{ value: "yes", label: "Yes, add another" }
|
|
1827
|
+
],
|
|
1828
|
+
initialValue: "no"
|
|
1829
|
+
});
|
|
1830
|
+
return !p__namespace.isCancel(addAnother) && addAnother === "yes";
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
spinner.stop("Failed to create package");
|
|
1833
|
+
p__namespace.log.error(String(error));
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async function promptAndOpenEditor(projectPath) {
|
|
1838
|
+
const savedEditor = getPreferredEditor();
|
|
1839
|
+
let selectedEditor;
|
|
1840
|
+
if (savedEditor && savedEditor !== "skip") {
|
|
1841
|
+
const useDefault = await p__namespace.confirm({
|
|
1842
|
+
message: `Open in editor? ${color__default.dim(`(${editorNames[savedEditor]})`)}`,
|
|
1843
|
+
initialValue: true
|
|
1844
|
+
});
|
|
1845
|
+
if (p__namespace.isCancel(useDefault)) {
|
|
1846
|
+
selectedEditor = void 0;
|
|
1847
|
+
} else if (useDefault) {
|
|
1848
|
+
selectedEditor = savedEditor;
|
|
1849
|
+
} else {
|
|
1850
|
+
selectedEditor = "skip";
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
const openEditor = await p__namespace.select({
|
|
1854
|
+
message: "Open project in editor?",
|
|
1855
|
+
options: [
|
|
1856
|
+
{ value: "skip", label: "Skip" },
|
|
1857
|
+
{ value: "cursor", label: "Cursor" },
|
|
1858
|
+
{ value: "code", label: "VS Code" },
|
|
1859
|
+
{ value: "webstorm", label: "WebStorm" }
|
|
1860
|
+
],
|
|
1861
|
+
initialValue: "skip"
|
|
1862
|
+
});
|
|
1863
|
+
if (!p__namespace.isCancel(openEditor)) {
|
|
1864
|
+
selectedEditor = openEditor;
|
|
1865
|
+
const saveChoice = await p__namespace.confirm({
|
|
1866
|
+
message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
|
|
1867
|
+
initialValue: true
|
|
1868
|
+
});
|
|
1869
|
+
if (!p__namespace.isCancel(saveChoice) && saveChoice) {
|
|
1870
|
+
setPreferredEditor(selectedEditor);
|
|
1871
|
+
if (selectedEditor === "cursor" || selectedEditor === "code") {
|
|
1872
|
+
const reuseChoice = await p__namespace.confirm({
|
|
1873
|
+
message: "Reuse current window when opening projects?",
|
|
1874
|
+
initialValue: false
|
|
1875
|
+
});
|
|
1876
|
+
if (!p__namespace.isCancel(reuseChoice)) {
|
|
1877
|
+
setReuseWindow(reuseChoice);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (selectedEditor && selectedEditor !== "skip") {
|
|
1884
|
+
try {
|
|
1885
|
+
await openInEditor(
|
|
1886
|
+
selectedEditor,
|
|
1887
|
+
projectPath,
|
|
1888
|
+
getReuseWindow()
|
|
1889
|
+
);
|
|
1890
|
+
p__namespace.log.success(`Opening in ${editorNames[selectedEditor]}...`);
|
|
1891
|
+
} catch {
|
|
1892
|
+
p__namespace.log.warn(
|
|
1893
|
+
`Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
async function handleCheckCommand() {
|
|
1899
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1900
|
+
if (!monorepoRoot) {
|
|
1901
|
+
console.log(color__default.red("\u2717") + " Not a monorepo workspace");
|
|
1902
|
+
process.exit(1);
|
|
1903
|
+
}
|
|
1904
|
+
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1905
|
+
if (valid) {
|
|
1906
|
+
console.log(color__default.green("\u2713") + " Valid monorepo workspace");
|
|
1907
|
+
console.log(color__default.dim(` ${monorepoRoot}`));
|
|
1908
|
+
} else {
|
|
1909
|
+
console.log(color__default.red("\u2717") + " Invalid monorepo workspace");
|
|
1910
|
+
console.log(color__default.dim(` ${monorepoRoot}`));
|
|
1911
|
+
for (const error of errors) {
|
|
1912
|
+
console.log(color__default.red(` \u2022 ${error}`));
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
process.exit(valid ? 0 : 1);
|
|
1916
|
+
}
|
|
1917
|
+
async function handleFixCommand(options) {
|
|
1918
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1919
|
+
if (!monorepoRoot) {
|
|
1920
|
+
console.log(color__default.red("\u2717") + " Not a monorepo workspace");
|
|
1921
|
+
console.log(color__default.dim(" Run this command from within a monorepo"));
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1925
|
+
if (valid) {
|
|
1926
|
+
console.log(color__default.green("\u2713") + " Workspace is already valid");
|
|
1927
|
+
console.log(color__default.dim(` ${monorepoRoot}`));
|
|
1928
|
+
process.exit(0);
|
|
1929
|
+
}
|
|
1930
|
+
console.log(color__default.yellow("!") + " Invalid monorepo workspace");
|
|
1931
|
+
for (const error of errors) {
|
|
1932
|
+
console.log(color__default.dim(` \u2022 ${error}`));
|
|
1933
|
+
}
|
|
1934
|
+
console.log();
|
|
1935
|
+
const tooling = await detectWorkspaceSettings(monorepoRoot);
|
|
1936
|
+
const existingConfigs = await detectExistingConfigs(monorepoRoot);
|
|
1937
|
+
const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
|
|
1938
|
+
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
|
|
1939
|
+
const isNonInteractive = Boolean(options.linter && options.formatter);
|
|
1940
|
+
let linter;
|
|
1941
|
+
let formatter;
|
|
1942
|
+
if (isNonInteractive) {
|
|
1943
|
+
linter = options.linter;
|
|
1944
|
+
formatter = options.formatter;
|
|
1945
|
+
} else {
|
|
1946
|
+
const linterChoice = await p__namespace.select({
|
|
1947
|
+
message: "Linter",
|
|
1948
|
+
options: [
|
|
1949
|
+
{
|
|
1950
|
+
value: "oxlint",
|
|
1951
|
+
label: "oxlint" + (tooling.linter === "oxlint" ? color__default.dim(" (installed)") : "")
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
value: "eslint",
|
|
1955
|
+
label: "eslint" + (tooling.linter === "eslint" || existingConfigs.linter === "eslint" ? color__default.dim(" (installed)") : "")
|
|
1956
|
+
},
|
|
1957
|
+
{
|
|
1958
|
+
value: "biome",
|
|
1959
|
+
label: "biome" + (tooling.linter === "biome" ? color__default.dim(" (installed)") : "")
|
|
1960
|
+
}
|
|
1961
|
+
],
|
|
1962
|
+
initialValue: detectedLinter
|
|
1963
|
+
});
|
|
1964
|
+
if (p__namespace.isCancel(linterChoice)) {
|
|
1965
|
+
p__namespace.cancel("Operation cancelled.");
|
|
1966
|
+
process.exit(0);
|
|
1967
|
+
}
|
|
1968
|
+
const formatterChoice = await p__namespace.select({
|
|
1969
|
+
message: "Formatter",
|
|
1970
|
+
options: [
|
|
1971
|
+
{
|
|
1972
|
+
value: "oxfmt",
|
|
1973
|
+
label: "oxfmt" + (tooling.formatter === "oxfmt" ? color__default.dim(" (installed)") : "")
|
|
1974
|
+
},
|
|
1975
|
+
{
|
|
1976
|
+
value: "prettier",
|
|
1977
|
+
label: "prettier" + (tooling.formatter === "prettier" || existingConfigs.formatter === "prettier" ? color__default.dim(" (installed)") : "")
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
value: "biome",
|
|
1981
|
+
label: "biome" + (tooling.formatter === "biome" ? color__default.dim(" (installed)") : "")
|
|
1982
|
+
}
|
|
1983
|
+
],
|
|
1984
|
+
initialValue: detectedFormatter
|
|
1985
|
+
});
|
|
1986
|
+
if (p__namespace.isCancel(formatterChoice)) {
|
|
1987
|
+
p__namespace.cancel("Operation cancelled.");
|
|
1988
|
+
process.exit(0);
|
|
1989
|
+
}
|
|
1990
|
+
linter = linterChoice;
|
|
1991
|
+
formatter = formatterChoice;
|
|
1992
|
+
}
|
|
1993
|
+
console.log();
|
|
1994
|
+
const spinner = p__namespace.spinner();
|
|
1995
|
+
spinner.start("Fixing workspace...");
|
|
1996
|
+
try {
|
|
1997
|
+
const files = {};
|
|
1998
|
+
const tsConfigExists = await fileExists(
|
|
1999
|
+
node_path.join(monorepoRoot, ".config/typescript/package.json")
|
|
2000
|
+
);
|
|
2001
|
+
if (!tsConfigExists) {
|
|
2002
|
+
index.generateTypescriptConfigPackage(files);
|
|
2003
|
+
}
|
|
2004
|
+
if (linter === "oxlint") {
|
|
2005
|
+
const oxlintExists = await fileExists(node_path.join(monorepoRoot, ".config/oxlint/package.json"));
|
|
2006
|
+
if (!oxlintExists) index.generateOxlintConfigPackage(files);
|
|
2007
|
+
} else if (linter === "eslint") {
|
|
2008
|
+
const eslintPkgExists = await fileExists(
|
|
2009
|
+
node_path.join(monorepoRoot, ".config/eslint/package.json")
|
|
2010
|
+
);
|
|
2011
|
+
if (!eslintPkgExists) {
|
|
2012
|
+
if (existingConfigs.eslintConfigPath) {
|
|
2013
|
+
await migrateEslintConfig(monorepoRoot, files);
|
|
2014
|
+
} else {
|
|
2015
|
+
index.generateEslintConfigPackage(files);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (formatter === "oxfmt") {
|
|
2020
|
+
const oxfmtExists = await fileExists(node_path.join(monorepoRoot, ".config/oxfmt/package.json"));
|
|
2021
|
+
if (!oxfmtExists) index.generateOxfmtConfigPackage(files);
|
|
2022
|
+
} else if (formatter === "prettier") {
|
|
2023
|
+
const prettierPkgExists = await fileExists(
|
|
2024
|
+
node_path.join(monorepoRoot, ".config/prettier/package.json")
|
|
2025
|
+
);
|
|
2026
|
+
if (!prettierPkgExists) {
|
|
2027
|
+
if (existingConfigs.prettierConfigPath) {
|
|
2028
|
+
await migratePrettierConfig(monorepoRoot, files);
|
|
2029
|
+
} else {
|
|
2030
|
+
index.generatePrettierConfigPackage(files);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
|
|
2035
|
+
const versions = await index.resolveMonorepoRootPackageVersions({
|
|
2036
|
+
linter,
|
|
2037
|
+
formatter
|
|
2038
|
+
});
|
|
2039
|
+
const biomeVersion = index.getResolvedPackageVersion(versions, "@biomejs/biome");
|
|
2040
|
+
const biomeConfig = {
|
|
2041
|
+
$schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
|
|
2042
|
+
vcs: {
|
|
2043
|
+
enabled: true,
|
|
2044
|
+
clientKind: "git",
|
|
2045
|
+
useIgnoreFile: true
|
|
2046
|
+
},
|
|
2047
|
+
linter: {
|
|
2048
|
+
enabled: linter === "biome",
|
|
2049
|
+
rules: {
|
|
2050
|
+
recommended: true
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
formatter: {
|
|
2054
|
+
enabled: formatter === "biome"
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
files["biome.json"] = {
|
|
2058
|
+
type: "text",
|
|
2059
|
+
content: JSON.stringify(biomeConfig, null, 2)
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
for (const [filePath, file] of Object.entries(files)) {
|
|
2063
|
+
const fullPath = node_path.join(monorepoRoot, filePath);
|
|
2064
|
+
await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
|
|
2065
|
+
await promises$1.writeFile(fullPath, file.content);
|
|
2066
|
+
}
|
|
2067
|
+
await ensureConfigInWorkspace(monorepoRoot);
|
|
2068
|
+
if (existingConfigs.eslintConfigPath && linter === "eslint") {
|
|
2069
|
+
try {
|
|
2070
|
+
await promises$1.unlink(existingConfigs.eslintConfigPath);
|
|
2071
|
+
} catch {
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (existingConfigs.prettierConfigPath && formatter === "prettier") {
|
|
2075
|
+
try {
|
|
2076
|
+
await promises$1.unlink(existingConfigs.prettierConfigPath);
|
|
2077
|
+
} catch {
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
spinner.stop(color__default.green("\u2713") + " Workspace fixed!");
|
|
2081
|
+
const generated = Object.keys(files).filter((f) => f.endsWith("package.json"));
|
|
2082
|
+
for (const pkgFile of generated) {
|
|
2083
|
+
const pkgName = pkgFile.replace("/package.json", "");
|
|
2084
|
+
console.log(color__default.dim(` Generated ${pkgName}`));
|
|
2085
|
+
}
|
|
2086
|
+
const vscodeSettingsExists = await fileExists(node_path.join(monorepoRoot, ".vscode/settings.json"));
|
|
2087
|
+
const vscodeExtensionsExists = await fileExists(
|
|
2088
|
+
node_path.join(monorepoRoot, ".vscode/extensions.json")
|
|
2089
|
+
);
|
|
2090
|
+
const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
|
|
2091
|
+
if (!vscodeExists) {
|
|
2092
|
+
let addVscode = false;
|
|
2093
|
+
if (isNonInteractive) {
|
|
2094
|
+
addVscode = true;
|
|
2095
|
+
} else {
|
|
2096
|
+
const vscodeChoice = await p__namespace.confirm({
|
|
2097
|
+
message: "Generate VS Code settings?",
|
|
2098
|
+
initialValue: true
|
|
2099
|
+
});
|
|
2100
|
+
addVscode = !p__namespace.isCancel(vscodeChoice) && vscodeChoice;
|
|
2101
|
+
}
|
|
2102
|
+
if (addVscode) {
|
|
2103
|
+
const vscodeFiles = {};
|
|
2104
|
+
index.generateVscodeFiles(vscodeFiles, linter, formatter);
|
|
2105
|
+
for (const [filePath, file] of Object.entries(vscodeFiles)) {
|
|
2106
|
+
const fullPath = node_path.join(monorepoRoot, filePath);
|
|
2107
|
+
await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
|
|
2108
|
+
await promises$1.writeFile(fullPath, file.content);
|
|
2109
|
+
}
|
|
2110
|
+
console.log(color__default.dim(" Generated .vscode/settings.json"));
|
|
2111
|
+
console.log(color__default.dim(" Generated .vscode/extensions.json"));
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
const aiRulesExist = await fileExists(node_path.join(monorepoRoot, ".ai/workspace.md"));
|
|
2115
|
+
if (!aiRulesExist) {
|
|
2116
|
+
const platforms = await promptForAiPlatforms(isNonInteractive);
|
|
2117
|
+
if (platforms.length > 0) {
|
|
2118
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
2119
|
+
const aiFilesOutput = {};
|
|
2120
|
+
index.generateAiFiles(aiFilesOutput, {
|
|
2121
|
+
name: scope,
|
|
2122
|
+
packageManager: "pnpm",
|
|
2123
|
+
linter,
|
|
2124
|
+
formatter,
|
|
2125
|
+
isMonorepo: true,
|
|
2126
|
+
platforms
|
|
2127
|
+
});
|
|
2128
|
+
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
2129
|
+
const fullPath = node_path.join(monorepoRoot, filePath);
|
|
2130
|
+
await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
|
|
2131
|
+
await promises$1.writeFile(fullPath, file.content);
|
|
2132
|
+
console.log(color__default.dim(` Generated ${filePath}`));
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
process.exit(0);
|
|
2137
|
+
} catch (error) {
|
|
2138
|
+
spinner.stop(color__default.red("\u2717") + " Failed to fix workspace");
|
|
2139
|
+
console.error(error);
|
|
2140
|
+
process.exit(1);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
async function handleMigration(config, target, root, options) {
|
|
2144
|
+
const plan = await getMigrationPlan(config, target, root);
|
|
2145
|
+
console.log(color__default.cyan("Migration:"));
|
|
2146
|
+
if (plan.fromLinter !== plan.toLinter) {
|
|
2147
|
+
console.log(` Linter: ${color__default.dim(plan.fromLinter)} \u2192 ${color__default.green(plan.toLinter)}`);
|
|
2148
|
+
}
|
|
2149
|
+
if (plan.fromFormatter !== plan.toFormatter) {
|
|
2150
|
+
console.log(
|
|
2151
|
+
` Formatter: ${color__default.dim(plan.fromFormatter)} \u2192 ${color__default.green(plan.toFormatter)}`
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
console.log();
|
|
2155
|
+
console.log(color__default.cyan("Changes:"));
|
|
2156
|
+
for (const change of plan.changes) {
|
|
2157
|
+
console.log(formatMigrationChange(change));
|
|
2158
|
+
}
|
|
2159
|
+
if (plan.subPackageUpdates.length > 0) {
|
|
2160
|
+
console.log();
|
|
2161
|
+
console.log(color__default.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
|
|
2162
|
+
for (const update of plan.subPackageUpdates) {
|
|
2163
|
+
const changes = [
|
|
2164
|
+
...update.remove.map((d) => `-${d}`),
|
|
2165
|
+
...update.add.map((d) => `+${d}`)
|
|
2166
|
+
].join(", ");
|
|
2167
|
+
console.log(` ~ ${update.path} (${changes})`);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
console.log();
|
|
2171
|
+
if (!options.yes) {
|
|
2172
|
+
const confirm = await p__namespace.confirm({
|
|
2173
|
+
message: "Apply migration?",
|
|
2174
|
+
initialValue: true
|
|
2175
|
+
});
|
|
2176
|
+
if (p__namespace.isCancel(confirm) || !confirm) {
|
|
2177
|
+
console.log(color__default.dim(" Migration cancelled"));
|
|
2178
|
+
process.exit(0);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
await applyMigration(plan, root);
|
|
2182
|
+
const aiWorkspacePath = node_path.join(root, ".ai/workspace.md");
|
|
2183
|
+
const aiRulesExist = await fileExists(aiWorkspacePath);
|
|
2184
|
+
if (aiRulesExist) {
|
|
2185
|
+
console.log();
|
|
2186
|
+
console.log(color__default.cyan("Updating AI rules..."));
|
|
2187
|
+
const scope = await getMonorepoScope(root);
|
|
2188
|
+
const existingPlatforms = [];
|
|
2189
|
+
if (await fileExists(node_path.join(root, "AGENTS.md"))) {
|
|
2190
|
+
existingPlatforms.push("agents");
|
|
2191
|
+
}
|
|
2192
|
+
if (await fileExists(node_path.join(root, "CLAUDE.md"))) {
|
|
2193
|
+
existingPlatforms.push("claude");
|
|
2194
|
+
}
|
|
2195
|
+
const aiFilesOutput = {};
|
|
2196
|
+
index.generateAiFiles(aiFilesOutput, {
|
|
2197
|
+
name: scope,
|
|
2198
|
+
packageManager: "pnpm",
|
|
2199
|
+
linter: plan.toLinter,
|
|
2200
|
+
formatter: plan.toFormatter,
|
|
2201
|
+
isMonorepo: true,
|
|
2202
|
+
platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
|
|
2203
|
+
});
|
|
2204
|
+
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
2205
|
+
const fullPath = node_path.join(root, filePath);
|
|
2206
|
+
await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
|
|
2207
|
+
await promises$1.writeFile(fullPath, file.content);
|
|
2208
|
+
console.log(color__default.dim(` ${filePath}`));
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
console.log();
|
|
2212
|
+
console.log(color__default.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`);
|
|
2213
|
+
console.log(color__default.dim(" Run `pnpm install` to update dependencies"));
|
|
2214
|
+
process.exit(0);
|
|
2215
|
+
}
|
|
2216
|
+
async function handleUpdateCommand(options) {
|
|
2217
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
2218
|
+
const projectRoot = monorepoRoot ?? await detectPackageRoot();
|
|
2219
|
+
if (!projectRoot) {
|
|
2220
|
+
console.log(color__default.red("\u2717") + " Could not find a project root");
|
|
2221
|
+
console.log(color__default.dim(" Run this command from inside a generated project"));
|
|
2222
|
+
process.exit(1);
|
|
2223
|
+
}
|
|
2224
|
+
const isMonorepo = monorepoRoot != null;
|
|
2225
|
+
if (isMonorepo) {
|
|
2226
|
+
const { valid, errors } = await validateWorkspace(projectRoot);
|
|
2227
|
+
if (!valid) {
|
|
2228
|
+
console.log(color__default.yellow("!") + " Workspace has issues:");
|
|
2229
|
+
for (const error of errors) {
|
|
2230
|
+
console.log(color__default.dim(` \u2022 ${error}`));
|
|
2231
|
+
}
|
|
2232
|
+
console.log();
|
|
2233
|
+
const shouldFix = options.yes || await p__namespace.confirm({
|
|
2234
|
+
message: "Run fix first to resolve these issues?",
|
|
2235
|
+
initialValue: true
|
|
2236
|
+
});
|
|
2237
|
+
if (p__namespace.isCancel(shouldFix) || !shouldFix) {
|
|
2238
|
+
console.log(color__default.dim(" Run `pnpm create krispya --fix` to fix manually"));
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
const preFixConfig = await detectCurrentConfig(projectRoot);
|
|
2242
|
+
const fixOptions = {
|
|
2243
|
+
...options,
|
|
2244
|
+
linter: options.linter ?? preFixConfig.linter,
|
|
2245
|
+
formatter: options.formatter ?? preFixConfig.formatter
|
|
2246
|
+
};
|
|
2247
|
+
await handleFixCommand(fixOptions);
|
|
2248
|
+
}
|
|
2249
|
+
} else if (options.linter || options.formatter) {
|
|
2250
|
+
console.log(
|
|
2251
|
+
color__default.yellow("!") + " Linter/formatter migrations in --update are currently monorepo-only"
|
|
2252
|
+
);
|
|
2253
|
+
console.log(color__default.dim(" Continuing with standalone shared config updates"));
|
|
2254
|
+
console.log();
|
|
2255
|
+
}
|
|
2256
|
+
const config = await detectCurrentConfig(projectRoot, isMonorepo);
|
|
2257
|
+
const targetLinter = options.linter;
|
|
2258
|
+
const targetFormatter = options.formatter;
|
|
2259
|
+
const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
|
|
2260
|
+
if (isMonorepo && needsMigration(config, migrationTarget)) {
|
|
2261
|
+
await handleMigration(config, migrationTarget, projectRoot, options);
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
console.log(
|
|
2265
|
+
color__default.cyan("Checking for updates...") + color__default.dim(` (${config.linter}/${config.formatter})`)
|
|
2266
|
+
);
|
|
2267
|
+
console.log();
|
|
2268
|
+
const expected = await generateExpectedFiles(config);
|
|
2269
|
+
const categories = await compareWithDisk(expected, projectRoot);
|
|
2270
|
+
const allCategories = categories.filter((c) => c.category !== "workspace-config");
|
|
2271
|
+
if (isMonorepo) {
|
|
2272
|
+
const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
|
|
2273
|
+
const workspaceCategory = {
|
|
2274
|
+
category: "workspace-config",
|
|
2275
|
+
label: "Workspace Config",
|
|
2276
|
+
changes: workspaceConfigChanges,
|
|
2277
|
+
hasUserModifications: workspaceConfigChanges.some((c) => c.status === "modified")
|
|
2278
|
+
};
|
|
2279
|
+
if (workspaceConfigChanges.length > 0) {
|
|
2280
|
+
const configPkgIndex = allCategories.findIndex((c) => c.category === "config-packages");
|
|
2281
|
+
if (configPkgIndex !== -1) {
|
|
2282
|
+
allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
|
|
2283
|
+
} else {
|
|
2284
|
+
allCategories.push(workspaceCategory);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
let updatedCount = 0;
|
|
2289
|
+
let skippedCount = 0;
|
|
2290
|
+
for (const category of allCategories) {
|
|
2291
|
+
const newChanges = category.changes.filter((c) => c.status === "added");
|
|
2292
|
+
const modifiedChanges = category.changes.filter((c) => c.status === "modified");
|
|
2293
|
+
const hasNew = newChanges.length > 0;
|
|
2294
|
+
const hasModified = modifiedChanges.length > 0;
|
|
2295
|
+
const hasChanges = hasNew || hasModified;
|
|
2296
|
+
if (!hasChanges) {
|
|
2297
|
+
console.log(color__default.green("\u2713") + ` ${category.label}: Up to date`);
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
if (category.category === "ai-files") {
|
|
2301
|
+
if (hasNew) {
|
|
2302
|
+
console.log(color__default.cyan(category.label + ":"));
|
|
2303
|
+
console.log(color__default.dim(` ${newChanges.length} AI file(s) can be added`));
|
|
2304
|
+
console.log();
|
|
2305
|
+
const applyAi = options.yes ? true : await p__namespace.confirm({
|
|
2306
|
+
message: "Add AI rules?",
|
|
2307
|
+
initialValue: true
|
|
2308
|
+
});
|
|
2309
|
+
if (!p__namespace.isCancel(applyAi) && applyAi) {
|
|
2310
|
+
await applyUpdates(newChanges, projectRoot);
|
|
2311
|
+
console.log(color__default.green("\u2713") + ` Added ${newChanges.length} AI file(s)`);
|
|
2312
|
+
updatedCount++;
|
|
2313
|
+
} else {
|
|
2314
|
+
console.log(color__default.dim(` Skipped ${category.label}`));
|
|
2315
|
+
skippedCount++;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (hasModified) {
|
|
2319
|
+
console.log(color__default.cyan("AI Files (existing):"));
|
|
2320
|
+
for (const change of modifiedChanges) {
|
|
2321
|
+
console.log(formatFileChange(change));
|
|
2322
|
+
}
|
|
2323
|
+
console.log();
|
|
2324
|
+
if (options.yes) {
|
|
2325
|
+
console.log(color__default.dim(" (--yes mode: keeping existing AI files)"));
|
|
2326
|
+
} else {
|
|
2327
|
+
const updateExisting = await p__namespace.confirm({
|
|
2328
|
+
message: "Update existing AI files to latest template?",
|
|
2329
|
+
initialValue: false
|
|
2330
|
+
});
|
|
2331
|
+
if (!p__namespace.isCancel(updateExisting) && updateExisting) {
|
|
2332
|
+
await applyUpdates(modifiedChanges, projectRoot);
|
|
2333
|
+
console.log(color__default.green("\u2713") + " Updated existing AI files");
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
console.log();
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
let changesToApply = [];
|
|
2341
|
+
if (options.yes) {
|
|
2342
|
+
console.log(color__default.cyan(category.label + ":"));
|
|
2343
|
+
for (const change of [...newChanges, ...modifiedChanges]) {
|
|
2344
|
+
console.log(formatFileChange(change));
|
|
2345
|
+
}
|
|
2346
|
+
console.log();
|
|
2347
|
+
if (category.category === "workspace-config") {
|
|
2348
|
+
changesToApply = [...newChanges, ...modifiedChanges];
|
|
2349
|
+
if (changesToApply.length > 0) {
|
|
2350
|
+
console.log(color__default.dim(" (--yes mode: applying merge updates)"));
|
|
2351
|
+
}
|
|
2352
|
+
} else {
|
|
2353
|
+
changesToApply = newChanges;
|
|
2354
|
+
if (newChanges.length > 0) {
|
|
2355
|
+
console.log(color__default.dim(" (--yes mode: adding new files only)"));
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
} else if (hasNew && hasModified) {
|
|
2359
|
+
const allChanges = [...newChanges, ...modifiedChanges];
|
|
2360
|
+
const selectedFiles = await p__namespace.multiselect({
|
|
2361
|
+
message: `${category.label} (+ new, ~ changed)`,
|
|
2362
|
+
options: allChanges.map((change) => ({
|
|
2363
|
+
value: change.path,
|
|
2364
|
+
label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
|
|
2365
|
+
})),
|
|
2366
|
+
initialValues: newChanges.map((c) => c.path),
|
|
2367
|
+
// Pre-select new files
|
|
2368
|
+
required: false
|
|
2369
|
+
});
|
|
2370
|
+
if (p__namespace.isCancel(selectedFiles)) {
|
|
2371
|
+
p__namespace.cancel("Operation cancelled.");
|
|
2372
|
+
process.exit(0);
|
|
2373
|
+
}
|
|
2374
|
+
if (selectedFiles.length > 0) {
|
|
2375
|
+
changesToApply = allChanges.filter((c) => selectedFiles.includes(c.path));
|
|
2376
|
+
}
|
|
2377
|
+
} else if (hasNew) {
|
|
2378
|
+
console.log(color__default.cyan(category.label + ":"));
|
|
2379
|
+
for (const change of newChanges) {
|
|
2380
|
+
console.log(formatFileChange(change));
|
|
2381
|
+
}
|
|
2382
|
+
console.log();
|
|
2383
|
+
const shouldAdd = await p__namespace.confirm({
|
|
2384
|
+
message: `Add ${newChanges.length} new file(s)?`,
|
|
2385
|
+
initialValue: true
|
|
2386
|
+
});
|
|
2387
|
+
if (p__namespace.isCancel(shouldAdd)) {
|
|
2388
|
+
p__namespace.cancel("Operation cancelled.");
|
|
2389
|
+
process.exit(0);
|
|
2390
|
+
}
|
|
2391
|
+
if (shouldAdd) {
|
|
2392
|
+
changesToApply = newChanges;
|
|
2393
|
+
}
|
|
2394
|
+
} else if (hasModified) {
|
|
2395
|
+
console.log(color__default.cyan(category.label + ":"));
|
|
2396
|
+
for (const change of modifiedChanges) {
|
|
2397
|
+
console.log(formatFileChange(change));
|
|
2398
|
+
}
|
|
2399
|
+
console.log();
|
|
2400
|
+
const shouldUpdate = await p__namespace.confirm({
|
|
2401
|
+
message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
|
|
2402
|
+
initialValue: false
|
|
2403
|
+
});
|
|
2404
|
+
if (p__namespace.isCancel(shouldUpdate)) {
|
|
2405
|
+
p__namespace.cancel("Operation cancelled.");
|
|
2406
|
+
process.exit(0);
|
|
2407
|
+
}
|
|
2408
|
+
if (shouldUpdate) {
|
|
2409
|
+
changesToApply = modifiedChanges;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (changesToApply.length > 0) {
|
|
2413
|
+
await applyUpdates(changesToApply, projectRoot);
|
|
2414
|
+
const addedCount = changesToApply.filter((c) => c.status === "added").length;
|
|
2415
|
+
const updatedFilesCount = changesToApply.filter((c) => c.status === "modified").length;
|
|
2416
|
+
const parts = [];
|
|
2417
|
+
if (addedCount > 0) parts.push(`added ${addedCount}`);
|
|
2418
|
+
if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
|
|
2419
|
+
console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
|
|
2420
|
+
updatedCount++;
|
|
2421
|
+
} else {
|
|
2422
|
+
console.log(color__default.dim(` Skipped ${category.label}`));
|
|
2423
|
+
skippedCount++;
|
|
2424
|
+
}
|
|
2425
|
+
console.log();
|
|
2426
|
+
}
|
|
2427
|
+
if (updatedCount === 0 && skippedCount === 0) {
|
|
2428
|
+
console.log(color__default.green("\u2713") + " Everything is up to date!");
|
|
2429
|
+
} else if (updatedCount > 0) {
|
|
2430
|
+
console.log(
|
|
2431
|
+
color__default.green("\u2713") + ` Updated ${updatedCount} ${updatedCount === 1 ? "category" : "categories"}`
|
|
2432
|
+
);
|
|
2433
|
+
if (skippedCount > 0) {
|
|
2434
|
+
console.log(color__default.dim(` Skipped ${skippedCount}`));
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
process.exit(0);
|
|
2438
|
+
}
|
|
2439
|
+
async function handleWorkspaceCommand(name, options) {
|
|
2440
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
2441
|
+
if (!monorepoRoot) {
|
|
2442
|
+
console.error(color__default.red("Error:") + " --workspace flag requires being inside a monorepo");
|
|
2443
|
+
process.exit(1);
|
|
2444
|
+
}
|
|
2445
|
+
if (!name) {
|
|
2446
|
+
console.error(color__default.red("Error:") + " Package name is required with --workspace flag");
|
|
2447
|
+
console.log(color__default.dim(" Example: pnpm create krispya my-lib --workspace --type library"));
|
|
2448
|
+
process.exit(1);
|
|
2449
|
+
}
|
|
2450
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
2451
|
+
const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
|
|
2452
|
+
const projectType = options.type ?? "app";
|
|
2453
|
+
const defaultDir = projectType === "library" ? "packages" : "apps";
|
|
2454
|
+
const targetDir = options.dir ?? defaultDir;
|
|
2455
|
+
const template = options.template ?? "vanilla";
|
|
2456
|
+
const baseTemplate = index.getBaseTemplate(template);
|
|
2457
|
+
const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
|
|
2458
|
+
const fullPackagePath = node_path.join(monorepoRoot, targetDir, name);
|
|
2459
|
+
try {
|
|
2460
|
+
await promises$1.access(fullPackagePath, node_fs.constants.F_OK);
|
|
2461
|
+
console.error(color__default.red("Error:") + ` Directory ${targetDir}/${name} already exists`);
|
|
2462
|
+
process.exit(1);
|
|
2463
|
+
} catch {
|
|
2464
|
+
}
|
|
2465
|
+
const linter = inheritedSettings.linter ?? options.linter ?? "oxlint";
|
|
2466
|
+
const formatter = inheritedSettings.formatter ?? options.formatter ?? "prettier";
|
|
2467
|
+
const packageManager = inheritedSettings.packageManager?.name ?? "pnpm";
|
|
2468
|
+
const engine = inheritedSettings.engine ?? {
|
|
2469
|
+
name: "node",
|
|
2470
|
+
version: "latest"
|
|
2471
|
+
};
|
|
2472
|
+
const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
|
|
2473
|
+
const isLibrary = projectType === "library";
|
|
2474
|
+
const relativePkgPath = node_path.join(targetDir, name);
|
|
2475
|
+
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
2476
|
+
const generateOptions = {
|
|
2477
|
+
name: scopedName,
|
|
2478
|
+
projectType,
|
|
2479
|
+
libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
|
|
2480
|
+
template,
|
|
2481
|
+
linter,
|
|
2482
|
+
formatter,
|
|
2483
|
+
packageManager: { name: packageManager },
|
|
2484
|
+
engine,
|
|
2485
|
+
pnpmManageVersions,
|
|
2486
|
+
workspaceRoot,
|
|
2487
|
+
...baseTemplate === "r3f" && {
|
|
2488
|
+
drei: options.drei ? {} : void 0,
|
|
2489
|
+
handle: options.handle ? {} : void 0,
|
|
2490
|
+
leva: options.leva ? {} : void 0,
|
|
2491
|
+
postprocessing: options.postprocessing ? {} : void 0,
|
|
2492
|
+
rapier: options.rapier ? {} : void 0,
|
|
2493
|
+
xr: options.xr ? {} : void 0,
|
|
2494
|
+
uikit: options.uikit ? {} : void 0,
|
|
2495
|
+
offscreen: options.offscreen ? {} : void 0,
|
|
2496
|
+
zustand: options.zustand ? {} : void 0,
|
|
2497
|
+
koota: options.koota ? {} : void 0,
|
|
2498
|
+
viverse: options.viverse ? {} : void 0,
|
|
2499
|
+
triplex: options.triplex ? {} : void 0
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
|
|
2503
|
+
generateOptions.engine = await index.resolveEngine(generateOptions);
|
|
2504
|
+
generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
|
|
2505
|
+
console.log(color__default.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
|
|
2506
|
+
try {
|
|
2507
|
+
const files = index.generate(generateOptions);
|
|
2508
|
+
await writeGeneratedFiles(fullPackagePath, files);
|
|
2509
|
+
console.log(color__default.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
|
|
2510
|
+
process.exit(0);
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
console.error(color__default.red("Error:") + " Failed to create package");
|
|
2513
|
+
console.error(String(error));
|
|
2514
|
+
process.exit(1);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
async function handleMonorepoCreation(generateOptions, isNonInteractive) {
|
|
2518
|
+
const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
|
|
2519
|
+
const packageManager = index.getPackageManagerName(generateOptions.packageManager);
|
|
2520
|
+
generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
|
|
2521
|
+
generateOptions.engine = await index.resolveEngine(generateOptions);
|
|
2522
|
+
generateOptions.versions = await index.resolveMonorepoRootPackageVersions({
|
|
2523
|
+
linter: generateOptions.linter ?? "oxlint",
|
|
2524
|
+
formatter: generateOptions.formatter ?? "prettier",
|
|
2525
|
+
versions: generateOptions.versions
|
|
2526
|
+
});
|
|
2527
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
2528
|
+
const projectPath = node_path.join(node_process.cwd(), generateOptions.name);
|
|
2529
|
+
const spinner = p__namespace.spinner();
|
|
2530
|
+
spinner.start("Creating monorepo workspace...");
|
|
2531
|
+
try {
|
|
2532
|
+
const { files } = generateMonorepo({
|
|
2533
|
+
name: generateOptions.name,
|
|
2534
|
+
linter: generateOptions.linter ?? "oxlint",
|
|
2535
|
+
formatter: generateOptions.formatter ?? "prettier",
|
|
2536
|
+
packageManager: generateOptions.packageManager ?? {
|
|
2537
|
+
name: packageManager
|
|
2538
|
+
},
|
|
2539
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
2540
|
+
engine: generateOptions.engine,
|
|
2541
|
+
versions: generateOptions.versions,
|
|
2542
|
+
aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
|
|
2543
|
+
});
|
|
2544
|
+
const filePaths = Object.keys(files).sort();
|
|
2545
|
+
for (const filePath of filePaths) {
|
|
2546
|
+
const fullFilePath = node_path.join(projectPath, filePath);
|
|
2547
|
+
await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
|
|
2548
|
+
const file = files[filePath];
|
|
2549
|
+
if (file.type === "text") {
|
|
2550
|
+
await promises$1.writeFile(fullFilePath, file.content);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
spinner.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
|
|
2554
|
+
if (isNonInteractive) {
|
|
2555
|
+
process.exit(0);
|
|
2556
|
+
}
|
|
2557
|
+
const newWorkspaceSettings = {
|
|
2558
|
+
linter: generateOptions.linter,
|
|
2559
|
+
formatter: generateOptions.formatter,
|
|
2560
|
+
packageManager: generateOptions.packageManager ?? {
|
|
2561
|
+
name: packageManager
|
|
2562
|
+
},
|
|
2563
|
+
engine: generateOptions.engine,
|
|
2564
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions
|
|
2565
|
+
};
|
|
2566
|
+
const scope = generateOptions.name;
|
|
2567
|
+
let addMore = true;
|
|
2568
|
+
while (addMore) {
|
|
2569
|
+
addMore = await createPackageInWorkspace(
|
|
2570
|
+
projectPath,
|
|
2571
|
+
packageManager,
|
|
2572
|
+
newWorkspaceSettings,
|
|
2573
|
+
scope
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
const nextSteps = [
|
|
2577
|
+
`cd ${generateOptions.name}`,
|
|
2578
|
+
`${packageManager} install`,
|
|
2579
|
+
`${packageManager} run dev`
|
|
2580
|
+
].join("\n");
|
|
2581
|
+
p__namespace.note(nextSteps, "Next steps");
|
|
2582
|
+
await promptAndOpenEditor(projectPath);
|
|
2583
|
+
p__namespace.outro(color__default.green("Happy coding! \u2728"));
|
|
2584
|
+
process.exit(0);
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
spinner.stop("Failed to create monorepo workspace");
|
|
2587
|
+
p__namespace.log.error(String(error));
|
|
2588
|
+
process.exit(1);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
|
|
2592
|
+
const base = generateOptions.template ? index.getBaseTemplate(generateOptions.template) : "vanilla";
|
|
2593
|
+
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
2594
|
+
generateOptions.name ??= defaultFallbackName;
|
|
2595
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
2596
|
+
if (aiPlatforms.length > 0) {
|
|
2597
|
+
generateOptions.aiPlatforms = aiPlatforms;
|
|
2598
|
+
}
|
|
2599
|
+
const packageManager = index.getPackageManagerName(generateOptions.packageManager);
|
|
2600
|
+
const isLibrary = generateOptions.projectType === "library";
|
|
2601
|
+
generateOptions.packageManager = await index.resolvePackageManager(generateOptions);
|
|
2602
|
+
generateOptions.engine = await index.resolveEngine(generateOptions);
|
|
2603
|
+
generateOptions.versions = await index.resolveProjectPackageVersions(generateOptions);
|
|
2604
|
+
const projectPath = node_path.join(node_process.cwd(), generateOptions.name);
|
|
2605
|
+
const spinner = p__namespace.spinner();
|
|
2606
|
+
spinner.start("Creating project...");
|
|
2607
|
+
try {
|
|
2608
|
+
const files = index.generate(generateOptions);
|
|
2609
|
+
await writeGeneratedFiles(projectPath, files);
|
|
2610
|
+
spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
|
|
2611
|
+
if (isNonInteractive) process.exit(0);
|
|
2612
|
+
const nextSteps = isLibrary ? [
|
|
2613
|
+
`cd ${generateOptions.name}`,
|
|
2614
|
+
`${packageManager} install`,
|
|
2615
|
+
`${packageManager} run build`
|
|
2616
|
+
].join("\n") : [
|
|
2617
|
+
`cd ${generateOptions.name}`,
|
|
2618
|
+
`${packageManager} install`,
|
|
2619
|
+
`${packageManager} run dev`
|
|
2620
|
+
].join("\n");
|
|
2621
|
+
p__namespace.note(nextSteps, "Next steps");
|
|
2622
|
+
await promptAndOpenEditor(projectPath);
|
|
2623
|
+
p__namespace.outro(color__default.green("Happy coding! \u2728"));
|
|
2624
|
+
} catch (error) {
|
|
2625
|
+
spinner.stop("Failed to create project");
|
|
2626
|
+
p__namespace.log.error(String(error));
|
|
2627
|
+
process.exit(1);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
async function handleInteractiveMonorepoMode(monorepoRoot) {
|
|
2631
|
+
const choice = await p__namespace.select({
|
|
2632
|
+
message: "Detected monorepo workspace",
|
|
2633
|
+
options: [
|
|
2634
|
+
{ value: "add", label: "Add new package to this workspace" },
|
|
2635
|
+
{ value: "standalone", label: "Create standalone project" }
|
|
2636
|
+
],
|
|
2637
|
+
initialValue: "add"
|
|
2638
|
+
});
|
|
2639
|
+
if (p__namespace.isCancel(choice)) {
|
|
2640
|
+
p__namespace.cancel("Operation cancelled.");
|
|
2641
|
+
process.exit(0);
|
|
2642
|
+
}
|
|
2643
|
+
if (choice === "add") {
|
|
2644
|
+
const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
|
|
2645
|
+
const hasSettings = Object.values(inheritedSettings).some(Boolean);
|
|
2646
|
+
if (hasSettings) {
|
|
2647
|
+
const settingsInfo = [
|
|
2648
|
+
inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
|
|
2649
|
+
inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
|
|
2650
|
+
inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager.name}`
|
|
2651
|
+
].filter(Boolean).join(", ");
|
|
2652
|
+
p__namespace.log.info(`Using workspace settings (${settingsInfo})`);
|
|
2653
|
+
}
|
|
2654
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
2655
|
+
let addMore = true;
|
|
2656
|
+
while (addMore) {
|
|
2657
|
+
addMore = await createPackageInWorkspace(
|
|
2658
|
+
monorepoRoot,
|
|
2659
|
+
inheritedSettings.packageManager?.name ?? "pnpm",
|
|
2660
|
+
inheritedSettings,
|
|
2661
|
+
scope
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
p__namespace.note([`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"), "Next steps");
|
|
2665
|
+
await promptAndOpenEditor(monorepoRoot);
|
|
2666
|
+
p__namespace.outro(color__default.green("Happy coding! \u2728"));
|
|
2667
|
+
process.exit(0);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
async function main() {
|
|
2671
|
+
const program = new commander.Command().name("create-krispya").description("CLI for creating Vanilla, React, and React Three Fiber projects").argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
|
|
2672
|
+
"--bundler <bundler>",
|
|
2673
|
+
"library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
|
|
2674
|
+
).option(
|
|
2675
|
+
"--template <type>",
|
|
2676
|
+
"project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
|
|
2677
|
+
).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: prettier)").option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option("--package-manager <manager>", "specify package manager (e.g. npm, yarn, pnpm)").option(
|
|
2678
|
+
"--pnpm-manage-versions",
|
|
2679
|
+
"enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
|
|
2680
|
+
).option(
|
|
2681
|
+
"--no-pnpm-manage-versions",
|
|
2682
|
+
"disable manage-package-manager-versions in pnpm-workspace.yaml"
|
|
2683
|
+
).option(
|
|
2684
|
+
"--node-version <version>",
|
|
2685
|
+
'set Node.js version for engines.node field (default: "latest")'
|
|
2686
|
+
).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
|
|
2687
|
+
"--path <directory>",
|
|
2688
|
+
"Run in specified directory instead of current working directory"
|
|
2689
|
+
).action(async (name, options) => {
|
|
2690
|
+
if (options.path) {
|
|
2691
|
+
process.chdir(options.path);
|
|
2692
|
+
}
|
|
2693
|
+
if (options.clearConfig) {
|
|
2694
|
+
clearConfig();
|
|
2695
|
+
console.log("Configuration cleared.");
|
|
2696
|
+
process.exit(0);
|
|
2697
|
+
}
|
|
2698
|
+
if (options.configPath) {
|
|
2699
|
+
console.log(getConfigPath());
|
|
2700
|
+
process.exit(0);
|
|
2701
|
+
}
|
|
2702
|
+
if (name?.startsWith("-")) {
|
|
2703
|
+
switch (name) {
|
|
2704
|
+
case "--version":
|
|
2705
|
+
case "-V":
|
|
2706
|
+
console.log(pkg.version);
|
|
2707
|
+
process.exit(0);
|
|
2708
|
+
case "--help":
|
|
2709
|
+
case "-h":
|
|
2710
|
+
program.help();
|
|
2711
|
+
break;
|
|
2712
|
+
case "--clear-config":
|
|
2713
|
+
clearConfig();
|
|
2714
|
+
console.log("Configuration cleared.");
|
|
2715
|
+
process.exit(0);
|
|
2716
|
+
case "--config-path":
|
|
2717
|
+
console.log(getConfigPath());
|
|
2718
|
+
process.exit(0);
|
|
2719
|
+
case "--check":
|
|
2720
|
+
await handleCheckCommand();
|
|
2721
|
+
break;
|
|
2722
|
+
case "--fix":
|
|
2723
|
+
options.fix = true;
|
|
2724
|
+
break;
|
|
2725
|
+
case "--update":
|
|
2726
|
+
options.update = true;
|
|
2727
|
+
break;
|
|
2728
|
+
case "--yes":
|
|
2729
|
+
options.yes = true;
|
|
2730
|
+
break;
|
|
2731
|
+
default:
|
|
2732
|
+
console.error(color__default.red(`Unknown option: ${name}`));
|
|
2733
|
+
process.exit(1);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (options.check) {
|
|
2737
|
+
await handleCheckCommand();
|
|
2738
|
+
}
|
|
2739
|
+
if (options.fix) {
|
|
2740
|
+
await handleFixCommand(options);
|
|
2741
|
+
}
|
|
2742
|
+
if (options.update) {
|
|
2743
|
+
await handleUpdateCommand(options);
|
|
2744
|
+
}
|
|
2745
|
+
if (options.dir && !options.workspace) {
|
|
2746
|
+
console.error(color__default.red("Error:") + " --dir requires --workspace flag");
|
|
2747
|
+
console.log(
|
|
2748
|
+
color__default.dim(" Example: pnpm create krispya my-lib --workspace --dir examples")
|
|
2749
|
+
);
|
|
2750
|
+
process.exit(1);
|
|
2751
|
+
}
|
|
2752
|
+
if (options.workspace) {
|
|
2753
|
+
await handleWorkspaceCommand(name, options);
|
|
2754
|
+
}
|
|
2755
|
+
console.clear();
|
|
2756
|
+
p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
|
|
2757
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
2758
|
+
if (monorepoRoot && !hasConfigOptions(options)) {
|
|
2759
|
+
await handleInteractiveMonorepoMode(monorepoRoot);
|
|
2760
|
+
}
|
|
2761
|
+
let generateOptions;
|
|
2762
|
+
if (options.yes) {
|
|
2763
|
+
const template = options.template ?? "vanilla";
|
|
2764
|
+
const baseTemplate = index.getBaseTemplate(template);
|
|
2765
|
+
const defaultName = getDefaultProjectName(template);
|
|
2766
|
+
const projectType = options.type ?? "app";
|
|
2767
|
+
generateOptions = {
|
|
2768
|
+
name: name || defaultName,
|
|
2769
|
+
projectType,
|
|
2770
|
+
libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
|
|
2771
|
+
template,
|
|
2772
|
+
linter: options.linter ?? "oxlint",
|
|
2773
|
+
formatter: options.formatter ?? "prettier",
|
|
2774
|
+
...baseTemplate === "r3f" && {
|
|
2775
|
+
drei: options.drei ? {} : void 0,
|
|
2776
|
+
handle: options.handle ? {} : void 0,
|
|
2777
|
+
leva: options.leva ? {} : void 0,
|
|
2778
|
+
postprocessing: options.postprocessing ? {} : void 0,
|
|
2779
|
+
rapier: options.rapier ? {} : void 0,
|
|
2780
|
+
xr: options.xr ? {} : void 0,
|
|
2781
|
+
uikit: options.uikit ? {} : void 0,
|
|
2782
|
+
offscreen: options.offscreen ? {} : void 0,
|
|
2783
|
+
zustand: options.zustand ? {} : void 0,
|
|
2784
|
+
koota: options.koota ? {} : void 0,
|
|
2785
|
+
viverse: options.viverse ? {} : void 0,
|
|
2786
|
+
triplex: options.triplex ? {} : void 0
|
|
2787
|
+
},
|
|
2788
|
+
packageManager: options.packageManager ? { name: options.packageManager } : void 0,
|
|
2789
|
+
pnpmManageVersions: options.pnpmManageVersions,
|
|
2790
|
+
engine: { name: "node", version: options.nodeVersion ?? "latest" }
|
|
2791
|
+
};
|
|
2792
|
+
} else {
|
|
2793
|
+
const presets = hasConfigOptions(options) ? {
|
|
2794
|
+
type: options.type,
|
|
2795
|
+
template: options.template,
|
|
2796
|
+
bundler: options.bundler,
|
|
2797
|
+
linter: options.linter,
|
|
2798
|
+
formatter: options.formatter,
|
|
2799
|
+
packageManager: options.packageManager,
|
|
2800
|
+
engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
|
|
2801
|
+
pnpmManageVersions: options.pnpmManageVersions,
|
|
2802
|
+
drei: options.drei,
|
|
2803
|
+
handle: options.handle,
|
|
2804
|
+
leva: options.leva,
|
|
2805
|
+
postprocessing: options.postprocessing,
|
|
2806
|
+
rapier: options.rapier,
|
|
2807
|
+
xr: options.xr,
|
|
2808
|
+
uikit: options.uikit,
|
|
2809
|
+
offscreen: options.offscreen,
|
|
2810
|
+
zustand: options.zustand,
|
|
2811
|
+
koota: options.koota,
|
|
2812
|
+
triplex: options.triplex,
|
|
2813
|
+
viverse: options.viverse
|
|
2814
|
+
} : void 0;
|
|
2815
|
+
generateOptions = await promptForOptions(name, presets);
|
|
2816
|
+
}
|
|
2817
|
+
const isNonInteractive = options.yes ?? false;
|
|
2818
|
+
if (generateOptions.projectType === "monorepo") {
|
|
2819
|
+
await handleMonorepoCreation(generateOptions, isNonInteractive);
|
|
2820
|
+
} else {
|
|
2821
|
+
await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
await program.parseAsync();
|
|
2825
|
+
}
|
|
2826
|
+
main().catch(console.error);
|