create-krispya 0.5.2 → 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 +1282 -302
- package/dist/cli.mjs +1285 -305
- 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
|
}
|
|
@@ -800,29 +1522,26 @@ async function getMonorepoScope(monorepoRoot) {
|
|
|
800
1522
|
}
|
|
801
1523
|
async function getWorkspacePackages(monorepoRoot) {
|
|
802
1524
|
const packagesDir = join(monorepoRoot, "packages");
|
|
803
|
-
const packages = [];
|
|
804
1525
|
try {
|
|
805
1526
|
const { readdir } = await import('fs/promises');
|
|
806
1527
|
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
1528
|
+
const names = [];
|
|
807
1529
|
for (const entry of entries) {
|
|
808
|
-
if (entry.isDirectory())
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
} catch {
|
|
820
|
-
}
|
|
1530
|
+
if (!entry.isDirectory()) continue;
|
|
1531
|
+
try {
|
|
1532
|
+
const content = await readFile(
|
|
1533
|
+
join(packagesDir, entry.name, "package.json"),
|
|
1534
|
+
"utf-8"
|
|
1535
|
+
);
|
|
1536
|
+
const pkg2 = JSON.parse(content);
|
|
1537
|
+
if (pkg2.name) names.push(pkg2.name);
|
|
1538
|
+
} catch {
|
|
821
1539
|
}
|
|
822
1540
|
}
|
|
1541
|
+
return names;
|
|
823
1542
|
} catch {
|
|
1543
|
+
return [];
|
|
824
1544
|
}
|
|
825
|
-
return packages;
|
|
826
1545
|
}
|
|
827
1546
|
async function ensureConfigInWorkspace(monorepoRoot) {
|
|
828
1547
|
const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
@@ -990,7 +1709,7 @@ Or in \`package.json\`:
|
|
|
990
1709
|
content: existingContent
|
|
991
1710
|
};
|
|
992
1711
|
}
|
|
993
|
-
async function createPackageInWorkspace(monorepoRoot, packageManager,
|
|
1712
|
+
async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedSettings, scope) {
|
|
994
1713
|
const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
|
|
995
1714
|
const defaultDirectories = ["apps", "packages"];
|
|
996
1715
|
const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
|
|
@@ -1026,7 +1745,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
|
|
|
1026
1745
|
const packageOptions = await promptForPackageOptions(
|
|
1027
1746
|
scopedName,
|
|
1028
1747
|
packageType,
|
|
1029
|
-
|
|
1748
|
+
inheritedSettings
|
|
1030
1749
|
);
|
|
1031
1750
|
let targetDir = defaultDir;
|
|
1032
1751
|
if (hasCustomDirectories && workspaceDirectories.length > 0) {
|
|
@@ -1104,7 +1823,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
|
|
|
1104
1823
|
})
|
|
1105
1824
|
);
|
|
1106
1825
|
}
|
|
1107
|
-
const formatter = packageOptions.formatter ?? "
|
|
1826
|
+
const formatter = packageOptions.formatter ?? "prettier";
|
|
1108
1827
|
if (formatter === "prettier") {
|
|
1109
1828
|
versionPromises.push(
|
|
1110
1829
|
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
@@ -1126,20 +1845,15 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
|
|
|
1126
1845
|
}
|
|
1127
1846
|
await Promise.all(versionPromises);
|
|
1128
1847
|
packageOptions.versions = versions;
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
required: false
|
|
1139
|
-
});
|
|
1140
|
-
if (!p.isCancel(selectedDeps) && selectedDeps.length > 0) {
|
|
1141
|
-
packageOptions.workspaceDependencies = selectedDeps;
|
|
1142
|
-
}
|
|
1848
|
+
const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
|
|
1849
|
+
if (workspacePackages.length > 0) {
|
|
1850
|
+
const selectedDeps = await p.multiselect({
|
|
1851
|
+
message: "Add workspace dependencies?",
|
|
1852
|
+
options: workspacePackages.map((name) => ({ value: name, label: name })),
|
|
1853
|
+
required: false
|
|
1854
|
+
});
|
|
1855
|
+
if (!p.isCancel(selectedDeps) && selectedDeps.length > 0) {
|
|
1856
|
+
packageOptions.workspaceDependencies = selectedDeps;
|
|
1143
1857
|
}
|
|
1144
1858
|
}
|
|
1145
1859
|
const outputPath = join(monorepoRoot, relativePkgPath);
|
|
@@ -1264,11 +1978,11 @@ async function handleFixCommand(options) {
|
|
|
1264
1978
|
console.log(color.dim(` \u2022 ${error}`));
|
|
1265
1979
|
}
|
|
1266
1980
|
console.log();
|
|
1267
|
-
const tooling = await
|
|
1981
|
+
const tooling = await detectWorkspaceSettings(monorepoRoot);
|
|
1268
1982
|
const existingConfigs = await detectExistingConfigs(monorepoRoot);
|
|
1269
1983
|
const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
|
|
1270
|
-
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "
|
|
1271
|
-
const isNonInteractive = options.linter && options.formatter;
|
|
1984
|
+
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "prettier";
|
|
1985
|
+
const isNonInteractive = Boolean(options.linter && options.formatter);
|
|
1272
1986
|
let linter;
|
|
1273
1987
|
let formatter;
|
|
1274
1988
|
if (isNonInteractive) {
|
|
@@ -1446,85 +2160,346 @@ async function handleFixCommand(options) {
|
|
|
1446
2160
|
console.log(color.dim(" Generated .vscode/extensions.json"));
|
|
1447
2161
|
}
|
|
1448
2162
|
}
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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
|
+
}
|
|
1459
2185
|
}
|
|
1460
2186
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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")
|
|
1471
2294
|
);
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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?",
|
|
1478
2364
|
initialValue: true
|
|
1479
2365
|
});
|
|
1480
|
-
if (!p.isCancel(
|
|
1481
|
-
|
|
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++;
|
|
1482
2375
|
}
|
|
1483
2376
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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}`
|
|
1491
2424
|
})),
|
|
2425
|
+
initialValues: newChanges.map((c) => c.path),
|
|
2426
|
+
// Pre-select new files
|
|
1492
2427
|
required: false
|
|
1493
2428
|
});
|
|
1494
|
-
if (
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
message: "Save as default for future?",
|
|
1498
|
-
initialValue: true
|
|
1499
|
-
});
|
|
1500
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1501
|
-
setAiFiles(selectedAiFiles);
|
|
1502
|
-
}
|
|
2429
|
+
if (p.isCancel(selectedFiles)) {
|
|
2430
|
+
p.cancel("Operation cancelled.");
|
|
2431
|
+
process.exit(0);
|
|
1503
2432
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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
|
|
1514
2447
|
});
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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;
|
|
1520
2471
|
}
|
|
1521
2472
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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();
|
|
1527
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);
|
|
1528
2503
|
}
|
|
1529
2504
|
async function handleWorkspaceCommand(name, options) {
|
|
1530
2505
|
const monorepoRoot = await detectMonorepoRoot();
|
|
@@ -1546,7 +2521,7 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1546
2521
|
process.exit(1);
|
|
1547
2522
|
}
|
|
1548
2523
|
const scope = await getMonorepoScope(monorepoRoot);
|
|
1549
|
-
const
|
|
2524
|
+
const inheritedSettings = await detectWorkspaceSettings(monorepoRoot);
|
|
1550
2525
|
const projectType = options.type ?? "app";
|
|
1551
2526
|
const defaultDir = projectType === "library" ? "packages" : "apps";
|
|
1552
2527
|
const targetDir = options.dir ?? defaultDir;
|
|
@@ -1572,8 +2547,11 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1572
2547
|
})
|
|
1573
2548
|
);
|
|
1574
2549
|
}
|
|
1575
|
-
const linter =
|
|
1576
|
-
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;
|
|
1577
2555
|
await Promise.all(versionPromises);
|
|
1578
2556
|
const relativePkgPath = join(targetDir, name);
|
|
1579
2557
|
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
@@ -1584,6 +2562,9 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1584
2562
|
template,
|
|
1585
2563
|
linter,
|
|
1586
2564
|
formatter,
|
|
2565
|
+
packageManager,
|
|
2566
|
+
nodeVersion,
|
|
2567
|
+
pnpmManageVersions,
|
|
1587
2568
|
workspaceRoot,
|
|
1588
2569
|
versions,
|
|
1589
2570
|
...baseTemplate === "r3f" && {
|
|
@@ -1617,8 +2598,8 @@ async function handleWorkspaceCommand(name, options) {
|
|
|
1617
2598
|
process.exit(1);
|
|
1618
2599
|
}
|
|
1619
2600
|
}
|
|
1620
|
-
async function handleMonorepoCreation(generateOptions) {
|
|
1621
|
-
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; });
|
|
1622
2603
|
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1623
2604
|
if (packageManager === "pnpm") {
|
|
1624
2605
|
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
@@ -1631,55 +2612,7 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1631
2612
|
if (nodeVersion === "latest") {
|
|
1632
2613
|
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1633
2614
|
}
|
|
1634
|
-
const
|
|
1635
|
-
let selectedAiFiles = [];
|
|
1636
|
-
if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1637
|
-
const aiFileLabels = {
|
|
1638
|
-
"cursor-rules": ".cursor/rules",
|
|
1639
|
-
"agents-md": "AGENTS.md",
|
|
1640
|
-
"claude-md": "CLAUDE.md",
|
|
1641
|
-
"copilot-md": ".github/copilot-instructions.md"
|
|
1642
|
-
};
|
|
1643
|
-
const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
|
|
1644
|
-
const useDefault = await p.confirm({
|
|
1645
|
-
message: `Generate AI instruction files? ${color.dim(
|
|
1646
|
-
`(${savedLabels})`
|
|
1647
|
-
)}`,
|
|
1648
|
-
initialValue: true
|
|
1649
|
-
});
|
|
1650
|
-
if (!p.isCancel(useDefault) && useDefault) {
|
|
1651
|
-
selectedAiFiles = savedAiFiles;
|
|
1652
|
-
}
|
|
1653
|
-
} else {
|
|
1654
|
-
const aiFilesChoice = await p.multiselect({
|
|
1655
|
-
message: "Generate AI instruction files?",
|
|
1656
|
-
options: [
|
|
1657
|
-
{ value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
|
|
1658
|
-
{
|
|
1659
|
-
value: "agents-md",
|
|
1660
|
-
label: "AGENTS.md",
|
|
1661
|
-
hint: "GitHub Copilot, general"
|
|
1662
|
-
},
|
|
1663
|
-
{ value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
|
|
1664
|
-
{
|
|
1665
|
-
value: "copilot-md",
|
|
1666
|
-
label: ".github/copilot-instructions.md",
|
|
1667
|
-
hint: "GitHub Copilot"
|
|
1668
|
-
}
|
|
1669
|
-
],
|
|
1670
|
-
required: false
|
|
1671
|
-
});
|
|
1672
|
-
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1673
|
-
selectedAiFiles = aiFilesChoice;
|
|
1674
|
-
const saveChoice = await p.confirm({
|
|
1675
|
-
message: "Save as default for future monorepos?",
|
|
1676
|
-
initialValue: true
|
|
1677
|
-
});
|
|
1678
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1679
|
-
setAiFiles(selectedAiFiles);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
2615
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
1683
2616
|
const projectPath = join(cwd(), generateOptions.name);
|
|
1684
2617
|
const spinner = p.spinner();
|
|
1685
2618
|
spinner.start("Creating monorepo workspace...");
|
|
@@ -1687,12 +2620,12 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1687
2620
|
const { files } = generateMonorepo({
|
|
1688
2621
|
name: generateOptions.name,
|
|
1689
2622
|
linter: generateOptions.linter ?? "oxlint",
|
|
1690
|
-
formatter: generateOptions.formatter ?? "
|
|
2623
|
+
formatter: generateOptions.formatter ?? "prettier",
|
|
1691
2624
|
packageManager,
|
|
1692
2625
|
pnpmVersion: generateOptions.pnpmVersion,
|
|
1693
2626
|
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
1694
2627
|
nodeVersion: generateOptions.nodeVersion,
|
|
1695
|
-
|
|
2628
|
+
aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
|
|
1696
2629
|
});
|
|
1697
2630
|
const filePaths = Object.keys(files).sort();
|
|
1698
2631
|
for (const filePath of filePaths) {
|
|
@@ -1704,9 +2637,15 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1704
2637
|
}
|
|
1705
2638
|
}
|
|
1706
2639
|
spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
|
|
1707
|
-
|
|
2640
|
+
if (isNonInteractive) {
|
|
2641
|
+
process.exit(0);
|
|
2642
|
+
}
|
|
2643
|
+
const newWorkspaceSettings = {
|
|
1708
2644
|
linter: generateOptions.linter,
|
|
1709
|
-
formatter: generateOptions.formatter
|
|
2645
|
+
formatter: generateOptions.formatter,
|
|
2646
|
+
packageManager,
|
|
2647
|
+
nodeVersion: generateOptions.nodeVersion,
|
|
2648
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions
|
|
1710
2649
|
};
|
|
1711
2650
|
const scope = generateOptions.name;
|
|
1712
2651
|
let addMore = true;
|
|
@@ -1714,7 +2653,7 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1714
2653
|
addMore = await createPackageInWorkspace(
|
|
1715
2654
|
projectPath,
|
|
1716
2655
|
packageManager,
|
|
1717
|
-
|
|
2656
|
+
newWorkspaceSettings,
|
|
1718
2657
|
scope
|
|
1719
2658
|
);
|
|
1720
2659
|
}
|
|
@@ -1733,10 +2672,14 @@ async function handleMonorepoCreation(generateOptions) {
|
|
|
1733
2672
|
process.exit(1);
|
|
1734
2673
|
}
|
|
1735
2674
|
}
|
|
1736
|
-
async function handleStandaloneProjectCreation(generateOptions) {
|
|
2675
|
+
async function handleStandaloneProjectCreation(generateOptions, isNonInteractive) {
|
|
1737
2676
|
const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
|
|
1738
2677
|
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
1739
2678
|
generateOptions.name ??= defaultFallbackName;
|
|
2679
|
+
const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
|
|
2680
|
+
if (aiPlatforms.length > 0) {
|
|
2681
|
+
generateOptions.aiPlatforms = aiPlatforms;
|
|
2682
|
+
}
|
|
1740
2683
|
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1741
2684
|
if (packageManager === "pnpm") {
|
|
1742
2685
|
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
@@ -1787,7 +2730,7 @@ async function handleStandaloneProjectCreation(generateOptions) {
|
|
|
1787
2730
|
})
|
|
1788
2731
|
);
|
|
1789
2732
|
}
|
|
1790
|
-
const formatter = generateOptions.formatter ?? "
|
|
2733
|
+
const formatter = generateOptions.formatter ?? "prettier";
|
|
1791
2734
|
if (formatter === "prettier") {
|
|
1792
2735
|
versionPromises.push(
|
|
1793
2736
|
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
@@ -1816,6 +2759,9 @@ async function handleStandaloneProjectCreation(generateOptions) {
|
|
|
1816
2759
|
const files = generate(generateOptions);
|
|
1817
2760
|
await writeGeneratedFiles(projectPath, files);
|
|
1818
2761
|
spinner.stop(color.green.inverse(" \u2713 Project created! "));
|
|
2762
|
+
if (isNonInteractive) {
|
|
2763
|
+
process.exit(0);
|
|
2764
|
+
}
|
|
1819
2765
|
const nextSteps = isLibrary ? [
|
|
1820
2766
|
`cd ${generateOptions.name}`,
|
|
1821
2767
|
`${packageManager} install`,
|
|
@@ -1848,21 +2794,23 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
|
|
|
1848
2794
|
process.exit(0);
|
|
1849
2795
|
}
|
|
1850
2796
|
if (choice === "add") {
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
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}`
|
|
1856
2804
|
].filter(Boolean).join(", ");
|
|
1857
|
-
p.log.info(`Using workspace
|
|
2805
|
+
p.log.info(`Using workspace settings (${settingsInfo})`);
|
|
1858
2806
|
}
|
|
1859
2807
|
const scope = await getMonorepoScope(monorepoRoot);
|
|
1860
2808
|
let addMore = true;
|
|
1861
2809
|
while (addMore) {
|
|
1862
2810
|
addMore = await createPackageInWorkspace(
|
|
1863
2811
|
monorepoRoot,
|
|
1864
|
-
"pnpm",
|
|
1865
|
-
|
|
2812
|
+
inheritedSettings.packageManager ?? "pnpm",
|
|
2813
|
+
inheritedSettings,
|
|
1866
2814
|
scope
|
|
1867
2815
|
);
|
|
1868
2816
|
}
|
|
@@ -1889,7 +2837,7 @@ async function main() {
|
|
|
1889
2837
|
"linter: eslint, oxlint, or biome (default: oxlint)"
|
|
1890
2838
|
).option(
|
|
1891
2839
|
"--formatter <type>",
|
|
1892
|
-
"formatter: prettier, oxfmt, or biome (default:
|
|
2840
|
+
"formatter: prettier, oxfmt, or biome (default: prettier)"
|
|
1893
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(
|
|
1894
2842
|
"--package-manager <manager>",
|
|
1895
2843
|
"specify package manager (e.g. npm, yarn, pnpm)"
|
|
@@ -1911,7 +2859,7 @@ async function main() {
|
|
|
1911
2859
|
).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
|
|
1912
2860
|
"--check",
|
|
1913
2861
|
"Check if current directory is in a valid monorepo workspace"
|
|
1914
|
-
).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(
|
|
1915
2863
|
"--path <directory>",
|
|
1916
2864
|
"Run in specified directory instead of current working directory"
|
|
1917
2865
|
).action(async (name, options) => {
|
|
@@ -1950,6 +2898,12 @@ async function main() {
|
|
|
1950
2898
|
case "--fix":
|
|
1951
2899
|
options.fix = true;
|
|
1952
2900
|
break;
|
|
2901
|
+
case "--update":
|
|
2902
|
+
options.update = true;
|
|
2903
|
+
break;
|
|
2904
|
+
case "--yes":
|
|
2905
|
+
options.yes = true;
|
|
2906
|
+
break;
|
|
1953
2907
|
default:
|
|
1954
2908
|
console.error(color.red(`Unknown option: ${name}`));
|
|
1955
2909
|
process.exit(1);
|
|
@@ -1961,6 +2915,9 @@ async function main() {
|
|
|
1961
2915
|
if (options.fix) {
|
|
1962
2916
|
await handleFixCommand(options);
|
|
1963
2917
|
}
|
|
2918
|
+
if (options.update) {
|
|
2919
|
+
await handleUpdateCommand(options);
|
|
2920
|
+
}
|
|
1964
2921
|
if (options.dir && !options.workspace) {
|
|
1965
2922
|
console.error(color.red("Error:") + " --dir requires --workspace flag");
|
|
1966
2923
|
console.log(
|
|
@@ -1976,11 +2933,11 @@ async function main() {
|
|
|
1976
2933
|
console.clear();
|
|
1977
2934
|
p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
|
|
1978
2935
|
const monorepoRoot = await detectMonorepoRoot();
|
|
1979
|
-
if (monorepoRoot &&
|
|
2936
|
+
if (monorepoRoot && !hasConfigOptions(options)) {
|
|
1980
2937
|
await handleInteractiveMonorepoMode(monorepoRoot);
|
|
1981
2938
|
}
|
|
1982
2939
|
let generateOptions;
|
|
1983
|
-
if (
|
|
2940
|
+
if (options.yes) {
|
|
1984
2941
|
const template = options.template ?? "vanilla";
|
|
1985
2942
|
const baseTemplate = getBaseTemplate(template);
|
|
1986
2943
|
const defaultName = getDefaultProjectName(template);
|
|
@@ -1991,7 +2948,7 @@ async function main() {
|
|
|
1991
2948
|
libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
|
|
1992
2949
|
template,
|
|
1993
2950
|
linter: options.linter ?? "oxlint",
|
|
1994
|
-
formatter: options.formatter ?? "
|
|
2951
|
+
formatter: options.formatter ?? "prettier",
|
|
1995
2952
|
...baseTemplate === "r3f" && {
|
|
1996
2953
|
drei: options.drei ? {} : void 0,
|
|
1997
2954
|
handle: options.handle ? {} : void 0,
|
|
@@ -2011,12 +2968,35 @@ async function main() {
|
|
|
2011
2968
|
nodeVersion: options.nodeVersion ?? "latest"
|
|
2012
2969
|
};
|
|
2013
2970
|
} else {
|
|
2014
|
-
|
|
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);
|
|
2015
2994
|
}
|
|
2995
|
+
const isNonInteractive = options.yes ?? false;
|
|
2016
2996
|
if (generateOptions.projectType === "monorepo") {
|
|
2017
|
-
await handleMonorepoCreation(generateOptions);
|
|
2997
|
+
await handleMonorepoCreation(generateOptions, isNonInteractive);
|
|
2018
2998
|
} else {
|
|
2019
|
-
await handleStandaloneProjectCreation(generateOptions);
|
|
2999
|
+
await handleStandaloneProjectCreation(generateOptions, isNonInteractive);
|
|
2020
3000
|
}
|
|
2021
3001
|
});
|
|
2022
3002
|
await program.parseAsync();
|