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