create-krispya 0.5.3 → 0.6.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 +115 -11
- package/dist/chunks/index.cjs +487 -277
- package/dist/chunks/index.mjs +484 -278
- package/dist/cli.cjs +1261 -273
- package/dist/cli.mjs +1264 -276
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +21 -5
- package/dist/index.d.mts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.mjs +4 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { cwd } from 'process';
|
|
4
4
|
import { join, dirname, resolve } from 'path';
|
|
5
|
-
import { access, constants, mkdir, writeFile,
|
|
5
|
+
import { access, constants, readFile, mkdir, writeFile, rm, readdir, unlink } from 'fs/promises';
|
|
6
6
|
import { constants as constants$1 } from 'fs';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import * as p from '@clack/prompts';
|
|
9
9
|
import color from 'chalk';
|
|
10
10
|
import { fetch } from 'undici';
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
|
-
import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as
|
|
12
|
+
import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, d as detectTooling, c as generateAiFiles, A as ALL_AI_PLATFORMS, e as generateVscodeFiles, f as generateTypescriptConfigPackage, h as generateOxlintConfigPackage, i as generateEslintConfigPackage, j as generateOxfmtConfigPackage, k as generatePrettierConfigPackage, p as parseWorkspaceYamlContent, l as getLatestNpmVersion, m as generate, n as getLatestPnpmVersion, o as getLatestYarnVersion, q as getLatestNpmCliVersion, r as getLatestNodeVersion, s as AI_PLATFORM_LABELS, t as AI_PLATFORM_HINTS, v as validatePackageName } from './chunks/index.mjs';
|
|
13
13
|
import Conf from 'conf';
|
|
14
14
|
|
|
15
15
|
const editorNames = {
|
|
@@ -37,14 +37,15 @@ function openInEditor(editor, path, reuseWindow) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function formatConfigSummary(options) {
|
|
40
|
+
function formatConfigSummary(options, inherited) {
|
|
41
41
|
const lines = [];
|
|
42
42
|
const VALUE_COL = 27;
|
|
43
|
-
const formatRow = (label, value, indent = "") => {
|
|
43
|
+
const formatRow = (label, value, isInherited = false, indent = "") => {
|
|
44
44
|
const fullLabel = indent + label;
|
|
45
45
|
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
46
46
|
const dots = color.gray(".".repeat(dotCount));
|
|
47
|
-
|
|
47
|
+
const displayValue = isInherited ? `${value} \u{1F512}` : value;
|
|
48
|
+
return `${indent}${label} ${dots} ${displayValue}`;
|
|
48
49
|
};
|
|
49
50
|
const formatLanguage = (lang) => {
|
|
50
51
|
return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
|
|
@@ -63,20 +64,29 @@ function formatConfigSummary(options) {
|
|
|
63
64
|
} else {
|
|
64
65
|
lines.push(formatRow("Bundler", "vite"));
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
lines.push(formatRow("
|
|
67
|
+
const nodeVersionInherited = inherited?.nodeVersion !== void 0;
|
|
68
|
+
lines.push(formatRow("Node version", options.nodeVersion || "latest", nodeVersionInherited));
|
|
69
|
+
const pmInherited = inherited?.packageManager !== void 0;
|
|
70
|
+
lines.push(formatRow("Package manager", options.packageManager || "pnpm", pmInherited));
|
|
68
71
|
if (options.packageManager === "pnpm") {
|
|
69
72
|
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
70
|
-
|
|
73
|
+
const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
|
|
74
|
+
lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
|
|
71
75
|
}
|
|
72
76
|
if (options.linter) {
|
|
73
|
-
|
|
77
|
+
const linterInherited = inherited?.linter !== void 0;
|
|
78
|
+
lines.push(formatRow("Linter", options.linter, linterInherited));
|
|
74
79
|
}
|
|
75
80
|
if (options.formatter) {
|
|
76
|
-
|
|
81
|
+
const formatterInherited = inherited?.formatter !== void 0;
|
|
82
|
+
lines.push(formatRow("Formatter", options.formatter, formatterInherited));
|
|
77
83
|
}
|
|
78
84
|
const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
|
|
79
85
|
lines.push(formatRow("Testing", testing));
|
|
86
|
+
if (!inherited) {
|
|
87
|
+
const configStrategy = options.configStrategy ?? "stealth";
|
|
88
|
+
lines.push(formatRow("Config strategy", configStrategy));
|
|
89
|
+
}
|
|
80
90
|
if (options.template && getBaseTemplate(options.template) === "r3f") {
|
|
81
91
|
const integrationNames = [
|
|
82
92
|
options.drei && "drei",
|
|
@@ -138,11 +148,14 @@ function getReuseWindow() {
|
|
|
138
148
|
function setReuseWindow(reuse) {
|
|
139
149
|
config.set("reuseWindow", reuse);
|
|
140
150
|
}
|
|
141
|
-
function
|
|
142
|
-
return config.get("
|
|
151
|
+
function getAiPlatforms() {
|
|
152
|
+
return config.get("aiPlatforms");
|
|
153
|
+
}
|
|
154
|
+
function setAiPlatforms(platforms) {
|
|
155
|
+
config.set("aiPlatforms", platforms);
|
|
143
156
|
}
|
|
144
|
-
function
|
|
145
|
-
config.
|
|
157
|
+
function getConfigStrategy() {
|
|
158
|
+
return config.get("configStrategy") ?? "stealth";
|
|
146
159
|
}
|
|
147
160
|
function clearConfig() {
|
|
148
161
|
config.clear();
|
|
@@ -154,20 +167,21 @@ function getCustomTemplates() {
|
|
|
154
167
|
return config.get("customTemplates") ?? {};
|
|
155
168
|
}
|
|
156
169
|
|
|
157
|
-
function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations,
|
|
170
|
+
function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedSettings) {
|
|
158
171
|
const baseTemplate = getBaseTemplate(template);
|
|
159
172
|
const base = {
|
|
160
173
|
name,
|
|
161
174
|
template,
|
|
162
175
|
projectType,
|
|
163
176
|
libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
|
|
164
|
-
packageManager: "pnpm",
|
|
165
|
-
pnpmManageVersions: true,
|
|
166
|
-
nodeVersion: "latest",
|
|
167
|
-
linter:
|
|
168
|
-
formatter:
|
|
177
|
+
packageManager: inheritedSettings?.packageManager ?? "pnpm",
|
|
178
|
+
pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
|
|
179
|
+
nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
|
|
180
|
+
linter: inheritedSettings?.linter ?? "oxlint",
|
|
181
|
+
formatter: inheritedSettings?.formatter ?? "prettier",
|
|
169
182
|
// Libraries get vitest by default, apps don't
|
|
170
|
-
testing: projectType === "library" ? "vitest" : "none"
|
|
183
|
+
testing: projectType === "library" ? "vitest" : "none",
|
|
184
|
+
configStrategy: getConfigStrategy()
|
|
171
185
|
};
|
|
172
186
|
if (baseTemplate === "r3f" && integrations) {
|
|
173
187
|
return {
|
|
@@ -199,7 +213,22 @@ function getDefaultProjectName(template) {
|
|
|
199
213
|
return `react-three-${generateRandomName()}`;
|
|
200
214
|
}
|
|
201
215
|
}
|
|
202
|
-
async function promptForR3fIntegrations() {
|
|
216
|
+
async function promptForR3fIntegrations(presets) {
|
|
217
|
+
const initialValues = [];
|
|
218
|
+
if (presets) {
|
|
219
|
+
if (presets.drei) initialValues.push("drei");
|
|
220
|
+
if (presets.handle) initialValues.push("handle");
|
|
221
|
+
if (presets.leva) initialValues.push("leva");
|
|
222
|
+
if (presets.postprocessing) initialValues.push("postprocessing");
|
|
223
|
+
if (presets.rapier) initialValues.push("rapier");
|
|
224
|
+
if (presets.xr) initialValues.push("xr");
|
|
225
|
+
if (presets.uikit) initialValues.push("uikit");
|
|
226
|
+
if (presets.offscreen) initialValues.push("offscreen");
|
|
227
|
+
if (presets.zustand) initialValues.push("zustand");
|
|
228
|
+
if (presets.koota) initialValues.push("koota");
|
|
229
|
+
if (presets.triplex) initialValues.push("triplex");
|
|
230
|
+
if (presets.viverse) initialValues.push("viverse");
|
|
231
|
+
}
|
|
203
232
|
const selected = await p.multiselect({
|
|
204
233
|
message: "R3F integrations",
|
|
205
234
|
options: [
|
|
@@ -216,7 +245,7 @@ async function promptForR3fIntegrations() {
|
|
|
216
245
|
{ value: "triplex", label: "Triplex" },
|
|
217
246
|
{ value: "viverse", label: "Viverse" }
|
|
218
247
|
],
|
|
219
|
-
initialValues: ["drei"],
|
|
248
|
+
initialValues: initialValues.length > 0 ? initialValues : ["drei"],
|
|
220
249
|
required: false
|
|
221
250
|
});
|
|
222
251
|
if (p.isCancel(selected)) {
|
|
@@ -225,7 +254,7 @@ async function promptForR3fIntegrations() {
|
|
|
225
254
|
}
|
|
226
255
|
return selected;
|
|
227
256
|
}
|
|
228
|
-
async function promptForCustomization(template, name, projectType, integrations,
|
|
257
|
+
async function promptForCustomization(template, name, projectType, integrations, inheritedSettings, presets) {
|
|
229
258
|
let libraryBundler;
|
|
230
259
|
if (projectType === "library") {
|
|
231
260
|
const bundler = await p.select({
|
|
@@ -234,7 +263,7 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
234
263
|
{ value: "unbuild", label: "unbuild", hint: "unjs, simple config" },
|
|
235
264
|
{ value: "tsdown", label: "tsdown", hint: "fast, esbuild-based" }
|
|
236
265
|
],
|
|
237
|
-
initialValue: "unbuild"
|
|
266
|
+
initialValue: presets?.bundler ?? "unbuild"
|
|
238
267
|
});
|
|
239
268
|
if (p.isCancel(bundler)) {
|
|
240
269
|
p.cancel("Operation cancelled.");
|
|
@@ -242,64 +271,57 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
242
271
|
}
|
|
243
272
|
libraryBundler = bundler;
|
|
244
273
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
if (p.isCancel(nodeVersion)) {
|
|
257
|
-
p.cancel("Operation cancelled.");
|
|
258
|
-
process.exit(0);
|
|
259
|
-
}
|
|
260
|
-
const packageManager = await p.select({
|
|
261
|
-
message: "Package manager",
|
|
262
|
-
options: [
|
|
263
|
-
{ value: "pnpm", label: "pnpm" },
|
|
264
|
-
{ value: "npm", label: "npm" },
|
|
265
|
-
{ value: "yarn", label: "yarn" },
|
|
266
|
-
{ value: "custom", label: "Other (custom)" }
|
|
267
|
-
],
|
|
268
|
-
initialValue: "pnpm"
|
|
269
|
-
});
|
|
270
|
-
if (p.isCancel(packageManager)) {
|
|
271
|
-
p.cancel("Operation cancelled.");
|
|
272
|
-
process.exit(0);
|
|
273
|
-
}
|
|
274
|
-
let finalPackageManager = packageManager;
|
|
275
|
-
if (packageManager === "custom") {
|
|
276
|
-
const customPm = await p.text({
|
|
277
|
-
message: "Enter package manager command",
|
|
274
|
+
let nodeVersion = inheritedSettings?.nodeVersion ?? presets?.nodeVersion ?? "latest";
|
|
275
|
+
let finalPackageManager = inheritedSettings?.packageManager ?? presets?.packageManager ?? "pnpm";
|
|
276
|
+
let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
|
|
277
|
+
if (!inheritedSettings?.nodeVersion) {
|
|
278
|
+
const nodeVersionInput = await p.text({
|
|
279
|
+
message: "Node.js version",
|
|
280
|
+
placeholder: presets?.nodeVersion ?? "latest",
|
|
281
|
+
defaultValue: presets?.nodeVersion ?? "latest",
|
|
278
282
|
validate: (value) => {
|
|
279
283
|
if (!value.length) return "Required";
|
|
284
|
+
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
285
|
+
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
286
|
+
}
|
|
280
287
|
}
|
|
281
288
|
});
|
|
282
|
-
if (p.isCancel(
|
|
289
|
+
if (p.isCancel(nodeVersionInput)) {
|
|
283
290
|
p.cancel("Operation cancelled.");
|
|
284
291
|
process.exit(0);
|
|
285
292
|
}
|
|
286
|
-
|
|
293
|
+
nodeVersion = nodeVersionInput;
|
|
287
294
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
if (!inheritedSettings?.packageManager) {
|
|
296
|
+
const packageManager = await p.select({
|
|
297
|
+
message: "Package manager",
|
|
298
|
+
options: [
|
|
299
|
+
{ value: "pnpm", label: "pnpm" },
|
|
300
|
+
{ value: "npm", label: "npm" },
|
|
301
|
+
{ value: "yarn", label: "yarn" }
|
|
302
|
+
],
|
|
303
|
+
initialValue: presets?.packageManager ?? "pnpm"
|
|
293
304
|
});
|
|
294
|
-
if (p.isCancel(
|
|
305
|
+
if (p.isCancel(packageManager)) {
|
|
295
306
|
p.cancel("Operation cancelled.");
|
|
296
307
|
process.exit(0);
|
|
297
308
|
}
|
|
298
|
-
|
|
309
|
+
finalPackageManager = packageManager;
|
|
310
|
+
if (packageManager === "pnpm") {
|
|
311
|
+
const managePnpm = await p.confirm({
|
|
312
|
+
message: "Enable manage-package-manager-versions?",
|
|
313
|
+
initialValue: presets?.pnpmManageVersions ?? true
|
|
314
|
+
});
|
|
315
|
+
if (p.isCancel(managePnpm)) {
|
|
316
|
+
p.cancel("Operation cancelled.");
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
pnpmManageVersions = managePnpm;
|
|
320
|
+
}
|
|
299
321
|
}
|
|
300
|
-
let linter =
|
|
301
|
-
let formatter =
|
|
302
|
-
if (!
|
|
322
|
+
let linter = inheritedSettings?.linter ?? presets?.linter ?? "oxlint";
|
|
323
|
+
let formatter = inheritedSettings?.formatter ?? presets?.formatter ?? "prettier";
|
|
324
|
+
if (!inheritedSettings?.linter) {
|
|
303
325
|
const linterChoice = await p.select({
|
|
304
326
|
message: "Linter",
|
|
305
327
|
options: [
|
|
@@ -307,7 +329,7 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
307
329
|
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
308
330
|
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
309
331
|
],
|
|
310
|
-
initialValue: "oxlint"
|
|
332
|
+
initialValue: presets?.linter ?? "oxlint"
|
|
311
333
|
});
|
|
312
334
|
if (p.isCancel(linterChoice)) {
|
|
313
335
|
p.cancel("Operation cancelled.");
|
|
@@ -315,15 +337,15 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
315
337
|
}
|
|
316
338
|
linter = linterChoice;
|
|
317
339
|
}
|
|
318
|
-
if (!
|
|
340
|
+
if (!inheritedSettings?.formatter) {
|
|
319
341
|
const formatterChoice = await p.select({
|
|
320
342
|
message: "Formatter",
|
|
321
343
|
options: [
|
|
344
|
+
{ value: "prettier", label: "Prettier", hint: "widely adopted" },
|
|
322
345
|
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
323
|
-
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
324
346
|
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
325
347
|
],
|
|
326
|
-
initialValue: "
|
|
348
|
+
initialValue: presets?.formatter ?? "prettier"
|
|
327
349
|
});
|
|
328
350
|
if (p.isCancel(formatterChoice)) {
|
|
329
351
|
p.cancel("Operation cancelled.");
|
|
@@ -355,6 +377,18 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
355
377
|
p.cancel("Operation cancelled.");
|
|
356
378
|
process.exit(0);
|
|
357
379
|
}
|
|
380
|
+
const configStrategyChoice = await p.select({
|
|
381
|
+
message: "Config strategy",
|
|
382
|
+
options: [
|
|
383
|
+
{ value: "stealth", label: "stealth", hint: "configs in .config/" },
|
|
384
|
+
{ value: "root", label: "root", hint: "configs at project root" }
|
|
385
|
+
],
|
|
386
|
+
initialValue: getConfigStrategy()
|
|
387
|
+
});
|
|
388
|
+
if (p.isCancel(configStrategyChoice)) {
|
|
389
|
+
p.cancel("Operation cancelled.");
|
|
390
|
+
process.exit(0);
|
|
391
|
+
}
|
|
358
392
|
const baseTemplate = getBaseTemplate(template);
|
|
359
393
|
const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
|
|
360
394
|
const base = {
|
|
@@ -367,7 +401,8 @@ async function promptForCustomization(template, name, projectType, integrations,
|
|
|
367
401
|
pnpmManageVersions,
|
|
368
402
|
linter,
|
|
369
403
|
formatter,
|
|
370
|
-
testing
|
|
404
|
+
testing,
|
|
405
|
+
configStrategy: configStrategyChoice
|
|
371
406
|
};
|
|
372
407
|
if (baseTemplate === "r3f" && integrations) {
|
|
373
408
|
return {
|
|
@@ -412,14 +447,14 @@ function getDefaultMonorepoOptions(name) {
|
|
|
412
447
|
pnpmManageVersions: true,
|
|
413
448
|
nodeVersion: "latest",
|
|
414
449
|
linter: "oxlint",
|
|
415
|
-
formatter: "
|
|
450
|
+
formatter: "prettier"
|
|
416
451
|
};
|
|
417
452
|
}
|
|
418
|
-
async function promptForMonorepoCustomization(name) {
|
|
453
|
+
async function promptForMonorepoCustomization(name, presets) {
|
|
419
454
|
const nodeVersion = await p.text({
|
|
420
455
|
message: "Node.js version",
|
|
421
|
-
placeholder: "latest",
|
|
422
|
-
defaultValue: "latest",
|
|
456
|
+
placeholder: presets?.nodeVersion ?? "latest",
|
|
457
|
+
defaultValue: presets?.nodeVersion ?? "latest",
|
|
423
458
|
validate: (value) => {
|
|
424
459
|
if (!value.length) return "Required";
|
|
425
460
|
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
@@ -433,7 +468,7 @@ async function promptForMonorepoCustomization(name) {
|
|
|
433
468
|
}
|
|
434
469
|
const managePnpm = await p.confirm({
|
|
435
470
|
message: "Enable manage-package-manager-versions?",
|
|
436
|
-
initialValue: true
|
|
471
|
+
initialValue: presets?.pnpmManageVersions ?? true
|
|
437
472
|
});
|
|
438
473
|
if (p.isCancel(managePnpm)) {
|
|
439
474
|
p.cancel("Operation cancelled.");
|
|
@@ -446,7 +481,7 @@ async function promptForMonorepoCustomization(name) {
|
|
|
446
481
|
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
447
482
|
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
448
483
|
],
|
|
449
|
-
initialValue: "oxlint"
|
|
484
|
+
initialValue: presets?.linter ?? "oxlint"
|
|
450
485
|
});
|
|
451
486
|
if (p.isCancel(linter)) {
|
|
452
487
|
p.cancel("Operation cancelled.");
|
|
@@ -455,11 +490,11 @@ async function promptForMonorepoCustomization(name) {
|
|
|
455
490
|
const formatter = await p.select({
|
|
456
491
|
message: "Formatter",
|
|
457
492
|
options: [
|
|
493
|
+
{ value: "prettier", label: "Prettier", hint: "widely adopted" },
|
|
458
494
|
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
459
|
-
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
460
495
|
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
461
496
|
],
|
|
462
|
-
initialValue: "
|
|
497
|
+
initialValue: presets?.formatter ?? "prettier"
|
|
463
498
|
});
|
|
464
499
|
if (p.isCancel(formatter)) {
|
|
465
500
|
p.cancel("Operation cancelled.");
|
|
@@ -475,8 +510,15 @@ async function promptForMonorepoCustomization(name) {
|
|
|
475
510
|
formatter
|
|
476
511
|
};
|
|
477
512
|
}
|
|
478
|
-
async function promptForMonorepo(workspaceName) {
|
|
513
|
+
async function promptForMonorepo(workspaceName, presets) {
|
|
479
514
|
const defaultOptions = getDefaultMonorepoOptions(workspaceName);
|
|
515
|
+
if (presets) {
|
|
516
|
+
if (presets.linter) defaultOptions.linter = presets.linter;
|
|
517
|
+
if (presets.formatter) defaultOptions.formatter = presets.formatter;
|
|
518
|
+
if (presets.nodeVersion) defaultOptions.nodeVersion = presets.nodeVersion;
|
|
519
|
+
if (presets.pnpmManageVersions !== void 0)
|
|
520
|
+
defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
|
|
521
|
+
}
|
|
480
522
|
p.note(
|
|
481
523
|
formatMonorepoConfigSummary({
|
|
482
524
|
name: defaultOptions.name,
|
|
@@ -484,24 +526,28 @@ async function promptForMonorepo(workspaceName) {
|
|
|
484
526
|
packageManager: defaultOptions.packageManager ?? "pnpm",
|
|
485
527
|
pnpmManageVersions: defaultOptions.pnpmManageVersions,
|
|
486
528
|
linter: defaultOptions.linter ?? "oxlint",
|
|
487
|
-
formatter: defaultOptions.formatter ?? "
|
|
529
|
+
formatter: defaultOptions.formatter ?? "prettier"
|
|
488
530
|
}),
|
|
489
531
|
"Workspace Configuration"
|
|
490
532
|
);
|
|
491
|
-
const proceed = await p.
|
|
533
|
+
const proceed = await p.select({
|
|
492
534
|
message: "Proceed with these settings?",
|
|
493
|
-
|
|
535
|
+
options: [
|
|
536
|
+
{ value: "continue", label: "Yes, continue" },
|
|
537
|
+
{ value: "customize", label: "No, customize settings" }
|
|
538
|
+
],
|
|
539
|
+
initialValue: "continue"
|
|
494
540
|
});
|
|
495
541
|
if (p.isCancel(proceed)) {
|
|
496
542
|
p.cancel("Operation cancelled.");
|
|
497
543
|
process.exit(0);
|
|
498
544
|
}
|
|
499
|
-
if (proceed) {
|
|
545
|
+
if (proceed === "continue") {
|
|
500
546
|
return defaultOptions;
|
|
501
547
|
}
|
|
502
|
-
return promptForMonorepoCustomization(workspaceName);
|
|
548
|
+
return promptForMonorepoCustomization(workspaceName, presets);
|
|
503
549
|
}
|
|
504
|
-
async function promptForOptions(name) {
|
|
550
|
+
async function promptForOptions(name, presets) {
|
|
505
551
|
let projectName = name;
|
|
506
552
|
if (!projectName) {
|
|
507
553
|
const nameResult = await p.text({
|
|
@@ -525,30 +571,36 @@ async function promptForOptions(name) {
|
|
|
525
571
|
{ value: "library", label: "Library" },
|
|
526
572
|
{ value: "monorepo", label: "Monorepo" }
|
|
527
573
|
],
|
|
528
|
-
initialValue: "app"
|
|
574
|
+
initialValue: presets?.type ?? "app"
|
|
529
575
|
});
|
|
530
576
|
if (p.isCancel(projectType)) {
|
|
531
577
|
p.cancel("Operation cancelled.");
|
|
532
578
|
process.exit(0);
|
|
533
579
|
}
|
|
534
580
|
if (projectType === "monorepo") {
|
|
535
|
-
return promptForMonorepo(projectName);
|
|
581
|
+
return promptForMonorepo(projectName, presets);
|
|
536
582
|
}
|
|
537
|
-
return promptForPackageOptions(
|
|
583
|
+
return promptForPackageOptions(
|
|
584
|
+
projectName,
|
|
585
|
+
projectType,
|
|
586
|
+
void 0,
|
|
587
|
+
presets
|
|
588
|
+
);
|
|
538
589
|
}
|
|
539
|
-
function customTemplateToOptions(customTemplate, name, projectType) {
|
|
590
|
+
function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
|
|
540
591
|
const baseTemplate = customTemplate.baseTemplate;
|
|
541
592
|
const template = baseTemplate;
|
|
542
593
|
const base = {
|
|
543
594
|
name,
|
|
544
595
|
template,
|
|
545
596
|
projectType,
|
|
546
|
-
packageManager: "pnpm",
|
|
547
|
-
pnpmManageVersions: true,
|
|
548
|
-
nodeVersion: "latest",
|
|
549
|
-
linter: customTemplate.linter,
|
|
550
|
-
formatter: customTemplate.formatter,
|
|
551
|
-
testing: customTemplate.testing
|
|
597
|
+
packageManager: inheritedSettings?.packageManager ?? "pnpm",
|
|
598
|
+
pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
|
|
599
|
+
nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
|
|
600
|
+
linter: inheritedSettings?.linter ?? customTemplate.linter,
|
|
601
|
+
formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
|
|
602
|
+
testing: customTemplate.testing,
|
|
603
|
+
configStrategy: customTemplate.configStrategy ?? getConfigStrategy()
|
|
552
604
|
};
|
|
553
605
|
if (baseTemplate === "r3f" && customTemplate.integrations) {
|
|
554
606
|
const integrations = customTemplate.integrations;
|
|
@@ -570,7 +622,17 @@ function customTemplateToOptions(customTemplate, name, projectType) {
|
|
|
570
622
|
}
|
|
571
623
|
return base;
|
|
572
624
|
}
|
|
573
|
-
|
|
625
|
+
function presetsToInheritedSettings(presets) {
|
|
626
|
+
if (!presets) return void 0;
|
|
627
|
+
return {
|
|
628
|
+
linter: presets.linter,
|
|
629
|
+
formatter: presets.formatter,
|
|
630
|
+
packageManager: presets.packageManager,
|
|
631
|
+
nodeVersion: presets.nodeVersion,
|
|
632
|
+
pnpmManageVersions: presets.pnpmManageVersions
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
async function promptForPackageOptions(projectName, projectType, inheritedSettings, presets) {
|
|
574
636
|
const builtInOptions = [
|
|
575
637
|
{ value: "vanilla", label: "Vanilla" },
|
|
576
638
|
{ value: "react", label: "React" },
|
|
@@ -586,7 +648,7 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
|
|
|
586
648
|
const templateSelection = await p.select({
|
|
587
649
|
message: "Select a template",
|
|
588
650
|
options: allOptions,
|
|
589
|
-
initialValue: "vanilla"
|
|
651
|
+
initialValue: presets?.template ?? "vanilla"
|
|
590
652
|
});
|
|
591
653
|
if (p.isCancel(templateSelection)) {
|
|
592
654
|
p.cancel("Operation cancelled.");
|
|
@@ -596,24 +658,27 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
|
|
|
596
658
|
if (selection.startsWith("custom:")) {
|
|
597
659
|
const customName = selection.slice(7);
|
|
598
660
|
const customTemplate = customTemplates[customName];
|
|
599
|
-
const defaultOptions2 = customTemplateToOptions(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
p.
|
|
608
|
-
const proceed2 = await p.confirm({
|
|
661
|
+
const defaultOptions2 = customTemplateToOptions(
|
|
662
|
+
customTemplate,
|
|
663
|
+
projectName,
|
|
664
|
+
projectType,
|
|
665
|
+
inheritedSettings
|
|
666
|
+
);
|
|
667
|
+
const configTitle2 = inheritedSettings ? `Template: ${customName} (using workspace settings)` : `Template: ${customName}`;
|
|
668
|
+
p.note(formatConfigSummary(defaultOptions2, inheritedSettings), configTitle2);
|
|
669
|
+
const proceed2 = await p.select({
|
|
609
670
|
message: "Proceed with these settings?",
|
|
610
|
-
|
|
671
|
+
options: [
|
|
672
|
+
{ value: "continue", label: "Yes, continue" },
|
|
673
|
+
{ value: "customize", label: "No, customize settings" }
|
|
674
|
+
],
|
|
675
|
+
initialValue: "continue"
|
|
611
676
|
});
|
|
612
677
|
if (p.isCancel(proceed2)) {
|
|
613
678
|
p.cancel("Operation cancelled.");
|
|
614
679
|
process.exit(0);
|
|
615
680
|
}
|
|
616
|
-
if (proceed2) {
|
|
681
|
+
if (proceed2 === "continue") {
|
|
617
682
|
return defaultOptions2;
|
|
618
683
|
}
|
|
619
684
|
return promptForCustomization(
|
|
@@ -621,37 +686,48 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
|
|
|
621
686
|
projectName,
|
|
622
687
|
projectType,
|
|
623
688
|
customTemplate.integrations,
|
|
624
|
-
|
|
689
|
+
inheritedSettings
|
|
625
690
|
);
|
|
626
691
|
}
|
|
627
692
|
const template = selection;
|
|
628
693
|
const baseTemplate = getBaseTemplate(template);
|
|
629
694
|
let integrations;
|
|
630
695
|
if (baseTemplate === "r3f") {
|
|
631
|
-
integrations = await promptForR3fIntegrations();
|
|
696
|
+
integrations = await promptForR3fIntegrations(presets);
|
|
632
697
|
}
|
|
633
698
|
const defaultOptions = getDefaultOptions(
|
|
634
699
|
template,
|
|
635
700
|
projectName,
|
|
636
701
|
projectType,
|
|
637
|
-
|
|
702
|
+
presets?.bundler,
|
|
638
703
|
integrations,
|
|
639
|
-
|
|
704
|
+
inheritedSettings ?? presetsToInheritedSettings(presets)
|
|
640
705
|
);
|
|
641
|
-
const configTitle =
|
|
642
|
-
p.note(formatConfigSummary(defaultOptions), configTitle);
|
|
643
|
-
const proceed = await p.
|
|
706
|
+
const configTitle = inheritedSettings ? "Template Configuration (using workspace settings)" : "Template Configuration";
|
|
707
|
+
p.note(formatConfigSummary(defaultOptions, inheritedSettings), configTitle);
|
|
708
|
+
const proceed = await p.select({
|
|
644
709
|
message: "Proceed with these settings?",
|
|
645
|
-
|
|
710
|
+
options: [
|
|
711
|
+
{ value: "continue", label: "Yes, continue" },
|
|
712
|
+
{ value: "customize", label: "No, customize settings" }
|
|
713
|
+
],
|
|
714
|
+
initialValue: "continue"
|
|
646
715
|
});
|
|
647
716
|
if (p.isCancel(proceed)) {
|
|
648
717
|
p.cancel("Operation cancelled.");
|
|
649
718
|
process.exit(0);
|
|
650
719
|
}
|
|
651
|
-
if (proceed) {
|
|
720
|
+
if (proceed === "continue") {
|
|
652
721
|
return defaultOptions;
|
|
653
722
|
}
|
|
654
|
-
return promptForCustomization(
|
|
723
|
+
return promptForCustomization(
|
|
724
|
+
template,
|
|
725
|
+
projectName,
|
|
726
|
+
projectType,
|
|
727
|
+
integrations,
|
|
728
|
+
inheritedSettings,
|
|
729
|
+
presets
|
|
730
|
+
);
|
|
655
731
|
}
|
|
656
732
|
|
|
657
733
|
async function checkAnyExists(paths) {
|
|
@@ -699,8 +775,586 @@ async function validateWorkspace(monorepoRoot) {
|
|
|
699
775
|
return { valid: errors.length === 0, errors };
|
|
700
776
|
}
|
|
701
777
|
|
|
778
|
+
async function detectCurrentConfig(root) {
|
|
779
|
+
let name = root.split(/[/\\]/).pop() ?? "workspace";
|
|
780
|
+
try {
|
|
781
|
+
const pkgPath = join(root, "package.json");
|
|
782
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
783
|
+
const pkgJson = JSON.parse(content);
|
|
784
|
+
if (pkgJson.name) {
|
|
785
|
+
name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
}
|
|
789
|
+
const tooling = await detectTooling(root);
|
|
790
|
+
return {
|
|
791
|
+
name,
|
|
792
|
+
linter: tooling.linter ?? "oxlint",
|
|
793
|
+
formatter: tooling.formatter ?? "prettier",
|
|
794
|
+
packageManager: "pnpm"
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function generateExpectedFiles(config) {
|
|
798
|
+
const { name, linter, formatter, packageManager } = config;
|
|
799
|
+
const aiFilesMap = {};
|
|
800
|
+
generateAiFiles(aiFilesMap, {
|
|
801
|
+
name,
|
|
802
|
+
packageManager,
|
|
803
|
+
linter,
|
|
804
|
+
formatter,
|
|
805
|
+
isMonorepo: true,
|
|
806
|
+
platforms: ALL_AI_PLATFORMS
|
|
807
|
+
});
|
|
808
|
+
const vscodeFiles = {};
|
|
809
|
+
generateVscodeFiles(vscodeFiles, linter, formatter);
|
|
810
|
+
const configPackages = {};
|
|
811
|
+
generateTypescriptConfigPackage(configPackages);
|
|
812
|
+
if (linter === "oxlint") {
|
|
813
|
+
generateOxlintConfigPackage(configPackages);
|
|
814
|
+
} else if (linter === "eslint") {
|
|
815
|
+
generateEslintConfigPackage(configPackages);
|
|
816
|
+
}
|
|
817
|
+
if (formatter === "oxfmt") {
|
|
818
|
+
generateOxfmtConfigPackage(configPackages);
|
|
819
|
+
} else if (formatter === "prettier") {
|
|
820
|
+
generatePrettierConfigPackage(configPackages);
|
|
821
|
+
}
|
|
822
|
+
const workspaceConfig = {};
|
|
823
|
+
const rootConfig = {};
|
|
824
|
+
rootConfig[".gitignore"] = {
|
|
825
|
+
type: "text",
|
|
826
|
+
content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
|
|
827
|
+
};
|
|
828
|
+
rootConfig[".gitattributes"] = {
|
|
829
|
+
type: "text",
|
|
830
|
+
content: `* text=auto eol=lf
|
|
831
|
+
*.{cmd,[cC][mM][dD]} text eol=crlf
|
|
832
|
+
*.{bat,[bB][aA][tT]} text eol=crlf
|
|
833
|
+
`
|
|
834
|
+
};
|
|
835
|
+
if (linter === "biome" || formatter === "biome") {
|
|
836
|
+
const biomeConfig = {
|
|
837
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
838
|
+
vcs: {
|
|
839
|
+
enabled: true,
|
|
840
|
+
clientKind: "git",
|
|
841
|
+
useIgnoreFile: true
|
|
842
|
+
},
|
|
843
|
+
linter: {
|
|
844
|
+
enabled: linter === "biome",
|
|
845
|
+
rules: {
|
|
846
|
+
recommended: true
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
formatter: {
|
|
850
|
+
enabled: formatter === "biome"
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
rootConfig["biome.json"] = {
|
|
854
|
+
type: "text",
|
|
855
|
+
content: JSON.stringify(biomeConfig, null, 2)
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
"ai-files": aiFilesMap,
|
|
860
|
+
vscode: vscodeFiles,
|
|
861
|
+
"config-packages": configPackages,
|
|
862
|
+
"workspace-config": workspaceConfig,
|
|
863
|
+
"root-config": rootConfig
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
async function fileExists$1(path) {
|
|
867
|
+
try {
|
|
868
|
+
await access(path, constants$1.F_OK);
|
|
869
|
+
return true;
|
|
870
|
+
} catch {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async function compareWithDisk(expected, root) {
|
|
875
|
+
const categoryLabels = {
|
|
876
|
+
"ai-files": "AI Files",
|
|
877
|
+
vscode: "VS Code",
|
|
878
|
+
"config-packages": "Config Packages",
|
|
879
|
+
"workspace-config": "Workspace Config",
|
|
880
|
+
"root-config": "Root Config"
|
|
881
|
+
};
|
|
882
|
+
const categories = [];
|
|
883
|
+
for (const [category, files] of Object.entries(expected)) {
|
|
884
|
+
const changes = [];
|
|
885
|
+
for (const [filePath, file] of Object.entries(files)) {
|
|
886
|
+
if (file.type !== "text") continue;
|
|
887
|
+
const fullPath = join(root, filePath);
|
|
888
|
+
const newContent = file.content;
|
|
889
|
+
if (await fileExists$1(fullPath)) {
|
|
890
|
+
const currentContent = await readFile(fullPath, "utf-8");
|
|
891
|
+
if (currentContent === newContent) {
|
|
892
|
+
changes.push({
|
|
893
|
+
path: filePath,
|
|
894
|
+
status: "unchanged",
|
|
895
|
+
currentContent,
|
|
896
|
+
newContent
|
|
897
|
+
});
|
|
898
|
+
} else {
|
|
899
|
+
changes.push({
|
|
900
|
+
path: filePath,
|
|
901
|
+
status: "modified",
|
|
902
|
+
currentContent,
|
|
903
|
+
newContent
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
} else {
|
|
907
|
+
changes.push({
|
|
908
|
+
path: filePath,
|
|
909
|
+
status: "added",
|
|
910
|
+
newContent
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (changes.length === 0) continue;
|
|
915
|
+
const hasUserModifications = changes.some((c) => c.status === "modified");
|
|
916
|
+
categories.push({
|
|
917
|
+
category,
|
|
918
|
+
label: categoryLabels[category],
|
|
919
|
+
changes,
|
|
920
|
+
hasUserModifications
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
return categories;
|
|
924
|
+
}
|
|
925
|
+
async function getWorkspaceConfigUpdates(root) {
|
|
926
|
+
const workspacePath = join(root, "pnpm-workspace.yaml");
|
|
927
|
+
const changes = [];
|
|
928
|
+
let currentContent = "";
|
|
929
|
+
let exists = false;
|
|
930
|
+
try {
|
|
931
|
+
currentContent = await readFile(workspacePath, "utf-8");
|
|
932
|
+
exists = true;
|
|
933
|
+
} catch {
|
|
934
|
+
}
|
|
935
|
+
if (!exists) {
|
|
936
|
+
const newContent = `manage-package-manager-versions: true
|
|
937
|
+
|
|
938
|
+
packages:
|
|
939
|
+
- ".config/*"
|
|
940
|
+
- "apps/*"
|
|
941
|
+
- "packages/*"
|
|
942
|
+
|
|
943
|
+
onlyBuiltDependencies:
|
|
944
|
+
- esbuild
|
|
945
|
+
`;
|
|
946
|
+
changes.push({
|
|
947
|
+
path: "pnpm-workspace.yaml",
|
|
948
|
+
status: "added",
|
|
949
|
+
newContent
|
|
950
|
+
});
|
|
951
|
+
return changes;
|
|
952
|
+
}
|
|
953
|
+
let updatedContent = currentContent;
|
|
954
|
+
let needsUpdate = false;
|
|
955
|
+
if (!currentContent.includes("manage-package-manager-versions")) {
|
|
956
|
+
updatedContent = `manage-package-manager-versions: true
|
|
957
|
+
|
|
958
|
+
${updatedContent}`;
|
|
959
|
+
needsUpdate = true;
|
|
960
|
+
}
|
|
961
|
+
if (!currentContent.includes("onlyBuiltDependencies")) {
|
|
962
|
+
updatedContent = `${updatedContent.trimEnd()}
|
|
963
|
+
|
|
964
|
+
onlyBuiltDependencies:
|
|
965
|
+
- esbuild
|
|
966
|
+
`;
|
|
967
|
+
needsUpdate = true;
|
|
968
|
+
}
|
|
969
|
+
if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
|
|
970
|
+
const lines = updatedContent.split("\n");
|
|
971
|
+
const packagesIndex = lines.findIndex(
|
|
972
|
+
(line) => line.trim().startsWith("packages:")
|
|
973
|
+
);
|
|
974
|
+
if (packagesIndex !== -1) {
|
|
975
|
+
lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
|
|
976
|
+
updatedContent = lines.join("\n");
|
|
977
|
+
needsUpdate = true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
if (needsUpdate) {
|
|
981
|
+
changes.push({
|
|
982
|
+
path: "pnpm-workspace.yaml",
|
|
983
|
+
status: "modified",
|
|
984
|
+
currentContent,
|
|
985
|
+
newContent: updatedContent
|
|
986
|
+
});
|
|
987
|
+
} else {
|
|
988
|
+
changes.push({
|
|
989
|
+
path: "pnpm-workspace.yaml",
|
|
990
|
+
status: "unchanged",
|
|
991
|
+
currentContent,
|
|
992
|
+
newContent: currentContent
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
return changes;
|
|
996
|
+
}
|
|
997
|
+
async function applyUpdates(changes, root) {
|
|
998
|
+
for (const change of changes) {
|
|
999
|
+
if (change.status === "unchanged") continue;
|
|
1000
|
+
const fullPath = join(root, change.path);
|
|
1001
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1002
|
+
await writeFile(fullPath, change.newContent);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
function formatFileChange(change) {
|
|
1006
|
+
const icon = change.status === "added" ? "+" : change.status === "modified" ? "~" : "=";
|
|
1007
|
+
return ` ${icon} ${change.path}`;
|
|
1008
|
+
}
|
|
1009
|
+
const LINTER_DEPS = {
|
|
1010
|
+
oxlint: "oxlint",
|
|
1011
|
+
eslint: "eslint",
|
|
1012
|
+
biome: "@biomejs/biome"
|
|
1013
|
+
};
|
|
1014
|
+
const FORMATTER_DEPS = {
|
|
1015
|
+
oxfmt: "oxfmt",
|
|
1016
|
+
prettier: "prettier",
|
|
1017
|
+
biome: "@biomejs/biome"
|
|
1018
|
+
};
|
|
1019
|
+
const LINTER_CONFIG_PACKAGES = {
|
|
1020
|
+
oxlint: "@config/oxlint",
|
|
1021
|
+
eslint: "@config/eslint",
|
|
1022
|
+
biome: null
|
|
1023
|
+
// biome uses root biome.json
|
|
1024
|
+
};
|
|
1025
|
+
const FORMATTER_CONFIG_PACKAGES = {
|
|
1026
|
+
oxfmt: "@config/oxfmt",
|
|
1027
|
+
prettier: "@config/prettier",
|
|
1028
|
+
biome: null
|
|
1029
|
+
// biome uses root biome.json
|
|
1030
|
+
};
|
|
1031
|
+
function needsMigration(current, target) {
|
|
1032
|
+
const linterChange = target.linter && target.linter !== current.linter;
|
|
1033
|
+
const formatterChange = target.formatter && target.formatter !== current.formatter;
|
|
1034
|
+
return linterChange || formatterChange || false;
|
|
1035
|
+
}
|
|
1036
|
+
async function getMigrationPlan(current, target, root) {
|
|
1037
|
+
const toLinter = target.linter ?? current.linter;
|
|
1038
|
+
const toFormatter = target.formatter ?? current.formatter;
|
|
1039
|
+
const changes = [];
|
|
1040
|
+
if (toLinter !== current.linter) {
|
|
1041
|
+
if (current.linter !== "biome") {
|
|
1042
|
+
changes.push({
|
|
1043
|
+
type: "remove-dir",
|
|
1044
|
+
path: `.config/${current.linter}`,
|
|
1045
|
+
description: `Remove @config/${current.linter} package`
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
if (toLinter !== "biome") {
|
|
1049
|
+
const files = {};
|
|
1050
|
+
if (toLinter === "oxlint") {
|
|
1051
|
+
generateOxlintConfigPackage(files);
|
|
1052
|
+
} else if (toLinter === "eslint") {
|
|
1053
|
+
generateEslintConfigPackage(files);
|
|
1054
|
+
}
|
|
1055
|
+
for (const [path, file] of Object.entries(files)) {
|
|
1056
|
+
if (file.type === "text") {
|
|
1057
|
+
changes.push({
|
|
1058
|
+
type: "add-file",
|
|
1059
|
+
path,
|
|
1060
|
+
description: `Add ${path}`,
|
|
1061
|
+
content: file.content
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (toLinter === "biome" && toFormatter === "biome") {
|
|
1067
|
+
changes.push({
|
|
1068
|
+
type: "add-file",
|
|
1069
|
+
path: "biome.json",
|
|
1070
|
+
description: "Add biome.json config",
|
|
1071
|
+
content: JSON.stringify(
|
|
1072
|
+
{
|
|
1073
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1074
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1075
|
+
linter: { enabled: true, rules: { recommended: true } },
|
|
1076
|
+
formatter: { enabled: true }
|
|
1077
|
+
},
|
|
1078
|
+
null,
|
|
1079
|
+
2
|
|
1080
|
+
)
|
|
1081
|
+
});
|
|
1082
|
+
} else if (toLinter === "biome" && toFormatter !== "biome") {
|
|
1083
|
+
changes.push({
|
|
1084
|
+
type: "add-file",
|
|
1085
|
+
path: "biome.json",
|
|
1086
|
+
description: "Add biome.json config (linter only)",
|
|
1087
|
+
content: JSON.stringify(
|
|
1088
|
+
{
|
|
1089
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1090
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1091
|
+
linter: { enabled: true, rules: { recommended: true } },
|
|
1092
|
+
formatter: { enabled: false }
|
|
1093
|
+
},
|
|
1094
|
+
null,
|
|
1095
|
+
2
|
|
1096
|
+
)
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
if (current.linter === "biome" && toLinter !== "biome" && current.formatter !== "biome" && toFormatter !== "biome") {
|
|
1100
|
+
changes.push({
|
|
1101
|
+
type: "remove-file",
|
|
1102
|
+
path: "biome.json",
|
|
1103
|
+
description: "Remove biome.json"
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (toFormatter !== current.formatter) {
|
|
1108
|
+
const formatterSameAsLinter = current.formatter === current.linter;
|
|
1109
|
+
if (current.formatter !== "biome" && !formatterSameAsLinter) {
|
|
1110
|
+
changes.push({
|
|
1111
|
+
type: "remove-dir",
|
|
1112
|
+
path: `.config/${current.formatter}`,
|
|
1113
|
+
description: `Remove @config/${current.formatter} package`
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
const newFormatterSameAsLinter = toFormatter === toLinter;
|
|
1117
|
+
if (toFormatter !== "biome" && !newFormatterSameAsLinter) {
|
|
1118
|
+
const files = {};
|
|
1119
|
+
if (toFormatter === "oxfmt") {
|
|
1120
|
+
generateOxfmtConfigPackage(files);
|
|
1121
|
+
} else if (toFormatter === "prettier") {
|
|
1122
|
+
generatePrettierConfigPackage(files);
|
|
1123
|
+
}
|
|
1124
|
+
for (const [path, file] of Object.entries(files)) {
|
|
1125
|
+
if (file.type === "text") {
|
|
1126
|
+
changes.push({
|
|
1127
|
+
type: "add-file",
|
|
1128
|
+
path,
|
|
1129
|
+
description: `Add ${path}`,
|
|
1130
|
+
content: file.content
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (toFormatter === "biome" && toLinter !== "biome") {
|
|
1136
|
+
changes.push({
|
|
1137
|
+
type: "add-file",
|
|
1138
|
+
path: "biome.json",
|
|
1139
|
+
description: "Add biome.json config (formatter only)",
|
|
1140
|
+
content: JSON.stringify(
|
|
1141
|
+
{
|
|
1142
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1143
|
+
vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
|
|
1144
|
+
linter: { enabled: false },
|
|
1145
|
+
formatter: { enabled: true }
|
|
1146
|
+
},
|
|
1147
|
+
null,
|
|
1148
|
+
2
|
|
1149
|
+
)
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (current.formatter === "biome" && toFormatter !== "biome" && current.linter !== "biome" && toLinter !== "biome") {
|
|
1153
|
+
changes.push({
|
|
1154
|
+
type: "remove-file",
|
|
1155
|
+
path: "biome.json",
|
|
1156
|
+
description: "Remove biome.json"
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
changes.push({
|
|
1161
|
+
type: "update-package-json",
|
|
1162
|
+
path: "package.json",
|
|
1163
|
+
description: "Update root package.json (devDependencies, scripts)"
|
|
1164
|
+
});
|
|
1165
|
+
const subPackageUpdates = await getSubPackageUpdates(
|
|
1166
|
+
root,
|
|
1167
|
+
current,
|
|
1168
|
+
toLinter,
|
|
1169
|
+
toFormatter
|
|
1170
|
+
);
|
|
1171
|
+
return {
|
|
1172
|
+
fromLinter: current.linter,
|
|
1173
|
+
toLinter,
|
|
1174
|
+
fromFormatter: current.formatter,
|
|
1175
|
+
toFormatter,
|
|
1176
|
+
changes,
|
|
1177
|
+
subPackageUpdates
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
async function getSubPackageUpdates(root, current, toLinter, toFormatter) {
|
|
1181
|
+
const updates = [];
|
|
1182
|
+
const workspacePath = join(root, "pnpm-workspace.yaml");
|
|
1183
|
+
let workspaceContent;
|
|
1184
|
+
try {
|
|
1185
|
+
workspaceContent = await readFile(workspacePath, "utf-8");
|
|
1186
|
+
} catch {
|
|
1187
|
+
return updates;
|
|
1188
|
+
}
|
|
1189
|
+
const packageGlobs = parseWorkspaceYamlContent(workspaceContent);
|
|
1190
|
+
for (const glob of packageGlobs) {
|
|
1191
|
+
if (glob.includes(".config")) continue;
|
|
1192
|
+
const baseDir = glob.replace(/\/\*$/, "").replace(/^["']|["']$/g, "");
|
|
1193
|
+
const basePath = join(root, baseDir);
|
|
1194
|
+
try {
|
|
1195
|
+
const entries = await readdir(basePath, { withFileTypes: true });
|
|
1196
|
+
for (const entry of entries) {
|
|
1197
|
+
if (!entry.isDirectory()) continue;
|
|
1198
|
+
const pkgJsonPath = join(basePath, entry.name, "package.json");
|
|
1199
|
+
try {
|
|
1200
|
+
const content = await readFile(pkgJsonPath, "utf-8");
|
|
1201
|
+
const pkg = JSON.parse(content);
|
|
1202
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1203
|
+
const remove = [];
|
|
1204
|
+
const add = [];
|
|
1205
|
+
const oldLinterPkg = LINTER_CONFIG_PACKAGES[current.linter];
|
|
1206
|
+
const newLinterPkg = LINTER_CONFIG_PACKAGES[toLinter];
|
|
1207
|
+
if (oldLinterPkg && oldLinterPkg !== newLinterPkg && devDeps[oldLinterPkg]) {
|
|
1208
|
+
remove.push(oldLinterPkg);
|
|
1209
|
+
}
|
|
1210
|
+
if (newLinterPkg && newLinterPkg !== oldLinterPkg && oldLinterPkg && devDeps[oldLinterPkg]) {
|
|
1211
|
+
add.push(newLinterPkg);
|
|
1212
|
+
}
|
|
1213
|
+
if (current.formatter !== current.linter) {
|
|
1214
|
+
const oldFormatterPkg = FORMATTER_CONFIG_PACKAGES[current.formatter];
|
|
1215
|
+
const newFormatterPkg = FORMATTER_CONFIG_PACKAGES[toFormatter];
|
|
1216
|
+
if (oldFormatterPkg && oldFormatterPkg !== newFormatterPkg && devDeps[oldFormatterPkg]) {
|
|
1217
|
+
remove.push(oldFormatterPkg);
|
|
1218
|
+
}
|
|
1219
|
+
if (newFormatterPkg && newFormatterPkg !== oldFormatterPkg && oldFormatterPkg && devDeps[oldFormatterPkg]) {
|
|
1220
|
+
add.push(newFormatterPkg);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (remove.length > 0 || add.length > 0) {
|
|
1224
|
+
updates.push({
|
|
1225
|
+
path: join(baseDir, entry.name, "package.json"),
|
|
1226
|
+
remove,
|
|
1227
|
+
add
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return updates;
|
|
1237
|
+
}
|
|
1238
|
+
async function applyMigration(plan, root) {
|
|
1239
|
+
for (const change of plan.changes) {
|
|
1240
|
+
if (change.type === "remove-dir") {
|
|
1241
|
+
const fullPath = join(root, change.path);
|
|
1242
|
+
try {
|
|
1243
|
+
await rm(fullPath, { recursive: true });
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
for (const change of plan.changes) {
|
|
1249
|
+
if (change.type === "remove-file") {
|
|
1250
|
+
const fullPath = join(root, change.path);
|
|
1251
|
+
try {
|
|
1252
|
+
await rm(fullPath);
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
for (const change of plan.changes) {
|
|
1258
|
+
if (change.type === "add-file" && change.content) {
|
|
1259
|
+
const fullPath = join(root, change.path);
|
|
1260
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1261
|
+
await writeFile(fullPath, change.content);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
await updateRootPackageJson(root, plan);
|
|
1265
|
+
for (const update of plan.subPackageUpdates) {
|
|
1266
|
+
await updateSubPackageJson(root, update);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function updateRootPackageJson(root, plan) {
|
|
1270
|
+
const pkgPath = join(root, "package.json");
|
|
1271
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
1272
|
+
const pkg = JSON.parse(content);
|
|
1273
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1274
|
+
const oldLinterDep = LINTER_DEPS[plan.fromLinter];
|
|
1275
|
+
delete devDeps[oldLinterDep];
|
|
1276
|
+
if (plan.fromFormatter !== plan.fromLinter) {
|
|
1277
|
+
const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
|
|
1278
|
+
delete devDeps[oldFormatterDep];
|
|
1279
|
+
}
|
|
1280
|
+
const newLinterDep = LINTER_DEPS[plan.toLinter];
|
|
1281
|
+
if (plan.toLinter === "oxlint") {
|
|
1282
|
+
devDeps[newLinterDep] = "^1.36.0";
|
|
1283
|
+
} else if (plan.toLinter === "eslint") {
|
|
1284
|
+
devDeps[newLinterDep] = "^9.17.0";
|
|
1285
|
+
} else if (plan.toLinter === "biome") {
|
|
1286
|
+
devDeps[newLinterDep] = "^1.9.4";
|
|
1287
|
+
}
|
|
1288
|
+
if (plan.toFormatter !== plan.toLinter) {
|
|
1289
|
+
const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
|
|
1290
|
+
if (plan.toFormatter === "oxfmt") {
|
|
1291
|
+
devDeps[newFormatterDep] = "^0.21.0";
|
|
1292
|
+
} else if (plan.toFormatter === "prettier") {
|
|
1293
|
+
devDeps[newFormatterDep] = "^3.4.2";
|
|
1294
|
+
} else if (plan.toFormatter === "biome") {
|
|
1295
|
+
devDeps[newFormatterDep] = "^1.9.4";
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
pkg.devDependencies = Object.fromEntries(
|
|
1299
|
+
Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
|
|
1300
|
+
);
|
|
1301
|
+
const scripts = pkg.scripts ?? {};
|
|
1302
|
+
if (plan.toLinter === "oxlint") {
|
|
1303
|
+
scripts.lint = "oxlint .";
|
|
1304
|
+
} else if (plan.toLinter === "eslint") {
|
|
1305
|
+
scripts.lint = "eslint .";
|
|
1306
|
+
} else if (plan.toLinter === "biome") {
|
|
1307
|
+
scripts.lint = "biome check .";
|
|
1308
|
+
}
|
|
1309
|
+
if (plan.toFormatter === "oxfmt") {
|
|
1310
|
+
scripts.format = "oxfmt .";
|
|
1311
|
+
} else if (plan.toFormatter === "prettier") {
|
|
1312
|
+
scripts.format = "prettier --write .";
|
|
1313
|
+
} else if (plan.toFormatter === "biome") {
|
|
1314
|
+
scripts.format = "biome format . --write";
|
|
1315
|
+
}
|
|
1316
|
+
pkg.scripts = scripts;
|
|
1317
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1318
|
+
}
|
|
1319
|
+
async function updateSubPackageJson(root, update) {
|
|
1320
|
+
const pkgPath = join(root, update.path);
|
|
1321
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
1322
|
+
const pkg = JSON.parse(content);
|
|
1323
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
1324
|
+
for (const dep of update.remove) {
|
|
1325
|
+
delete devDeps[dep];
|
|
1326
|
+
}
|
|
1327
|
+
for (const dep of update.add) {
|
|
1328
|
+
devDeps[dep] = "workspace:*";
|
|
1329
|
+
}
|
|
1330
|
+
pkg.devDependencies = Object.fromEntries(
|
|
1331
|
+
Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
|
|
1332
|
+
);
|
|
1333
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1334
|
+
}
|
|
1335
|
+
function formatMigrationChange(change) {
|
|
1336
|
+
const icon = change.type === "remove-dir" || change.type === "remove-file" ? "-" : change.type === "add-file" ? "+" : "~";
|
|
1337
|
+
return ` ${icon} ${change.description}`;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
702
1340
|
const require$1 = createRequire(import.meta.url);
|
|
703
1341
|
const pkg = require$1("../package.json");
|
|
1342
|
+
const META_OPTIONS = [
|
|
1343
|
+
"clearConfig",
|
|
1344
|
+
"configPath",
|
|
1345
|
+
"check",
|
|
1346
|
+
"fix",
|
|
1347
|
+
"update",
|
|
1348
|
+
"yes",
|
|
1349
|
+
"workspace",
|
|
1350
|
+
"path",
|
|
1351
|
+
"dir"
|
|
1352
|
+
];
|
|
1353
|
+
function hasConfigOptions(options) {
|
|
1354
|
+
return Object.keys(options).some(
|
|
1355
|
+
(key) => !META_OPTIONS.includes(key)
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
704
1358
|
async function fileExists(path) {
|
|
705
1359
|
try {
|
|
706
1360
|
await access(path, constants$1.F_OK);
|
|
@@ -709,6 +1363,50 @@ async function fileExists(path) {
|
|
|
709
1363
|
return false;
|
|
710
1364
|
}
|
|
711
1365
|
}
|
|
1366
|
+
async function promptForAiPlatforms(isNonInteractive) {
|
|
1367
|
+
const savedPlatforms = getAiPlatforms();
|
|
1368
|
+
if (isNonInteractive) {
|
|
1369
|
+
return savedPlatforms ?? ALL_AI_PLATFORMS;
|
|
1370
|
+
}
|
|
1371
|
+
if (savedPlatforms && savedPlatforms.length > 0) {
|
|
1372
|
+
const savedLabels = savedPlatforms.map((plat) => AI_PLATFORM_LABELS[plat]).join(", ");
|
|
1373
|
+
const useDefault = await p.confirm({
|
|
1374
|
+
message: `Add AI rules? ${color.dim(`(${savedLabels})`)}`,
|
|
1375
|
+
initialValue: true
|
|
1376
|
+
});
|
|
1377
|
+
if (p.isCancel(useDefault)) {
|
|
1378
|
+
return [];
|
|
1379
|
+
}
|
|
1380
|
+
if (useDefault) {
|
|
1381
|
+
return savedPlatforms;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const selected = await p.multiselect({
|
|
1385
|
+
message: "Add AI rules?",
|
|
1386
|
+
options: ALL_AI_PLATFORMS.map((platform) => ({
|
|
1387
|
+
value: platform,
|
|
1388
|
+
label: AI_PLATFORM_LABELS[platform],
|
|
1389
|
+
hint: AI_PLATFORM_HINTS[platform]
|
|
1390
|
+
})),
|
|
1391
|
+
initialValues: [],
|
|
1392
|
+
required: false
|
|
1393
|
+
});
|
|
1394
|
+
if (p.isCancel(selected)) {
|
|
1395
|
+
return [];
|
|
1396
|
+
}
|
|
1397
|
+
const platforms = selected;
|
|
1398
|
+
if (platforms.length === 0) {
|
|
1399
|
+
return [];
|
|
1400
|
+
}
|
|
1401
|
+
const saveChoice = await p.confirm({
|
|
1402
|
+
message: "Save selection for future projects?",
|
|
1403
|
+
initialValue: true
|
|
1404
|
+
});
|
|
1405
|
+
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1406
|
+
setAiPlatforms(platforms);
|
|
1407
|
+
}
|
|
1408
|
+
return platforms;
|
|
1409
|
+
}
|
|
712
1410
|
async function writeGeneratedFiles(basePath, files) {
|
|
713
1411
|
const filePaths = Object.keys(files).sort();
|
|
714
1412
|
for (const filePath of filePaths) {
|
|
@@ -753,15 +1451,39 @@ async function parseWorkspaceDirectories(monorepoRoot) {
|
|
|
753
1451
|
return [];
|
|
754
1452
|
}
|
|
755
1453
|
}
|
|
756
|
-
async function
|
|
1454
|
+
async function detectWorkspaceSettings(monorepoRoot) {
|
|
757
1455
|
try {
|
|
1456
|
+
const tooling = await detectTooling(monorepoRoot);
|
|
758
1457
|
const pkgPath = join(monorepoRoot, "package.json");
|
|
759
1458
|
const content = await readFile(pkgPath, "utf-8");
|
|
760
1459
|
const pkgJson = JSON.parse(content);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1460
|
+
let packageManager;
|
|
1461
|
+
if (pkgJson.packageManager) {
|
|
1462
|
+
packageManager = pkgJson.packageManager.split("@")[0];
|
|
1463
|
+
}
|
|
1464
|
+
let nodeVersion;
|
|
1465
|
+
if (pkgJson.engines?.node) {
|
|
1466
|
+
const match = pkgJson.engines.node.match(/(\d+)/);
|
|
1467
|
+
if (match) {
|
|
1468
|
+
nodeVersion = match[1];
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
let pnpmManageVersions;
|
|
1472
|
+
try {
|
|
1473
|
+
const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
1474
|
+
const workspaceContent = await readFile(workspaceFile, "utf-8");
|
|
1475
|
+
pnpmManageVersions = workspaceContent.includes(
|
|
1476
|
+
"manage-package-manager-versions: true"
|
|
1477
|
+
);
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
return {
|
|
1481
|
+
linter: tooling.linter,
|
|
1482
|
+
formatter: tooling.formatter,
|
|
1483
|
+
packageManager,
|
|
1484
|
+
nodeVersion,
|
|
1485
|
+
pnpmManageVersions
|
|
1486
|
+
};
|
|
765
1487
|
} catch {
|
|
766
1488
|
return {};
|
|
767
1489
|
}
|
|
@@ -987,7 +1709,7 @@ Or in \`package.json\`:
|
|
|
987
1709
|
content: existingContent
|
|
988
1710
|
};
|
|
989
1711
|
}
|
|
990
|
-
async function createPackageInWorkspace(monorepoRoot, packageManager,
|
|
1712
|
+
async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
|
|
991
1713
|
const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
|
|
992
1714
|
const defaultDirectories = ["apps", "packages"];
|
|
993
1715
|
const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
|
|
@@ -1023,7 +1745,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
|
|
|
1023
1745
|
const packageOptions = await promptForPackageOptions(
|
|
1024
1746
|
scopedName,
|
|
1025
1747
|
packageType,
|
|
1026
|
-
|
|
1748
|
+
inheritedSettings
|
|
1027
1749
|
);
|
|
1028
1750
|
let targetDir = defaultDir;
|
|
1029
1751
|
if (hasCustomDirectories && workspaceDirectories.length > 0) {
|
|
@@ -1101,7 +1823,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
|
|
|
1101
1823
|
})
|
|
1102
1824
|
);
|
|
1103
1825
|
}
|
|
1104
|
-
const formatter = packageOptions.formatter ?? "
|
|
1826
|
+
const formatter = packageOptions.formatter ?? "prettier";
|
|
1105
1827
|
if (formatter === "prettier") {
|
|
1106
1828
|
versionPromises.push(
|
|
1107
1829
|
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
@@ -1256,11 +1978,11 @@ async function handleFixCommand(options) {
|
|
|
1256
1978
|
console.log(color.dim(` \u2022 ${error}`));
|
|
1257
1979
|
}
|
|
1258
1980
|
console.log();
|
|
1259
|
-
const tooling = await
|
|
1981
|
+
const tooling = await detectWorkspaceSettings(monorepoRoot);
|
|
1260
1982
|
const existingConfigs = await detectExistingConfigs(monorepoRoot);
|
|
1261
1983
|
const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
|
|
1262
|
-
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "
|
|
1263
|
-
const isNonInteractive = options.linter && options.formatter;
|
|
1984
|
+
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
|
|
1985
|
+
const isNonInteractive = Boolean(options.linter && options.formatter);
|
|
1264
1986
|
let linter;
|
|
1265
1987
|
let formatter;
|
|
1266
1988
|
if (isNonInteractive) {
|
|
@@ -1438,85 +2160,346 @@ async function handleFixCommand(options) {
|
|
|
1438
2160
|
console.log(color.dim(" Generated .vscode/extensions.json"));
|
|
1439
2161
|
}
|
|
1440
2162
|
}
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
2163
|
+
const aiRulesExist = await fileExists(
|
|
2164
|
+
join(monorepoRoot, ".ai/workspace.md")
|
|
2165
|
+
);
|
|
2166
|
+
if (!aiRulesExist) {
|
|
2167
|
+
const platforms = await promptForAiPlatforms(isNonInteractive);
|
|
2168
|
+
if (platforms.length > 0) {
|
|
2169
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
2170
|
+
const aiFilesOutput = {};
|
|
2171
|
+
generateAiFiles(aiFilesOutput, {
|
|
2172
|
+
name: scope,
|
|
2173
|
+
packageManager: "pnpm",
|
|
2174
|
+
linter,
|
|
2175
|
+
formatter,
|
|
2176
|
+
isMonorepo: true,
|
|
2177
|
+
platforms
|
|
2178
|
+
});
|
|
2179
|
+
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
2180
|
+
const fullPath = join(monorepoRoot, filePath);
|
|
2181
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
2182
|
+
await writeFile(fullPath, file.content);
|
|
2183
|
+
console.log(color.dim(` Generated ${filePath}`));
|
|
2184
|
+
}
|
|
1451
2185
|
}
|
|
1452
2186
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
2187
|
+
process.exit(0);
|
|
2188
|
+
} catch (error) {
|
|
2189
|
+
spinner.stop(color.red("\u2717") + " Failed to fix workspace");
|
|
2190
|
+
console.error(error);
|
|
2191
|
+
process.exit(1);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
async function handleMigration(config, target, root, options) {
|
|
2195
|
+
const plan = await getMigrationPlan(config, target, root);
|
|
2196
|
+
console.log(color.cyan("Migration:"));
|
|
2197
|
+
if (plan.fromLinter !== plan.toLinter) {
|
|
2198
|
+
console.log(
|
|
2199
|
+
` Linter: ${color.dim(plan.fromLinter)} \u2192 ${color.green(plan.toLinter)}`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
if (plan.fromFormatter !== plan.toFormatter) {
|
|
2203
|
+
console.log(
|
|
2204
|
+
` Formatter: ${color.dim(plan.fromFormatter)} \u2192 ${color.green(
|
|
2205
|
+
plan.toFormatter
|
|
2206
|
+
)}`
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
console.log();
|
|
2210
|
+
console.log(color.cyan("Changes:"));
|
|
2211
|
+
for (const change of plan.changes) {
|
|
2212
|
+
console.log(formatMigrationChange(change));
|
|
2213
|
+
}
|
|
2214
|
+
if (plan.subPackageUpdates.length > 0) {
|
|
2215
|
+
console.log();
|
|
2216
|
+
console.log(color.cyan(`Sub-packages (${plan.subPackageUpdates.length}):`));
|
|
2217
|
+
for (const update of plan.subPackageUpdates) {
|
|
2218
|
+
const changes = [
|
|
2219
|
+
...update.remove.map((d) => `-${d}`),
|
|
2220
|
+
...update.add.map((d) => `+${d}`)
|
|
2221
|
+
].join(", ");
|
|
2222
|
+
console.log(` ~ ${update.path} (${changes})`);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
console.log();
|
|
2226
|
+
if (!options.yes) {
|
|
2227
|
+
const confirm = await p.confirm({
|
|
2228
|
+
message: "Apply migration?",
|
|
2229
|
+
initialValue: true
|
|
2230
|
+
});
|
|
2231
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
2232
|
+
console.log(color.dim(" Migration cancelled"));
|
|
2233
|
+
process.exit(0);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
await applyMigration(plan, root);
|
|
2237
|
+
const aiWorkspacePath = join(root, ".ai/workspace.md");
|
|
2238
|
+
const aiRulesExist = await fileExists(aiWorkspacePath);
|
|
2239
|
+
if (aiRulesExist) {
|
|
2240
|
+
console.log();
|
|
2241
|
+
console.log(color.cyan("Updating AI rules..."));
|
|
2242
|
+
const scope = await getMonorepoScope(root);
|
|
2243
|
+
const existingPlatforms = [];
|
|
2244
|
+
if (await fileExists(join(root, "AGENTS.md"))) {
|
|
2245
|
+
existingPlatforms.push("agents");
|
|
2246
|
+
}
|
|
2247
|
+
if (await fileExists(join(root, "CLAUDE.md"))) {
|
|
2248
|
+
existingPlatforms.push("claude");
|
|
2249
|
+
}
|
|
2250
|
+
const aiFilesOutput = {};
|
|
2251
|
+
generateAiFiles(aiFilesOutput, {
|
|
2252
|
+
name: scope,
|
|
2253
|
+
packageManager: "pnpm",
|
|
2254
|
+
linter: plan.toLinter,
|
|
2255
|
+
formatter: plan.toFormatter,
|
|
2256
|
+
isMonorepo: true,
|
|
2257
|
+
platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
|
|
2258
|
+
});
|
|
2259
|
+
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
2260
|
+
const fullPath = join(root, filePath);
|
|
2261
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
2262
|
+
await writeFile(fullPath, file.content);
|
|
2263
|
+
console.log(color.dim(` ${filePath}`));
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
console.log();
|
|
2267
|
+
console.log(
|
|
2268
|
+
color.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`
|
|
2269
|
+
);
|
|
2270
|
+
console.log(color.dim(" Run `pnpm install` to update dependencies"));
|
|
2271
|
+
process.exit(0);
|
|
2272
|
+
}
|
|
2273
|
+
async function handleUpdateCommand(options) {
|
|
2274
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
2275
|
+
if (!monorepoRoot) {
|
|
2276
|
+
console.log(color.red("\u2717") + " Not a monorepo workspace");
|
|
2277
|
+
console.log(color.dim(" --update only supports pnpm monorepos"));
|
|
2278
|
+
process.exit(1);
|
|
2279
|
+
}
|
|
2280
|
+
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
2281
|
+
if (!valid) {
|
|
2282
|
+
console.log(color.yellow("!") + " Workspace has issues:");
|
|
2283
|
+
for (const error of errors) {
|
|
2284
|
+
console.log(color.dim(` \u2022 ${error}`));
|
|
2285
|
+
}
|
|
2286
|
+
console.log();
|
|
2287
|
+
const shouldFix = options.yes || await p.confirm({
|
|
2288
|
+
message: "Run fix first to resolve these issues?",
|
|
2289
|
+
initialValue: true
|
|
2290
|
+
});
|
|
2291
|
+
if (p.isCancel(shouldFix) || !shouldFix) {
|
|
2292
|
+
console.log(
|
|
2293
|
+
color.dim(" Run `pnpm create krispya --fix` to fix manually")
|
|
1463
2294
|
);
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
2295
|
+
process.exit(1);
|
|
2296
|
+
}
|
|
2297
|
+
const preFixConfig = await detectCurrentConfig(monorepoRoot);
|
|
2298
|
+
const fixOptions = {
|
|
2299
|
+
...options,
|
|
2300
|
+
linter: options.linter ?? preFixConfig.linter,
|
|
2301
|
+
formatter: options.formatter ?? preFixConfig.formatter
|
|
2302
|
+
};
|
|
2303
|
+
await handleFixCommand(fixOptions);
|
|
2304
|
+
}
|
|
2305
|
+
const config = await detectCurrentConfig(monorepoRoot);
|
|
2306
|
+
const targetLinter = options.linter;
|
|
2307
|
+
const targetFormatter = options.formatter;
|
|
2308
|
+
const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
|
|
2309
|
+
if (needsMigration(config, migrationTarget)) {
|
|
2310
|
+
await handleMigration(config, migrationTarget, monorepoRoot, options);
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
console.log(
|
|
2314
|
+
color.cyan("Checking for updates...") + color.dim(` (${config.linter}/${config.formatter})`)
|
|
2315
|
+
);
|
|
2316
|
+
console.log();
|
|
2317
|
+
const expected = generateExpectedFiles(config);
|
|
2318
|
+
const categories = await compareWithDisk(expected, monorepoRoot);
|
|
2319
|
+
const workspaceConfigChanges = await getWorkspaceConfigUpdates(monorepoRoot);
|
|
2320
|
+
const workspaceCategory = {
|
|
2321
|
+
category: "workspace-config",
|
|
2322
|
+
label: "Workspace Config",
|
|
2323
|
+
changes: workspaceConfigChanges,
|
|
2324
|
+
hasUserModifications: workspaceConfigChanges.some(
|
|
2325
|
+
(c) => c.status === "modified"
|
|
2326
|
+
)
|
|
2327
|
+
};
|
|
2328
|
+
const allCategories = categories.filter(
|
|
2329
|
+
(c) => c.category !== "workspace-config"
|
|
2330
|
+
);
|
|
2331
|
+
if (workspaceConfigChanges.length > 0) {
|
|
2332
|
+
const configPkgIndex = allCategories.findIndex(
|
|
2333
|
+
(c) => c.category === "config-packages"
|
|
2334
|
+
);
|
|
2335
|
+
if (configPkgIndex !== -1) {
|
|
2336
|
+
allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
|
|
2337
|
+
} else {
|
|
2338
|
+
allCategories.push(workspaceCategory);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
let updatedCount = 0;
|
|
2342
|
+
let skippedCount = 0;
|
|
2343
|
+
for (const category of allCategories) {
|
|
2344
|
+
const newChanges = category.changes.filter((c) => c.status === "added");
|
|
2345
|
+
const modifiedChanges = category.changes.filter(
|
|
2346
|
+
(c) => c.status === "modified"
|
|
2347
|
+
);
|
|
2348
|
+
const hasNew = newChanges.length > 0;
|
|
2349
|
+
const hasModified = modifiedChanges.length > 0;
|
|
2350
|
+
const hasChanges = hasNew || hasModified;
|
|
2351
|
+
if (!hasChanges) {
|
|
2352
|
+
console.log(color.green("\u2713") + ` ${category.label}: Up to date`);
|
|
2353
|
+
continue;
|
|
2354
|
+
}
|
|
2355
|
+
if (category.category === "ai-files") {
|
|
2356
|
+
if (hasNew) {
|
|
2357
|
+
console.log(color.cyan(category.label + ":"));
|
|
2358
|
+
console.log(
|
|
2359
|
+
color.dim(` ${newChanges.length} AI file(s) can be added`)
|
|
2360
|
+
);
|
|
2361
|
+
console.log();
|
|
2362
|
+
const applyAi = options.yes ? true : await p.confirm({
|
|
2363
|
+
message: "Add AI rules?",
|
|
1470
2364
|
initialValue: true
|
|
1471
2365
|
});
|
|
1472
|
-
if (!p.isCancel(
|
|
1473
|
-
|
|
2366
|
+
if (!p.isCancel(applyAi) && applyAi) {
|
|
2367
|
+
await applyUpdates(newChanges, monorepoRoot);
|
|
2368
|
+
console.log(
|
|
2369
|
+
color.green("\u2713") + ` Added ${newChanges.length} AI file(s)`
|
|
2370
|
+
);
|
|
2371
|
+
updatedCount++;
|
|
2372
|
+
} else {
|
|
2373
|
+
console.log(color.dim(` Skipped ${category.label}`));
|
|
2374
|
+
skippedCount++;
|
|
1474
2375
|
}
|
|
1475
2376
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
2377
|
+
if (hasModified) {
|
|
2378
|
+
console.log(color.cyan("AI Files (existing):"));
|
|
2379
|
+
for (const change of modifiedChanges) {
|
|
2380
|
+
console.log(formatFileChange(change));
|
|
2381
|
+
}
|
|
2382
|
+
console.log();
|
|
2383
|
+
if (options.yes) {
|
|
2384
|
+
console.log(color.dim(" (--yes mode: keeping existing AI files)"));
|
|
2385
|
+
} else {
|
|
2386
|
+
const updateExisting = await p.confirm({
|
|
2387
|
+
message: "Update existing AI files to latest template?",
|
|
2388
|
+
initialValue: false
|
|
2389
|
+
});
|
|
2390
|
+
if (!p.isCancel(updateExisting) && updateExisting) {
|
|
2391
|
+
await applyUpdates(modifiedChanges, monorepoRoot);
|
|
2392
|
+
console.log(color.green("\u2713") + " Updated existing AI files");
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
console.log();
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
let changesToApply = [];
|
|
2400
|
+
if (options.yes) {
|
|
2401
|
+
console.log(color.cyan(category.label + ":"));
|
|
2402
|
+
for (const change of [...newChanges, ...modifiedChanges]) {
|
|
2403
|
+
console.log(formatFileChange(change));
|
|
2404
|
+
}
|
|
2405
|
+
console.log();
|
|
2406
|
+
if (category.category === "workspace-config") {
|
|
2407
|
+
changesToApply = [...newChanges, ...modifiedChanges];
|
|
2408
|
+
if (changesToApply.length > 0) {
|
|
2409
|
+
console.log(color.dim(" (--yes mode: applying merge updates)"));
|
|
2410
|
+
}
|
|
2411
|
+
} else {
|
|
2412
|
+
changesToApply = newChanges;
|
|
2413
|
+
if (newChanges.length > 0) {
|
|
2414
|
+
console.log(color.dim(" (--yes mode: adding new files only)"));
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
} else if (hasNew && hasModified) {
|
|
2418
|
+
const allChanges = [...newChanges, ...modifiedChanges];
|
|
2419
|
+
const selectedFiles = await p.multiselect({
|
|
2420
|
+
message: `${category.label} (+ new, ~ changed)`,
|
|
2421
|
+
options: allChanges.map((change) => ({
|
|
2422
|
+
value: change.path,
|
|
2423
|
+
label: change.status === "added" ? `+ ${change.path}` : `~ ${change.path}`
|
|
1483
2424
|
})),
|
|
2425
|
+
initialValues: newChanges.map((c) => c.path),
|
|
2426
|
+
// Pre-select new files
|
|
1484
2427
|
required: false
|
|
1485
2428
|
});
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
message: "Save as default for future?",
|
|
1490
|
-
initialValue: true
|
|
1491
|
-
});
|
|
1492
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1493
|
-
setAiFiles(selectedAiFiles);
|
|
1494
|
-
}
|
|
2429
|
+
if (p.isCancel(selectedFiles)) {
|
|
2430
|
+
p.cancel("Operation cancelled.");
|
|
2431
|
+
process.exit(0);
|
|
1495
2432
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
2433
|
+
if (selectedFiles.length > 0) {
|
|
2434
|
+
changesToApply = allChanges.filter(
|
|
2435
|
+
(c) => selectedFiles.includes(c.path)
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
} else if (hasNew) {
|
|
2439
|
+
console.log(color.cyan(category.label + ":"));
|
|
2440
|
+
for (const change of newChanges) {
|
|
2441
|
+
console.log(formatFileChange(change));
|
|
2442
|
+
}
|
|
2443
|
+
console.log();
|
|
2444
|
+
const shouldAdd = await p.confirm({
|
|
2445
|
+
message: `Add ${newChanges.length} new file(s)?`,
|
|
2446
|
+
initialValue: true
|
|
1506
2447
|
});
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
2448
|
+
if (p.isCancel(shouldAdd)) {
|
|
2449
|
+
p.cancel("Operation cancelled.");
|
|
2450
|
+
process.exit(0);
|
|
2451
|
+
}
|
|
2452
|
+
if (shouldAdd) {
|
|
2453
|
+
changesToApply = newChanges;
|
|
2454
|
+
}
|
|
2455
|
+
} else if (hasModified) {
|
|
2456
|
+
console.log(color.cyan(category.label + ":"));
|
|
2457
|
+
for (const change of modifiedChanges) {
|
|
2458
|
+
console.log(formatFileChange(change));
|
|
2459
|
+
}
|
|
2460
|
+
console.log();
|
|
2461
|
+
const shouldUpdate = await p.confirm({
|
|
2462
|
+
message: `Update ${modifiedChanges.length} file(s)? (will overwrite)`,
|
|
2463
|
+
initialValue: false
|
|
2464
|
+
});
|
|
2465
|
+
if (p.isCancel(shouldUpdate)) {
|
|
2466
|
+
p.cancel("Operation cancelled.");
|
|
2467
|
+
process.exit(0);
|
|
2468
|
+
}
|
|
2469
|
+
if (shouldUpdate) {
|
|
2470
|
+
changesToApply = modifiedChanges;
|
|
1512
2471
|
}
|
|
1513
2472
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
2473
|
+
if (changesToApply.length > 0) {
|
|
2474
|
+
await applyUpdates(changesToApply, monorepoRoot);
|
|
2475
|
+
const addedCount = changesToApply.filter(
|
|
2476
|
+
(c) => c.status === "added"
|
|
2477
|
+
).length;
|
|
2478
|
+
const updatedFilesCount = changesToApply.filter(
|
|
2479
|
+
(c) => c.status === "modified"
|
|
2480
|
+
).length;
|
|
2481
|
+
const parts = [];
|
|
2482
|
+
if (addedCount > 0) parts.push(`added ${addedCount}`);
|
|
2483
|
+
if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
|
|
2484
|
+
console.log(color.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
|
|
2485
|
+
updatedCount++;
|
|
2486
|
+
} else {
|
|
2487
|
+
console.log(color.dim(` Skipped ${category.label}`));
|
|
2488
|
+
skippedCount++;
|
|
2489
|
+
}
|
|
2490
|
+
console.log();
|
|
1519
2491
|
}
|
|
2492
|
+
if (updatedCount === 0 && skippedCount === 0) {
|
|
2493
|
+
console.log(color.green("\u2713") + " Everything is up to date!");
|
|
2494
|
+
} else if (updatedCount > 0) {
|
|
2495
|
+
console.log(
|
|
2496
|
+
color.green("\u2713") + ` Updated ${updatedCount} ${updatedCount === 1 ? "category" : "categories"}`
|
|
2497
|
+
);
|
|
2498
|
+
if (skippedCount > 0) {
|
|
2499
|
+
console.log(color.dim(` Skipped ${skippedCount}`));
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
process.exit(0);
|
|
1520
2503
|
}
|
|
1521
2504
|
async function handleWorkspaceCommand(name, options) {
|
|
1522
2505
|
const monorepoRoot = await detectMonorepoRoot();
|
|
@@ -1538,7 +2521,7 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1538
2521
|
process.exit(1);
|
|
1539
2522
|
}
|
|
1540
2523
|
const scope = await getMonorepoScope(monorepoRoot);
|
|
1541
|
-
const
|
|
2524
|
+
const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
|
|
1542
2525
|
const projectType = options.type ?? "app";
|
|
1543
2526
|
const defaultDir = projectType === "library" ? "packages" : "apps";
|
|
1544
2527
|
const targetDir = options.dir ?? defaultDir;
|
|
@@ -1564,8 +2547,11 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1564
2547
|
})
|
|
1565
2548
|
);
|
|
1566
2549
|
}
|
|
1567
|
-
const linter =
|
|
1568
|
-
const formatter =
|
|
2550
|
+
const linter = inheritedSettings.linter ?? options.linter ?? "oxlint";
|
|
2551
|
+
const formatter = inheritedSettings.formatter ?? options.formatter ?? "prettier";
|
|
2552
|
+
const packageManager = inheritedSettings.packageManager ?? "pnpm";
|
|
2553
|
+
const nodeVersion = inheritedSettings.nodeVersion ?? "latest";
|
|
2554
|
+
const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
|
|
1569
2555
|
await Promise.all(versionPromises);
|
|
1570
2556
|
const relativePkgPath = join(targetDir, name);
|
|
1571
2557
|
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
@@ -1576,6 +2562,9 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1576
2562
|
template,
|
|
1577
2563
|
linter,
|
|
1578
2564
|
formatter,
|
|
2565
|
+
packageManager,
|
|
2566
|
+
nodeVersion,
|
|
2567
|
+
pnpmManageVersions,
|
|
1579
2568
|
workspaceRoot,
|
|
1580
2569
|
versions,
|
|
1581
2570
|
...baseTemplate === "r3f" && {
|
|
@@ -1609,8 +2598,8 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1609
2598
|
process.exit(1);
|
|
1610
2599
|
}
|
|
1611
2600
|
}
|
|
1612
|
-
async function handleMonorepoCreation(generateOptions) {
|
|
1613
|
-
const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.
|
|
2601
|
+
async function handleMonorepoCreation(generateOptions, isNonInteractive) {
|
|
2602
|
+
const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.w; });
|
|
1614
2603
|
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1615
2604
|
if (packageManager === "pnpm") {
|
|
1616
2605
|
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
@@ -1623,55 +2612,7 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1623
2612
|
if (nodeVersion === "latest") {
|
|
1624
2613
|
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1625
2614
|
}
|
|
1626
|
-
const
|
|
1627
|
-
let selectedAiFiles = [];
|
|
1628
|
-
if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1629
|
-
const aiFileLabels = {
|
|
1630
|
-
"cursor-rules": ".cursor/rules",
|
|
1631
|
-
"agents-md": "AGENTS.md",
|
|
1632
|
-
"claude-md": "CLAUDE.md",
|
|
1633
|
-
"copilot-md": ".github/copilot-instructions.md"
|
|
1634
|
-
};
|
|
1635
|
-
const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
|
|
1636
|
-
const useDefault = await p.confirm({
|
|
1637
|
-
message: `Generate AI instruction files? ${color.dim(
|
|
1638
|
-
`(${savedLabels})`
|
|
1639
|
-
)}`,
|
|
1640
|
-
initialValue: true
|
|
1641
|
-
});
|
|
1642
|
-
if (!p.isCancel(useDefault) && useDefault) {
|
|
1643
|
-
selectedAiFiles = savedAiFiles;
|
|
1644
|
-
}
|
|
1645
|
-
} else {
|
|
1646
|
-
const aiFilesChoice = await p.multiselect({
|
|
1647
|
-
message: "Generate AI instruction files?",
|
|
1648
|
-
options: [
|
|
1649
|
-
{ value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
|
|
1650
|
-
{
|
|
1651
|
-
value: "agents-md",
|
|
1652
|
-
label: "AGENTS.md",
|
|
1653
|
-
hint: "GitHub Copilot, general"
|
|
1654
|
-
},
|
|
1655
|
-
{ value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
|
|
1656
|
-
{
|
|
1657
|
-
value: "copilot-md",
|
|
1658
|
-
label: ".github/copilot-instructions.md",
|
|
1659
|
-
hint: "GitHub Copilot"
|
|
1660
|
-
}
|
|
1661
|
-
],
|
|
1662
|
-
required: false
|
|
1663
|
-
});
|
|
1664
|
-
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1665
|
-
selectedAiFiles = aiFilesChoice;
|
|
1666
|
-
const saveChoice = await p.confirm({
|
|
1667
|
-
message: "Save as default for future monorepos?",
|
|
1668
|
-
initialValue: true
|
|
1669
|
-
});
|
|
1670
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1671
|
-
setAiFiles(selectedAiFiles);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
2615
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
1675
2616
|
const projectPath = join(cwd(), generateOptions.name);
|
|
1676
2617
|
const spinner = p.spinner();
|
|
1677
2618
|
spinner.start("Creating monorepo workspace...");
|
|
@@ -1679,12 +2620,12 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1679
2620
|
const { files } = generateMonorepo({
|
|
1680
2621
|
name: generateOptions.name,
|
|
1681
2622
|
linter: generateOptions.linter ?? "oxlint",
|
|
1682
|
-
formatter: generateOptions.formatter ?? "
|
|
2623
|
+
formatter: generateOptions.formatter ?? "prettier",
|
|
1683
2624
|
packageManager,
|
|
1684
2625
|
pnpmVersion: generateOptions.pnpmVersion,
|
|
1685
2626
|
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
1686
2627
|
nodeVersion: generateOptions.nodeVersion,
|
|
1687
|
-
|
|
2628
|
+
aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
|
|
1688
2629
|
});
|
|
1689
2630
|
const filePaths = Object.keys(files).sort();
|
|
1690
2631
|
for (const filePath of filePaths) {
|
|
@@ -1696,9 +2637,15 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1696
2637
|
}
|
|
1697
2638
|
}
|
|
1698
2639
|
spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
|
|
1699
|
-
|
|
2640
|
+
if (isNonInteractive) {
|
|
2641
|
+
process.exit(0);
|
|
2642
|
+
}
|
|
2643
|
+
const newWorkspaceSettings = {
|
|
1700
2644
|
linter: generateOptions.linter,
|
|
1701
|
-
formatter: generateOptions.formatter
|
|
2645
|
+
formatter: generateOptions.formatter,
|
|
2646
|
+
packageManager,
|
|
2647
|
+
nodeVersion: generateOptions.nodeVersion,
|
|
2648
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions
|
|
1702
2649
|
};
|
|
1703
2650
|
const scope = generateOptions.name;
|
|
1704
2651
|
let addMore = true;
|
|
@@ -1706,7 +2653,7 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1706
2653
|
addMore = await createPackageInWorkspace(
|
|
1707
2654
|
projectPath,
|
|
1708
2655
|
packageManager,
|
|
1709
|
-
|
|
2656
|
+
newWorkspaceSettings,
|
|
1710
2657
|
scope
|
|
1711
2658
|
);
|
|
1712
2659
|
}
|
|
@@ -1725,10 +2672,14 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1725
2672
|
process.exit(1);
|
|
1726
2673
|
}
|
|
1727
2674
|
}
|
|
1728
|
-
async function handleStandaloneProjectCreation(generateOptions) {
|
|
2675
|
+
async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
|
|
1729
2676
|
const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
|
|
1730
2677
|
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
1731
2678
|
generateOptions.name ??= defaultFallbackName;
|
|
2679
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
2680
|
+
if (aiPlatforms.length > 0) {
|
|
2681
|
+
generateOptions.aiPlatforms = aiPlatforms;
|
|
2682
|
+
}
|
|
1732
2683
|
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1733
2684
|
if (packageManager === "pnpm") {
|
|
1734
2685
|
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
@@ -1779,7 +2730,7 @@ async function handleStandaloneProjectCreation(generateOptions) {
|
|
|
1779
2730
|
})
|
|
1780
2731
|
);
|
|
1781
2732
|
}
|
|
1782
|
-
const formatter = generateOptions.formatter ?? "
|
|
2733
|
+
const formatter = generateOptions.formatter ?? "prettier";
|
|
1783
2734
|
if (formatter === "prettier") {
|
|
1784
2735
|
versionPromises.push(
|
|
1785
2736
|
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
@@ -1808,6 +2759,9 @@ async function handleStandaloneProjectCreation(generateOptions) {
|
|
|
1808
2759
|
const files = generate(generateOptions);
|
|
1809
2760
|
await writeGeneratedFiles(projectPath, files);
|
|
1810
2761
|
spinner.stop(color.green.inverse(" \u2713 Project created! "));
|
|
2762
|
+
if (isNonInteractive) {
|
|
2763
|
+
process.exit(0);
|
|
2764
|
+
}
|
|
1811
2765
|
const nextSteps = isLibrary ? [
|
|
1812
2766
|
`cd ${generateOptions.name}`,
|
|
1813
2767
|
`${packageManager} install`,
|
|
@@ -1840,21 +2794,23 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
|
|
|
1840
2794
|
process.exit(0);
|
|
1841
2795
|
}
|
|
1842
2796
|
if (choice === "add") {
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2797
|
+
const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
|
|
2798
|
+
const hasSettings = Object.values(inheritedSettings).some(Boolean);
|
|
2799
|
+
if (hasSettings) {
|
|
2800
|
+
const settingsInfo = [
|
|
2801
|
+
inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
|
|
2802
|
+
inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
|
|
2803
|
+
inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager}`
|
|
1848
2804
|
].filter(Boolean).join(", ");
|
|
1849
|
-
p.log.info(`Using workspace
|
|
2805
|
+
p.log.info(`Using workspace settings (${settingsInfo})`);
|
|
1850
2806
|
}
|
|
1851
2807
|
const scope = await getMonorepoScope(monorepoRoot);
|
|
1852
2808
|
let addMore = true;
|
|
1853
2809
|
while (addMore) {
|
|
1854
2810
|
addMore = await createPackageInWorkspace(
|
|
1855
2811
|
monorepoRoot,
|
|
1856
|
-
"pnpm",
|
|
1857
|
-
|
|
2812
|
+
inheritedSettings.packageManager ?? "pnpm",
|
|
2813
|
+
inheritedSettings,
|
|
1858
2814
|
scope
|
|
1859
2815
|
);
|
|
1860
2816
|
}
|
|
@@ -1881,7 +2837,7 @@ async function main() {
|
|
|
1881
2837
|
"linter: eslint, oxlint, or biome (default: oxlint)"
|
|
1882
2838
|
).option(
|
|
1883
2839
|
"--formatter <type>",
|
|
1884
|
-
"formatter: prettier, oxfmt, or biome (default:
|
|
2840
|
+
"formatter: prettier, oxfmt, or biome (default: prettier)"
|
|
1885
2841
|
).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(
|
|
1886
2842
|
"--package-manager <manager>",
|
|
1887
2843
|
"specify package manager (e.g. npm, yarn, pnpm)"
|
|
@@ -1903,7 +2859,7 @@ async function main() {
|
|
|
1903
2859
|
).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
|
|
1904
2860
|
"--check",
|
|
1905
2861
|
"Check if current directory is in a valid monorepo workspace"
|
|
1906
|
-
).option("--fix", "Fix monorepo by generating missing .config packages").option(
|
|
2862
|
+
).option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
|
|
1907
2863
|
"--path <directory>",
|
|
1908
2864
|
"Run in specified directory instead of current working directory"
|
|
1909
2865
|
).action(async (name, options) => {
|
|
@@ -1942,6 +2898,12 @@ async function main() {
|
|
|
1942
2898
|
case "--fix":
|
|
1943
2899
|
options.fix = true;
|
|
1944
2900
|
break;
|
|
2901
|
+
case "--update":
|
|
2902
|
+
options.update = true;
|
|
2903
|
+
break;
|
|
2904
|
+
case "--yes":
|
|
2905
|
+
options.yes = true;
|
|
2906
|
+
break;
|
|
1945
2907
|
default:
|
|
1946
2908
|
console.error(color.red(`Unknown option: ${name}`));
|
|
1947
2909
|
process.exit(1);
|
|
@@ -1953,6 +2915,9 @@ async function main() {
|
|
|
1953
2915
|
if (options.fix) {
|
|
1954
2916
|
await handleFixCommand(options);
|
|
1955
2917
|
}
|
|
2918
|
+
if (options.update) {
|
|
2919
|
+
await handleUpdateCommand(options);
|
|
2920
|
+
}
|
|
1956
2921
|
if (options.dir && !options.workspace) {
|
|
1957
2922
|
console.error(color.red("Error:") + " --dir requires --workspace flag");
|
|
1958
2923
|
console.log(
|
|
@@ -1968,11 +2933,11 @@ async function main() {
|
|
|
1968
2933
|
console.clear();
|
|
1969
2934
|
p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
|
|
1970
2935
|
const monorepoRoot = await detectMonorepoRoot();
|
|
1971
|
-
if (monorepoRoot &&
|
|
2936
|
+
if (monorepoRoot && !hasConfigOptions(options)) {
|
|
1972
2937
|
await handleInteractiveMonorepoMode(monorepoRoot);
|
|
1973
2938
|
}
|
|
1974
2939
|
let generateOptions;
|
|
1975
|
-
if (
|
|
2940
|
+
if (options.yes) {
|
|
1976
2941
|
const template = options.template ?? "vanilla";
|
|
1977
2942
|
const baseTemplate = getBaseTemplate(template);
|
|
1978
2943
|
const defaultName = getDefaultProjectName(template);
|
|
@@ -1983,7 +2948,7 @@ async function main() {
|
|
|
1983
2948
|
libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
|
|
1984
2949
|
template,
|
|
1985
2950
|
linter: options.linter ?? "oxlint",
|
|
1986
|
-
formatter: options.formatter ?? "
|
|
2951
|
+
formatter: options.formatter ?? "prettier",
|
|
1987
2952
|
...baseTemplate === "r3f" && {
|
|
1988
2953
|
drei: options.drei ? {} : void 0,
|
|
1989
2954
|
handle: options.handle ? {} : void 0,
|
|
@@ -2003,12 +2968,35 @@ async function main() {
|
|
|
2003
2968
|
nodeVersion: options.nodeVersion ?? "latest"
|
|
2004
2969
|
};
|
|
2005
2970
|
} else {
|
|
2006
|
-
|
|
2971
|
+
const presets = hasConfigOptions(options) ? {
|
|
2972
|
+
type: options.type,
|
|
2973
|
+
template: options.template,
|
|
2974
|
+
bundler: options.bundler,
|
|
2975
|
+
linter: options.linter,
|
|
2976
|
+
formatter: options.formatter,
|
|
2977
|
+
packageManager: options.packageManager,
|
|
2978
|
+
nodeVersion: options.nodeVersion,
|
|
2979
|
+
pnpmManageVersions: options.pnpmManageVersions,
|
|
2980
|
+
drei: options.drei,
|
|
2981
|
+
handle: options.handle,
|
|
2982
|
+
leva: options.leva,
|
|
2983
|
+
postprocessing: options.postprocessing,
|
|
2984
|
+
rapier: options.rapier,
|
|
2985
|
+
xr: options.xr,
|
|
2986
|
+
uikit: options.uikit,
|
|
2987
|
+
offscreen: options.offscreen,
|
|
2988
|
+
zustand: options.zustand,
|
|
2989
|
+
koota: options.koota,
|
|
2990
|
+
triplex: options.triplex,
|
|
2991
|
+
viverse: options.viverse
|
|
2992
|
+
} : void 0;
|
|
2993
|
+
generateOptions = await promptForOptions(name, presets);
|
|
2007
2994
|
}
|
|
2995
|
+
const isNonInteractive = options.yes ?? false;
|
|
2008
2996
|
if (generateOptions.projectType === "monorepo") {
|
|
2009
|
-
await handleMonorepoCreation(generateOptions);
|
|
2997
|
+
await handleMonorepoCreation(generateOptions, isNonInteractive);
|
|
2010
2998
|
} else {
|
|
2011
|
-
await handleStandaloneProjectCreation(generateOptions);
|
|
2999
|
+
await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
|
|
2012
3000
|
}
|
|
2013
3001
|
});
|
|
2014
3002
|
await program.parseAsync();
|