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