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