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