create-krispya 0.5.0 → 0.5.2
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 +106 -8
- package/dist/chunks/index.cjs +712 -325
- package/dist/chunks/index.mjs +702 -326
- package/dist/cli.cjs +1530 -587
- package/dist/cli.mjs +1534 -591
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +47 -1
- package/dist/index.d.mts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { cwd } from 'process';
|
|
4
4
|
import { join, dirname, resolve } from 'path';
|
|
5
|
-
import { mkdir, writeFile,
|
|
6
|
-
import { constants } from 'fs';
|
|
5
|
+
import { access, constants, mkdir, writeFile, unlink, readFile } from 'fs/promises';
|
|
6
|
+
import { constants as constants$1 } from 'fs';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import * as p from '@clack/prompts';
|
|
9
9
|
import color from 'chalk';
|
|
10
10
|
import { fetch } from 'undici';
|
|
11
11
|
import { spawn } from 'child_process';
|
|
12
|
-
import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as
|
|
12
|
+
import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as generateTypescriptConfigPackage, d as generateOxlintConfigPackage, e as generateEslintConfigPackage, f as generateOxfmtConfigPackage, h as generatePrettierConfigPackage, i as generateVscodeFiles, j as generateAiFiles, k as getLatestNpmVersion, l as generate, m as getLatestPnpmVersion, n as getLatestYarnVersion, o as getLatestNpmCliVersion, p as getLatestNodeVersion, v as validatePackageName, q as parseWorkspaceYamlContent } from './chunks/index.mjs';
|
|
13
13
|
import Conf from 'conf';
|
|
14
14
|
|
|
15
15
|
const editorNames = {
|
|
@@ -123,7 +123,38 @@ function formatMonorepoConfigSummary(options) {
|
|
|
123
123
|
return lines.join("\n");
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
const config = new Conf({
|
|
127
|
+
projectName: "create-krispya"
|
|
128
|
+
});
|
|
129
|
+
function getPreferredEditor() {
|
|
130
|
+
return config.get("preferredEditor");
|
|
131
|
+
}
|
|
132
|
+
function setPreferredEditor(editor) {
|
|
133
|
+
config.set("preferredEditor", editor);
|
|
134
|
+
}
|
|
135
|
+
function getReuseWindow() {
|
|
136
|
+
return config.get("reuseWindow") ?? false;
|
|
137
|
+
}
|
|
138
|
+
function setReuseWindow(reuse) {
|
|
139
|
+
config.set("reuseWindow", reuse);
|
|
140
|
+
}
|
|
141
|
+
function getAiFiles() {
|
|
142
|
+
return config.get("aiFiles");
|
|
143
|
+
}
|
|
144
|
+
function setAiFiles(files) {
|
|
145
|
+
config.set("aiFiles", files);
|
|
146
|
+
}
|
|
147
|
+
function clearConfig() {
|
|
148
|
+
config.clear();
|
|
149
|
+
}
|
|
150
|
+
function getConfigPath() {
|
|
151
|
+
return config.path;
|
|
152
|
+
}
|
|
153
|
+
function getCustomTemplates() {
|
|
154
|
+
return config.get("customTemplates") ?? {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedTooling) {
|
|
127
158
|
const baseTemplate = getBaseTemplate(template);
|
|
128
159
|
const base = {
|
|
129
160
|
name,
|
|
@@ -133,26 +164,26 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler)
|
|
|
133
164
|
packageManager: "pnpm",
|
|
134
165
|
pnpmManageVersions: true,
|
|
135
166
|
nodeVersion: "latest",
|
|
136
|
-
linter: "oxlint",
|
|
137
|
-
formatter: "oxfmt",
|
|
167
|
+
linter: inheritedTooling?.linter ?? "oxlint",
|
|
168
|
+
formatter: inheritedTooling?.formatter ?? "oxfmt",
|
|
138
169
|
// Libraries get vitest by default, apps don't
|
|
139
170
|
testing: projectType === "library" ? "vitest" : "none"
|
|
140
171
|
};
|
|
141
|
-
if (baseTemplate === "r3f") {
|
|
172
|
+
if (baseTemplate === "r3f" && integrations) {
|
|
142
173
|
return {
|
|
143
174
|
...base,
|
|
144
|
-
drei: {},
|
|
145
|
-
handle: {},
|
|
146
|
-
leva: {},
|
|
147
|
-
postprocessing: {},
|
|
148
|
-
rapier: {},
|
|
149
|
-
xr: {},
|
|
150
|
-
uikit: {},
|
|
151
|
-
offscreen: {},
|
|
152
|
-
zustand: {},
|
|
153
|
-
koota: {},
|
|
154
|
-
triplex: {},
|
|
155
|
-
viverse: {}
|
|
175
|
+
drei: integrations.includes("drei") ? {} : void 0,
|
|
176
|
+
handle: integrations.includes("handle") ? {} : void 0,
|
|
177
|
+
leva: integrations.includes("leva") ? {} : void 0,
|
|
178
|
+
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
179
|
+
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
180
|
+
xr: integrations.includes("xr") ? {} : void 0,
|
|
181
|
+
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
182
|
+
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
183
|
+
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
184
|
+
koota: integrations.includes("koota") ? {} : void 0,
|
|
185
|
+
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
186
|
+
viverse: integrations.includes("viverse") ? {} : void 0
|
|
156
187
|
};
|
|
157
188
|
}
|
|
158
189
|
return base;
|
|
@@ -168,7 +199,33 @@ function getDefaultProjectName(template) {
|
|
|
168
199
|
return `react-three-${generateRandomName()}`;
|
|
169
200
|
}
|
|
170
201
|
}
|
|
171
|
-
async function
|
|
202
|
+
async function promptForR3fIntegrations() {
|
|
203
|
+
const selected = await p.multiselect({
|
|
204
|
+
message: "R3F integrations",
|
|
205
|
+
options: [
|
|
206
|
+
{ value: "drei", label: "Drei" },
|
|
207
|
+
{ value: "handle", label: "Handle" },
|
|
208
|
+
{ value: "leva", label: "Leva" },
|
|
209
|
+
{ value: "postprocessing", label: "Postprocessing" },
|
|
210
|
+
{ value: "rapier", label: "Rapier" },
|
|
211
|
+
{ value: "xr", label: "XR" },
|
|
212
|
+
{ value: "uikit", label: "UIKit" },
|
|
213
|
+
{ value: "offscreen", label: "Offscreen" },
|
|
214
|
+
{ value: "zustand", label: "Zustand" },
|
|
215
|
+
{ value: "koota", label: "Koota" },
|
|
216
|
+
{ value: "triplex", label: "Triplex" },
|
|
217
|
+
{ value: "viverse", label: "Viverse" }
|
|
218
|
+
],
|
|
219
|
+
initialValues: ["drei"],
|
|
220
|
+
required: false
|
|
221
|
+
});
|
|
222
|
+
if (p.isCancel(selected)) {
|
|
223
|
+
p.cancel("Operation cancelled.");
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
226
|
+
return selected;
|
|
227
|
+
}
|
|
228
|
+
async function promptForCustomization(template, name, projectType, integrations, inheritedTooling) {
|
|
172
229
|
let libraryBundler;
|
|
173
230
|
if (projectType === "library") {
|
|
174
231
|
const bundler = await p.select({
|
|
@@ -240,31 +297,39 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
240
297
|
}
|
|
241
298
|
pnpmManageVersions = managePnpm;
|
|
242
299
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
300
|
+
let linter = inheritedTooling?.linter ?? "oxlint";
|
|
301
|
+
let formatter = inheritedTooling?.formatter ?? "oxfmt";
|
|
302
|
+
if (!inheritedTooling?.linter) {
|
|
303
|
+
const linterChoice = await p.select({
|
|
304
|
+
message: "Linter",
|
|
305
|
+
options: [
|
|
306
|
+
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
307
|
+
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
308
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
309
|
+
],
|
|
310
|
+
initialValue: "oxlint"
|
|
311
|
+
});
|
|
312
|
+
if (p.isCancel(linterChoice)) {
|
|
313
|
+
p.cancel("Operation cancelled.");
|
|
314
|
+
process.exit(0);
|
|
315
|
+
}
|
|
316
|
+
linter = linterChoice;
|
|
255
317
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
p.
|
|
267
|
-
|
|
318
|
+
if (!inheritedTooling?.formatter) {
|
|
319
|
+
const formatterChoice = await p.select({
|
|
320
|
+
message: "Formatter",
|
|
321
|
+
options: [
|
|
322
|
+
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
323
|
+
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
324
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
325
|
+
],
|
|
326
|
+
initialValue: "oxfmt"
|
|
327
|
+
});
|
|
328
|
+
if (p.isCancel(formatterChoice)) {
|
|
329
|
+
p.cancel("Operation cancelled.");
|
|
330
|
+
process.exit(0);
|
|
331
|
+
}
|
|
332
|
+
formatter = formatterChoice;
|
|
268
333
|
}
|
|
269
334
|
const testing = await p.select({
|
|
270
335
|
message: "Testing",
|
|
@@ -292,47 +357,7 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
292
357
|
}
|
|
293
358
|
const baseTemplate = getBaseTemplate(template);
|
|
294
359
|
const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
|
|
295
|
-
|
|
296
|
-
if (baseTemplate === "r3f") {
|
|
297
|
-
const selected = await p.multiselect({
|
|
298
|
-
message: "R3F integrations",
|
|
299
|
-
options: [
|
|
300
|
-
{ value: "drei", label: "Drei" },
|
|
301
|
-
{ value: "handle", label: "Handle" },
|
|
302
|
-
{ value: "leva", label: "Leva" },
|
|
303
|
-
{ value: "postprocessing", label: "Postprocessing" },
|
|
304
|
-
{ value: "rapier", label: "Rapier" },
|
|
305
|
-
{ value: "xr", label: "XR" },
|
|
306
|
-
{ value: "uikit", label: "UIKit" },
|
|
307
|
-
{ value: "offscreen", label: "Offscreen" },
|
|
308
|
-
{ value: "zustand", label: "Zustand" },
|
|
309
|
-
{ value: "koota", label: "Koota" },
|
|
310
|
-
{ value: "triplex", label: "Triplex" },
|
|
311
|
-
{ value: "viverse", label: "Viverse" }
|
|
312
|
-
],
|
|
313
|
-
initialValues: [
|
|
314
|
-
"drei",
|
|
315
|
-
"handle",
|
|
316
|
-
"leva",
|
|
317
|
-
"postprocessing",
|
|
318
|
-
"rapier",
|
|
319
|
-
"xr",
|
|
320
|
-
"uikit",
|
|
321
|
-
"offscreen",
|
|
322
|
-
"zustand",
|
|
323
|
-
"koota",
|
|
324
|
-
"triplex",
|
|
325
|
-
"viverse"
|
|
326
|
-
],
|
|
327
|
-
required: false
|
|
328
|
-
});
|
|
329
|
-
if (p.isCancel(selected)) {
|
|
330
|
-
p.cancel("Operation cancelled.");
|
|
331
|
-
process.exit(0);
|
|
332
|
-
}
|
|
333
|
-
integrations = selected;
|
|
334
|
-
}
|
|
335
|
-
return {
|
|
360
|
+
const base = {
|
|
336
361
|
name,
|
|
337
362
|
template: finalTemplate,
|
|
338
363
|
projectType,
|
|
@@ -342,8 +367,11 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
342
367
|
pnpmManageVersions,
|
|
343
368
|
linter,
|
|
344
369
|
formatter,
|
|
345
|
-
testing
|
|
346
|
-
|
|
370
|
+
testing
|
|
371
|
+
};
|
|
372
|
+
if (baseTemplate === "r3f" && integrations) {
|
|
373
|
+
return {
|
|
374
|
+
...base,
|
|
347
375
|
drei: integrations.includes("drei") ? {} : void 0,
|
|
348
376
|
handle: integrations.includes("handle") ? {} : void 0,
|
|
349
377
|
leva: integrations.includes("leva") ? {} : void 0,
|
|
@@ -356,8 +384,9 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
356
384
|
koota: integrations.includes("koota") ? {} : void 0,
|
|
357
385
|
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
358
386
|
viverse: integrations.includes("viverse") ? {} : void 0
|
|
359
|
-
}
|
|
360
|
-
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return base;
|
|
361
390
|
}
|
|
362
391
|
async function promptForInitialPackage() {
|
|
363
392
|
const choice = await p.select({
|
|
@@ -402,31 +431,14 @@ async function promptForMonorepoCustomization(name) {
|
|
|
402
431
|
p.cancel("Operation cancelled.");
|
|
403
432
|
process.exit(0);
|
|
404
433
|
}
|
|
405
|
-
const
|
|
406
|
-
message: "
|
|
407
|
-
|
|
408
|
-
{ value: "pnpm", label: "pnpm" },
|
|
409
|
-
{ value: "npm", label: "npm" },
|
|
410
|
-
{ value: "yarn", label: "yarn" }
|
|
411
|
-
],
|
|
412
|
-
initialValue: "pnpm"
|
|
434
|
+
const managePnpm = await p.confirm({
|
|
435
|
+
message: "Enable manage-package-manager-versions?",
|
|
436
|
+
initialValue: true
|
|
413
437
|
});
|
|
414
|
-
if (p.isCancel(
|
|
438
|
+
if (p.isCancel(managePnpm)) {
|
|
415
439
|
p.cancel("Operation cancelled.");
|
|
416
440
|
process.exit(0);
|
|
417
441
|
}
|
|
418
|
-
let pnpmManageVersions = true;
|
|
419
|
-
if (packageManager === "pnpm") {
|
|
420
|
-
const managePnpm = await p.confirm({
|
|
421
|
-
message: "Enable manage-package-manager-versions?",
|
|
422
|
-
initialValue: true
|
|
423
|
-
});
|
|
424
|
-
if (p.isCancel(managePnpm)) {
|
|
425
|
-
p.cancel("Operation cancelled.");
|
|
426
|
-
process.exit(0);
|
|
427
|
-
}
|
|
428
|
-
pnpmManageVersions = managePnpm;
|
|
429
|
-
}
|
|
430
442
|
const linter = await p.select({
|
|
431
443
|
message: "Linter",
|
|
432
444
|
options: [
|
|
@@ -457,8 +469,8 @@ async function promptForMonorepoCustomization(name) {
|
|
|
457
469
|
name,
|
|
458
470
|
projectType: "monorepo",
|
|
459
471
|
nodeVersion,
|
|
460
|
-
packageManager,
|
|
461
|
-
pnpmManageVersions,
|
|
472
|
+
packageManager: "pnpm",
|
|
473
|
+
pnpmManageVersions: managePnpm,
|
|
462
474
|
linter,
|
|
463
475
|
formatter
|
|
464
476
|
};
|
|
@@ -476,19 +488,15 @@ async function promptForMonorepo(workspaceName) {
|
|
|
476
488
|
}),
|
|
477
489
|
"Workspace Configuration"
|
|
478
490
|
);
|
|
479
|
-
const
|
|
491
|
+
const proceed = await p.confirm({
|
|
480
492
|
message: "Proceed with these settings?",
|
|
481
|
-
|
|
482
|
-
{ value: "confirm", label: "Yes, create workspace" },
|
|
483
|
-
{ value: "customize", label: "No, let me customize" }
|
|
484
|
-
],
|
|
485
|
-
initialValue: "confirm"
|
|
493
|
+
initialValue: true
|
|
486
494
|
});
|
|
487
|
-
if (p.isCancel(
|
|
495
|
+
if (p.isCancel(proceed)) {
|
|
488
496
|
p.cancel("Operation cancelled.");
|
|
489
497
|
process.exit(0);
|
|
490
498
|
}
|
|
491
|
-
if (
|
|
499
|
+
if (proceed) {
|
|
492
500
|
return defaultOptions;
|
|
493
501
|
}
|
|
494
502
|
return promptForMonorepoCustomization(workspaceName);
|
|
@@ -528,76 +536,204 @@ async function promptForOptions(name) {
|
|
|
528
536
|
}
|
|
529
537
|
return promptForPackageOptions(projectName, projectType);
|
|
530
538
|
}
|
|
531
|
-
|
|
532
|
-
const
|
|
539
|
+
function customTemplateToOptions(customTemplate, name, projectType) {
|
|
540
|
+
const baseTemplate = customTemplate.baseTemplate;
|
|
541
|
+
const template = baseTemplate;
|
|
542
|
+
const base = {
|
|
543
|
+
name,
|
|
544
|
+
template,
|
|
545
|
+
projectType,
|
|
546
|
+
packageManager: "pnpm",
|
|
547
|
+
pnpmManageVersions: true,
|
|
548
|
+
nodeVersion: "latest",
|
|
549
|
+
linter: customTemplate.linter,
|
|
550
|
+
formatter: customTemplate.formatter,
|
|
551
|
+
testing: customTemplate.testing
|
|
552
|
+
};
|
|
553
|
+
if (baseTemplate === "r3f" && customTemplate.integrations) {
|
|
554
|
+
const integrations = customTemplate.integrations;
|
|
555
|
+
return {
|
|
556
|
+
...base,
|
|
557
|
+
drei: integrations.includes("drei") ? {} : void 0,
|
|
558
|
+
handle: integrations.includes("handle") ? {} : void 0,
|
|
559
|
+
leva: integrations.includes("leva") ? {} : void 0,
|
|
560
|
+
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
561
|
+
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
562
|
+
xr: integrations.includes("xr") ? {} : void 0,
|
|
563
|
+
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
564
|
+
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
565
|
+
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
566
|
+
koota: integrations.includes("koota") ? {} : void 0,
|
|
567
|
+
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
568
|
+
viverse: integrations.includes("viverse") ? {} : void 0
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return base;
|
|
572
|
+
}
|
|
573
|
+
async function promptForPackageOptions(projectName, projectType, inheritedTooling) {
|
|
574
|
+
const builtInOptions = [
|
|
575
|
+
{ value: "vanilla", label: "Vanilla" },
|
|
576
|
+
{ value: "react", label: "React" },
|
|
577
|
+
{ value: "r3f", label: "React Three Fiber" }
|
|
578
|
+
];
|
|
579
|
+
const customTemplates = getCustomTemplates();
|
|
580
|
+
const customOptions = Object.keys(customTemplates).map((name) => ({
|
|
581
|
+
value: `custom:${name}`,
|
|
582
|
+
label: name,
|
|
583
|
+
hint: "saved template"
|
|
584
|
+
}));
|
|
585
|
+
const allOptions = [...builtInOptions, ...customOptions];
|
|
586
|
+
const templateSelection = await p.select({
|
|
533
587
|
message: "Select a template",
|
|
534
|
-
options:
|
|
535
|
-
{ value: "vanilla", label: "Vanilla" },
|
|
536
|
-
{ value: "react", label: "React" },
|
|
537
|
-
{ value: "r3f", label: "React Three Fiber" }
|
|
538
|
-
],
|
|
588
|
+
options: allOptions,
|
|
539
589
|
initialValue: "vanilla"
|
|
540
590
|
});
|
|
541
|
-
if (p.isCancel(
|
|
591
|
+
if (p.isCancel(templateSelection)) {
|
|
542
592
|
p.cancel("Operation cancelled.");
|
|
543
593
|
process.exit(0);
|
|
544
594
|
}
|
|
595
|
+
const selection = templateSelection;
|
|
596
|
+
if (selection.startsWith("custom:")) {
|
|
597
|
+
const customName = selection.slice(7);
|
|
598
|
+
const customTemplate = customTemplates[customName];
|
|
599
|
+
const defaultOptions2 = customTemplateToOptions(customTemplate, projectName, projectType);
|
|
600
|
+
if (inheritedTooling?.linter) {
|
|
601
|
+
defaultOptions2.linter = inheritedTooling.linter;
|
|
602
|
+
}
|
|
603
|
+
if (inheritedTooling?.formatter) {
|
|
604
|
+
defaultOptions2.formatter = inheritedTooling.formatter;
|
|
605
|
+
}
|
|
606
|
+
const configTitle2 = inheritedTooling ? `Template: ${customName} (using workspace tooling)` : `Template: ${customName}`;
|
|
607
|
+
p.note(formatConfigSummary(defaultOptions2), configTitle2);
|
|
608
|
+
const proceed2 = await p.confirm({
|
|
609
|
+
message: "Proceed with these settings?",
|
|
610
|
+
initialValue: true
|
|
611
|
+
});
|
|
612
|
+
if (p.isCancel(proceed2)) {
|
|
613
|
+
p.cancel("Operation cancelled.");
|
|
614
|
+
process.exit(0);
|
|
615
|
+
}
|
|
616
|
+
if (proceed2) {
|
|
617
|
+
return defaultOptions2;
|
|
618
|
+
}
|
|
619
|
+
return promptForCustomization(
|
|
620
|
+
customTemplate.baseTemplate,
|
|
621
|
+
projectName,
|
|
622
|
+
projectType,
|
|
623
|
+
customTemplate.integrations,
|
|
624
|
+
inheritedTooling
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const template = selection;
|
|
628
|
+
const baseTemplate = getBaseTemplate(template);
|
|
629
|
+
let integrations;
|
|
630
|
+
if (baseTemplate === "r3f") {
|
|
631
|
+
integrations = await promptForR3fIntegrations();
|
|
632
|
+
}
|
|
545
633
|
const defaultOptions = getDefaultOptions(
|
|
546
634
|
template,
|
|
547
635
|
projectName,
|
|
548
|
-
projectType
|
|
636
|
+
projectType,
|
|
637
|
+
void 0,
|
|
638
|
+
integrations,
|
|
639
|
+
inheritedTooling
|
|
549
640
|
);
|
|
550
|
-
|
|
551
|
-
|
|
641
|
+
const configTitle = inheritedTooling ? "Template Configuration (using workspace tooling)" : "Template Configuration";
|
|
642
|
+
p.note(formatConfigSummary(defaultOptions), configTitle);
|
|
643
|
+
const proceed = await p.confirm({
|
|
552
644
|
message: "Proceed with these settings?",
|
|
553
|
-
|
|
554
|
-
{ value: "confirm", label: "Yes, create project" },
|
|
555
|
-
{ value: "customize", label: "No, let me customize" }
|
|
556
|
-
],
|
|
557
|
-
initialValue: "confirm"
|
|
645
|
+
initialValue: true
|
|
558
646
|
});
|
|
559
|
-
if (p.isCancel(
|
|
647
|
+
if (p.isCancel(proceed)) {
|
|
560
648
|
p.cancel("Operation cancelled.");
|
|
561
649
|
process.exit(0);
|
|
562
650
|
}
|
|
563
|
-
if (
|
|
651
|
+
if (proceed) {
|
|
564
652
|
return defaultOptions;
|
|
565
653
|
}
|
|
566
|
-
return promptForCustomization(
|
|
567
|
-
template,
|
|
568
|
-
projectName,
|
|
569
|
-
projectType
|
|
570
|
-
);
|
|
654
|
+
return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
|
|
571
655
|
}
|
|
572
656
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
function getReuseWindow() {
|
|
583
|
-
return config.get("reuseWindow") ?? false;
|
|
584
|
-
}
|
|
585
|
-
function setReuseWindow(reuse) {
|
|
586
|
-
config.set("reuseWindow", reuse);
|
|
657
|
+
async function checkAnyExists(paths) {
|
|
658
|
+
for (const path of paths) {
|
|
659
|
+
try {
|
|
660
|
+
await access(path, constants.F_OK);
|
|
661
|
+
return true;
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return false;
|
|
587
666
|
}
|
|
588
|
-
function
|
|
589
|
-
|
|
667
|
+
async function validateWorkspace(monorepoRoot) {
|
|
668
|
+
const errors = [];
|
|
669
|
+
const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
|
|
670
|
+
try {
|
|
671
|
+
await access(tsConfigPath, constants.F_OK);
|
|
672
|
+
} catch {
|
|
673
|
+
errors.push("Missing .config/typescript package");
|
|
674
|
+
}
|
|
675
|
+
const linterPaths = [
|
|
676
|
+
join(monorepoRoot, ".config/oxlint/package.json"),
|
|
677
|
+
join(monorepoRoot, ".config/eslint/package.json"),
|
|
678
|
+
join(monorepoRoot, "eslint.config.js"),
|
|
679
|
+
join(monorepoRoot, "biome.json")
|
|
680
|
+
];
|
|
681
|
+
const hasLinter = await checkAnyExists(linterPaths);
|
|
682
|
+
if (!hasLinter) {
|
|
683
|
+
errors.push(
|
|
684
|
+
"Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const formatterPaths = [
|
|
688
|
+
join(monorepoRoot, ".config/oxfmt/package.json"),
|
|
689
|
+
join(monorepoRoot, ".config/prettier/package.json"),
|
|
690
|
+
join(monorepoRoot, ".prettierrc.json"),
|
|
691
|
+
join(monorepoRoot, "biome.json")
|
|
692
|
+
];
|
|
693
|
+
const hasFormatter = await checkAnyExists(formatterPaths);
|
|
694
|
+
if (!hasFormatter) {
|
|
695
|
+
errors.push(
|
|
696
|
+
"Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
return { valid: errors.length === 0, errors };
|
|
590
700
|
}
|
|
591
701
|
|
|
592
702
|
const require$1 = createRequire(import.meta.url);
|
|
593
703
|
const pkg = require$1("../package.json");
|
|
704
|
+
async function fileExists(path) {
|
|
705
|
+
try {
|
|
706
|
+
await access(path, constants$1.F_OK);
|
|
707
|
+
return true;
|
|
708
|
+
} catch {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async function writeGeneratedFiles(basePath, files) {
|
|
713
|
+
const filePaths = Object.keys(files).sort();
|
|
714
|
+
for (const filePath of filePaths) {
|
|
715
|
+
const fullFilePath = join(basePath, filePath);
|
|
716
|
+
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
717
|
+
const file = files[filePath];
|
|
718
|
+
if (file.type === "text") {
|
|
719
|
+
await writeFile(fullFilePath, file.content);
|
|
720
|
+
} else {
|
|
721
|
+
const response = await fetch(file.url);
|
|
722
|
+
await writeFile(fullFilePath, response.body);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function calculateWorkspaceRoot(packagePath) {
|
|
727
|
+
const segments = packagePath.split(/[/\\]/).filter(Boolean);
|
|
728
|
+
return segments.map(() => "..").join("/");
|
|
729
|
+
}
|
|
594
730
|
async function detectMonorepoRoot() {
|
|
595
731
|
let currentDir = cwd();
|
|
596
732
|
const root = resolve("/");
|
|
597
733
|
while (currentDir !== root) {
|
|
598
734
|
const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
|
|
599
735
|
try {
|
|
600
|
-
await access(workspaceFile, constants.F_OK);
|
|
736
|
+
await access(workspaceFile, constants$1.F_OK);
|
|
601
737
|
const content = await readFile(workspaceFile, "utf-8");
|
|
602
738
|
if (content.includes("packages:")) {
|
|
603
739
|
return currentDir;
|
|
@@ -608,473 +744,1280 @@ async function detectMonorepoRoot() {
|
|
|
608
744
|
}
|
|
609
745
|
return null;
|
|
610
746
|
}
|
|
611
|
-
async function
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
"
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
747
|
+
async function parseWorkspaceDirectories(monorepoRoot) {
|
|
748
|
+
try {
|
|
749
|
+
const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
750
|
+
const content = await readFile(workspaceFile, "utf-8");
|
|
751
|
+
return parseWorkspaceYamlContent(content);
|
|
752
|
+
} catch {
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async function detectWorkspaceTooling(monorepoRoot) {
|
|
757
|
+
try {
|
|
758
|
+
const pkgPath = join(monorepoRoot, "package.json");
|
|
759
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
760
|
+
const pkgJson = JSON.parse(content);
|
|
761
|
+
const devDeps = pkgJson.devDependencies ?? {};
|
|
762
|
+
const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
|
|
763
|
+
const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
|
|
764
|
+
return { linter, formatter };
|
|
765
|
+
} catch {
|
|
766
|
+
return {};
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function detectExistingConfigs(monorepoRoot) {
|
|
770
|
+
const configs = {};
|
|
771
|
+
const eslintPath = join(monorepoRoot, "eslint.config.js");
|
|
772
|
+
if (await fileExists(eslintPath)) {
|
|
773
|
+
configs.linter = "eslint";
|
|
774
|
+
configs.eslintConfigPath = eslintPath;
|
|
775
|
+
}
|
|
776
|
+
const prettierPath = join(monorepoRoot, ".prettierrc.json");
|
|
777
|
+
if (await fileExists(prettierPath)) {
|
|
778
|
+
configs.formatter = "prettier";
|
|
779
|
+
configs.prettierConfigPath = prettierPath;
|
|
780
|
+
}
|
|
781
|
+
const biomePath = join(monorepoRoot, "biome.json");
|
|
782
|
+
if (await fileExists(biomePath)) {
|
|
783
|
+
configs.biomeConfigPath = biomePath;
|
|
784
|
+
if (!configs.linter) configs.linter = "biome";
|
|
785
|
+
if (!configs.formatter) configs.formatter = "biome";
|
|
786
|
+
}
|
|
787
|
+
return configs;
|
|
788
|
+
}
|
|
789
|
+
async function getMonorepoScope(monorepoRoot) {
|
|
790
|
+
try {
|
|
791
|
+
const pkgPath = join(monorepoRoot, "package.json");
|
|
792
|
+
const content = await readFile(pkgPath, "utf-8");
|
|
793
|
+
const pkgJson = JSON.parse(content);
|
|
794
|
+
if (pkgJson.name) {
|
|
795
|
+
return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
|
|
632
796
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (p.isCancel(choice)) {
|
|
646
|
-
p.cancel("Operation cancelled.");
|
|
647
|
-
process.exit(0);
|
|
648
|
-
}
|
|
649
|
-
if (choice === "add") {
|
|
650
|
-
const packageType = await promptForInitialPackage();
|
|
651
|
-
if (packageType === "skip") {
|
|
652
|
-
p.cancel("Operation cancelled.");
|
|
653
|
-
process.exit(0);
|
|
654
|
-
}
|
|
655
|
-
const packageName = await p.text({
|
|
656
|
-
message: "Package name?",
|
|
657
|
-
placeholder: packageType === "app" ? "my-app" : "my-package",
|
|
658
|
-
validate: (value) => {
|
|
659
|
-
if (!value.length) return "Package name is required";
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
if (p.isCancel(packageName)) {
|
|
663
|
-
p.cancel("Operation cancelled.");
|
|
664
|
-
process.exit(0);
|
|
665
|
-
}
|
|
666
|
-
const targetDir = packageType === "app" ? "apps" : "packages";
|
|
667
|
-
const packagePath = join(targetDir, packageName);
|
|
668
|
-
const workspaceRoot = "../..";
|
|
669
|
-
const packageOptions = await promptForPackageOptions(packageName, packageType);
|
|
670
|
-
packageOptions.workspaceRoot = workspaceRoot;
|
|
671
|
-
packageOptions.name = packageName;
|
|
672
|
-
const packageManager2 = packageOptions.packageManager || "pnpm";
|
|
673
|
-
if (packageManager2 === "pnpm") {
|
|
674
|
-
packageOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
675
|
-
}
|
|
676
|
-
const nodeVersion2 = packageOptions.nodeVersion ?? "latest";
|
|
677
|
-
if (nodeVersion2 === "latest") {
|
|
678
|
-
packageOptions.nodeVersion = await getLatestNodeVersion();
|
|
679
|
-
}
|
|
680
|
-
const versions2 = {};
|
|
681
|
-
const versionPromises2 = [];
|
|
682
|
-
const pkgIsLibrary = packageOptions.projectType === "library";
|
|
683
|
-
const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
|
|
684
|
-
if (pkgTesting === "vitest") {
|
|
685
|
-
versionPromises2.push(
|
|
686
|
-
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
687
|
-
versions2.vitest = v;
|
|
688
|
-
})
|
|
689
|
-
);
|
|
690
|
-
}
|
|
691
|
-
if (!pkgIsLibrary) {
|
|
692
|
-
versionPromises2.push(
|
|
693
|
-
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
694
|
-
versions2.vite = v;
|
|
695
|
-
})
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
const linter2 = packageOptions.linter ?? "oxlint";
|
|
699
|
-
if (linter2 === "eslint") {
|
|
700
|
-
versionPromises2.push(
|
|
701
|
-
getLatestNpmVersion("eslint", "9.17.0").then((v) => {
|
|
702
|
-
versions2.eslint = v;
|
|
703
|
-
})
|
|
704
|
-
);
|
|
705
|
-
} else if (linter2 === "oxlint") {
|
|
706
|
-
versionPromises2.push(
|
|
707
|
-
getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
|
|
708
|
-
versions2.oxlint = v;
|
|
709
|
-
})
|
|
710
|
-
);
|
|
711
|
-
} else if (linter2 === "biome") {
|
|
712
|
-
versionPromises2.push(
|
|
713
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
714
|
-
versions2.biome = v;
|
|
715
|
-
})
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
const formatter2 = packageOptions.formatter ?? "oxfmt";
|
|
719
|
-
if (formatter2 === "prettier") {
|
|
720
|
-
versionPromises2.push(
|
|
721
|
-
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
722
|
-
versions2.prettier = v;
|
|
723
|
-
})
|
|
724
|
-
);
|
|
725
|
-
} else if (formatter2 === "oxfmt") {
|
|
726
|
-
versionPromises2.push(
|
|
727
|
-
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
728
|
-
versions2.oxfmt = v;
|
|
729
|
-
})
|
|
730
|
-
);
|
|
731
|
-
} else if (formatter2 === "biome" && linter2 !== "biome") {
|
|
732
|
-
versionPromises2.push(
|
|
733
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
734
|
-
versions2.biome = v;
|
|
735
|
-
})
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
await Promise.all(versionPromises2);
|
|
739
|
-
packageOptions.versions = versions2;
|
|
740
|
-
const basePath2 = join(monorepoRoot, packagePath);
|
|
741
|
-
const s2 = p.spinner();
|
|
742
|
-
s2.start("Creating package...");
|
|
797
|
+
} catch {
|
|
798
|
+
}
|
|
799
|
+
return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
|
|
800
|
+
}
|
|
801
|
+
async function getWorkspacePackages(monorepoRoot) {
|
|
802
|
+
const packagesDir = join(monorepoRoot, "packages");
|
|
803
|
+
const packages = [];
|
|
804
|
+
try {
|
|
805
|
+
const { readdir } = await import('fs/promises');
|
|
806
|
+
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
807
|
+
for (const entry of entries) {
|
|
808
|
+
if (entry.isDirectory()) {
|
|
743
809
|
try {
|
|
744
|
-
const
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
} else {
|
|
753
|
-
const response = await fetch(file.url);
|
|
754
|
-
await writeFile(fullFilePath, response.body);
|
|
755
|
-
}
|
|
810
|
+
const pkgJsonPath = join(packagesDir, entry.name, "package.json");
|
|
811
|
+
const content = await readFile(pkgJsonPath, "utf-8");
|
|
812
|
+
const pkgJson = JSON.parse(content);
|
|
813
|
+
if (pkgJson.name) {
|
|
814
|
+
packages.push({
|
|
815
|
+
name: pkgJson.name,
|
|
816
|
+
path: `packages/${entry.name}`
|
|
817
|
+
});
|
|
756
818
|
}
|
|
757
|
-
|
|
758
|
-
const isLibrary2 = packageOptions.projectType === "library";
|
|
759
|
-
const nextSteps = isLibrary2 ? [
|
|
760
|
-
`cd ${packagePath}`,
|
|
761
|
-
`${packageManager2} install`,
|
|
762
|
-
`${packageManager2} run build`
|
|
763
|
-
].join("\n") : [
|
|
764
|
-
`cd ${packagePath}`,
|
|
765
|
-
`${packageManager2} install`,
|
|
766
|
-
`${packageManager2} run dev`
|
|
767
|
-
].join("\n");
|
|
768
|
-
p.note(nextSteps, "Next steps");
|
|
769
|
-
p.outro(color.green("Happy coding! \u2728"));
|
|
770
|
-
process.exit(0);
|
|
771
|
-
} catch (error) {
|
|
772
|
-
s2.stop("Failed to create package");
|
|
773
|
-
p.log.error(String(error));
|
|
774
|
-
process.exit(1);
|
|
819
|
+
} catch {
|
|
775
820
|
}
|
|
776
821
|
}
|
|
777
822
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
823
|
+
} catch {
|
|
824
|
+
}
|
|
825
|
+
return packages;
|
|
826
|
+
}
|
|
827
|
+
async function ensureConfigInWorkspace(monorepoRoot) {
|
|
828
|
+
const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
829
|
+
let content;
|
|
830
|
+
try {
|
|
831
|
+
content = await readFile(workspacePath, "utf-8");
|
|
832
|
+
} catch {
|
|
833
|
+
content = `packages:
|
|
834
|
+
- ".config/*"
|
|
835
|
+
- "packages/*"
|
|
836
|
+
`;
|
|
837
|
+
await writeFile(workspacePath, content);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (content.includes(".config/*") || content.includes('".config/*"')) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const lines = content.split("\n");
|
|
844
|
+
const packagesIndex = lines.findIndex(
|
|
845
|
+
(line) => line.trim().startsWith("packages:")
|
|
846
|
+
);
|
|
847
|
+
if (packagesIndex === -1) {
|
|
848
|
+
content = `packages:
|
|
849
|
+
- ".config/*"
|
|
850
|
+
${content}`;
|
|
851
|
+
} else {
|
|
852
|
+
lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
|
|
853
|
+
content = lines.join("\n");
|
|
854
|
+
}
|
|
855
|
+
await writeFile(workspacePath, content);
|
|
856
|
+
}
|
|
857
|
+
async function migrateEslintConfig(monorepoRoot, files) {
|
|
858
|
+
const configBasePath = ".config/eslint";
|
|
859
|
+
const existingConfigPath = join(monorepoRoot, "eslint.config.js");
|
|
860
|
+
let existingContent;
|
|
861
|
+
try {
|
|
862
|
+
existingContent = await readFile(existingConfigPath, "utf-8");
|
|
863
|
+
} catch {
|
|
864
|
+
generateEslintConfigPackage(files);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
files[`${configBasePath}/package.json`] = {
|
|
868
|
+
type: "text",
|
|
869
|
+
content: JSON.stringify(
|
|
870
|
+
{
|
|
871
|
+
name: "@config/eslint",
|
|
872
|
+
version: "0.1.0",
|
|
873
|
+
private: true,
|
|
874
|
+
type: "module",
|
|
875
|
+
exports: {
|
|
876
|
+
"./base": "./base.js",
|
|
877
|
+
"./react": "./react.js"
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
null,
|
|
881
|
+
2
|
|
882
|
+
)
|
|
883
|
+
};
|
|
884
|
+
files[`${configBasePath}/README.md`] = {
|
|
885
|
+
type: "text",
|
|
886
|
+
content: `# \`@config/eslint\`
|
|
887
|
+
|
|
888
|
+
Shared ESLint configurations.
|
|
889
|
+
|
|
890
|
+
## Usage
|
|
891
|
+
|
|
892
|
+
In your package's \`eslint.config.js\`:
|
|
893
|
+
|
|
894
|
+
\`\`\`js
|
|
895
|
+
import base from "@config/eslint/base";
|
|
896
|
+
|
|
897
|
+
export default [...base];
|
|
898
|
+
\`\`\`
|
|
899
|
+
|
|
900
|
+
## Available Configs
|
|
901
|
+
|
|
902
|
+
- \`base\` - Base ESLint rules (migrated from root)
|
|
903
|
+
- \`react\` - React-specific rules
|
|
904
|
+
`
|
|
905
|
+
};
|
|
906
|
+
files[`${configBasePath}/base.js`] = {
|
|
907
|
+
type: "text",
|
|
908
|
+
content: existingContent
|
|
909
|
+
};
|
|
910
|
+
files[`${configBasePath}/react.js`] = {
|
|
911
|
+
type: "text",
|
|
912
|
+
content: `import react from "eslint-plugin-react";
|
|
913
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
914
|
+
|
|
915
|
+
export default [
|
|
916
|
+
{
|
|
917
|
+
plugins: {
|
|
918
|
+
react,
|
|
919
|
+
"react-hooks": reactHooks,
|
|
920
|
+
},
|
|
921
|
+
rules: {
|
|
922
|
+
...react.configs.recommended.rules,
|
|
923
|
+
...reactHooks.configs.recommended.rules,
|
|
924
|
+
"react/react-in-jsx-scope": "off",
|
|
925
|
+
},
|
|
926
|
+
settings: {
|
|
927
|
+
react: {
|
|
928
|
+
version: "detect",
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
];
|
|
933
|
+
`
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
async function migratePrettierConfig(monorepoRoot, files) {
|
|
937
|
+
const configBasePath = ".config/prettier";
|
|
938
|
+
const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
|
|
939
|
+
let existingContent;
|
|
940
|
+
try {
|
|
941
|
+
existingContent = await readFile(existingConfigPath, "utf-8");
|
|
942
|
+
} catch {
|
|
943
|
+
generatePrettierConfigPackage(files);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
files[`${configBasePath}/package.json`] = {
|
|
947
|
+
type: "text",
|
|
948
|
+
content: JSON.stringify(
|
|
949
|
+
{
|
|
950
|
+
name: "@config/prettier",
|
|
951
|
+
version: "0.1.0",
|
|
952
|
+
private: true,
|
|
953
|
+
exports: {
|
|
954
|
+
"./base": "./base.json"
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
null,
|
|
958
|
+
2
|
|
959
|
+
)
|
|
960
|
+
};
|
|
961
|
+
files[`${configBasePath}/README.md`] = {
|
|
962
|
+
type: "text",
|
|
963
|
+
content: `# \`@config/prettier\`
|
|
964
|
+
|
|
965
|
+
Shared Prettier configurations.
|
|
966
|
+
|
|
967
|
+
## Usage
|
|
968
|
+
|
|
969
|
+
In your package's \`.prettierrc\`:
|
|
970
|
+
|
|
971
|
+
\`\`\`json
|
|
972
|
+
"@config/prettier/base"
|
|
973
|
+
\`\`\`
|
|
974
|
+
|
|
975
|
+
Or in \`package.json\`:
|
|
976
|
+
|
|
977
|
+
\`\`\`json
|
|
978
|
+
{
|
|
979
|
+
"prettier": "@config/prettier/base"
|
|
980
|
+
}
|
|
981
|
+
\`\`\`
|
|
982
|
+
|
|
983
|
+
## Available Configs
|
|
984
|
+
|
|
985
|
+
- \`base\` - Base Prettier rules (migrated from root)
|
|
986
|
+
`
|
|
987
|
+
};
|
|
988
|
+
files[`${configBasePath}/base.json`] = {
|
|
989
|
+
type: "text",
|
|
990
|
+
content: existingContent
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
|
|
994
|
+
const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
|
|
995
|
+
const defaultDirectories = ["apps", "packages"];
|
|
996
|
+
const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
|
|
997
|
+
const packageType = await promptForInitialPackage();
|
|
998
|
+
if (packageType === "skip") {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
const defaultDir = packageType === "app" ? "apps" : "packages";
|
|
1002
|
+
const packageNameInput = await p.text({
|
|
1003
|
+
message: "Package name?",
|
|
1004
|
+
initialValue: `@${scope}/`,
|
|
1005
|
+
validate: (value) => {
|
|
1006
|
+
const validationError = validatePackageName(value);
|
|
1007
|
+
if (validationError) return validationError;
|
|
1008
|
+
const dirName = value.includes("/") ? value.split("/").pop() : value;
|
|
1009
|
+
if (!dirName) return "Package name is required";
|
|
1010
|
+
if (!hasCustomDirectories) {
|
|
1011
|
+
const targetPath = join(monorepoRoot, defaultDir, dirName);
|
|
1012
|
+
try {
|
|
1013
|
+
const { statSync } = require$1("fs");
|
|
1014
|
+
statSync(targetPath);
|
|
1015
|
+
return `Directory ${defaultDir}/${dirName} already exists`;
|
|
1016
|
+
} catch {
|
|
1017
|
+
}
|
|
817
1018
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
if (p.isCancel(packageNameInput)) {
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
const scopedName = packageNameInput;
|
|
1025
|
+
const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
|
|
1026
|
+
const packageOptions = await promptForPackageOptions(
|
|
1027
|
+
scopedName,
|
|
1028
|
+
packageType,
|
|
1029
|
+
inheritedTooling
|
|
1030
|
+
);
|
|
1031
|
+
let targetDir = defaultDir;
|
|
1032
|
+
if (hasCustomDirectories && workspaceDirectories.length > 0) {
|
|
1033
|
+
const dirChoice = await p.select({
|
|
1034
|
+
message: "Target directory",
|
|
1035
|
+
options: workspaceDirectories.map((dir) => ({
|
|
1036
|
+
value: dir,
|
|
1037
|
+
label: dir
|
|
1038
|
+
})),
|
|
1039
|
+
initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
|
|
1040
|
+
});
|
|
1041
|
+
if (p.isCancel(dirChoice)) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
targetDir = dirChoice;
|
|
1045
|
+
const targetPath = join(monorepoRoot, targetDir, shortName);
|
|
1046
|
+
try {
|
|
1047
|
+
const { statSync } = require$1("fs");
|
|
1048
|
+
statSync(targetPath);
|
|
1049
|
+
p.log.error(`Directory ${targetDir}/${shortName} already exists`);
|
|
1050
|
+
return false;
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const relativePkgPath = join(targetDir, shortName);
|
|
1055
|
+
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
1056
|
+
packageOptions.workspaceRoot = workspaceRoot;
|
|
1057
|
+
packageOptions.name = scopedName;
|
|
1058
|
+
if (packageManager === "pnpm") {
|
|
1059
|
+
packageOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1060
|
+
} else if (packageManager === "yarn") {
|
|
1061
|
+
packageOptions.yarnVersion = await getLatestYarnVersion();
|
|
1062
|
+
} else if (packageManager === "npm") {
|
|
1063
|
+
packageOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1064
|
+
}
|
|
1065
|
+
const nodeVersion = packageOptions.nodeVersion ?? "latest";
|
|
1066
|
+
if (nodeVersion === "latest") {
|
|
1067
|
+
packageOptions.nodeVersion = await getLatestNodeVersion();
|
|
1068
|
+
}
|
|
1069
|
+
const versions = {};
|
|
1070
|
+
const versionPromises = [];
|
|
1071
|
+
const pkgIsLibrary = packageOptions.projectType === "library";
|
|
1072
|
+
const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
|
|
1073
|
+
if (pkgTesting === "vitest") {
|
|
1074
|
+
versionPromises.push(
|
|
1075
|
+
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
1076
|
+
versions.vitest = v;
|
|
1077
|
+
})
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (!pkgIsLibrary) {
|
|
1081
|
+
versionPromises.push(
|
|
1082
|
+
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1083
|
+
versions.vite = v;
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const linter = packageOptions.linter ?? "oxlint";
|
|
1088
|
+
if (linter === "eslint") {
|
|
1089
|
+
versionPromises.push(
|
|
1090
|
+
getLatestNpmVersion("eslint", "9.17.0").then((v) => {
|
|
1091
|
+
versions.eslint = v;
|
|
1092
|
+
})
|
|
1093
|
+
);
|
|
1094
|
+
} else if (linter === "oxlint") {
|
|
1095
|
+
versionPromises.push(
|
|
1096
|
+
getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
|
|
1097
|
+
versions.oxlint = v;
|
|
1098
|
+
})
|
|
1099
|
+
);
|
|
1100
|
+
} else if (linter === "biome") {
|
|
1101
|
+
versionPromises.push(
|
|
1102
|
+
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1103
|
+
versions.biome = v;
|
|
1104
|
+
})
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
const formatter = packageOptions.formatter ?? "oxfmt";
|
|
1108
|
+
if (formatter === "prettier") {
|
|
1109
|
+
versionPromises.push(
|
|
1110
|
+
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
1111
|
+
versions.prettier = v;
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
1114
|
+
} else if (formatter === "oxfmt") {
|
|
1115
|
+
versionPromises.push(
|
|
1116
|
+
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
1117
|
+
versions.oxfmt = v;
|
|
1118
|
+
})
|
|
1119
|
+
);
|
|
1120
|
+
} else if (formatter === "biome" && linter !== "biome") {
|
|
1121
|
+
versionPromises.push(
|
|
1122
|
+
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1123
|
+
versions.biome = v;
|
|
1124
|
+
})
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
await Promise.all(versionPromises);
|
|
1128
|
+
packageOptions.versions = versions;
|
|
1129
|
+
if (packageType === "app") {
|
|
1130
|
+
const workspacePackages = await getWorkspacePackages(monorepoRoot);
|
|
1131
|
+
if (workspacePackages.length > 0) {
|
|
1132
|
+
const selectedDeps = await p.multiselect({
|
|
1133
|
+
message: "Add workspace dependencies?",
|
|
1134
|
+
options: workspacePackages.map((pkgInfo) => ({
|
|
1135
|
+
value: pkgInfo.name,
|
|
1136
|
+
label: pkgInfo.name.replace(/^@[^/]+\//, "")
|
|
1137
|
+
})),
|
|
1138
|
+
required: false
|
|
1139
|
+
});
|
|
1140
|
+
if (!p.isCancel(selectedDeps) && selectedDeps.length > 0) {
|
|
1141
|
+
packageOptions.workspaceDependencies = selectedDeps;
|
|
821
1142
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
const outputPath = join(monorepoRoot, relativePkgPath);
|
|
1146
|
+
const spinner = p.spinner();
|
|
1147
|
+
spinner.start("Creating package...");
|
|
1148
|
+
try {
|
|
1149
|
+
const files = generate(packageOptions);
|
|
1150
|
+
await writeGeneratedFiles(outputPath, files);
|
|
1151
|
+
spinner.stop(
|
|
1152
|
+
color.green.inverse(` \u2713 Package created at ${relativePkgPath}! `)
|
|
1153
|
+
);
|
|
1154
|
+
const addAnother = await p.select({
|
|
1155
|
+
message: "Add another package?",
|
|
1156
|
+
options: [
|
|
1157
|
+
{ value: "no", label: "No, I'm done" },
|
|
1158
|
+
{ value: "yes", label: "Yes, add another" }
|
|
1159
|
+
],
|
|
1160
|
+
initialValue: "no"
|
|
1161
|
+
});
|
|
1162
|
+
return !p.isCancel(addAnother) && addAnother === "yes";
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
spinner.stop("Failed to create package");
|
|
1165
|
+
p.log.error(String(error));
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
async function promptAndOpenEditor(projectPath) {
|
|
1170
|
+
const savedEditor = getPreferredEditor();
|
|
1171
|
+
let selectedEditor;
|
|
1172
|
+
if (savedEditor && savedEditor !== "skip") {
|
|
1173
|
+
const useDefault = await p.confirm({
|
|
1174
|
+
message: `Open in editor? ${color.dim(`(${editorNames[savedEditor]})`)}`,
|
|
1175
|
+
initialValue: true
|
|
1176
|
+
});
|
|
1177
|
+
if (p.isCancel(useDefault)) {
|
|
1178
|
+
selectedEditor = void 0;
|
|
1179
|
+
} else if (useDefault) {
|
|
1180
|
+
selectedEditor = savedEditor;
|
|
1181
|
+
} else {
|
|
1182
|
+
selectedEditor = "skip";
|
|
1183
|
+
}
|
|
1184
|
+
} else {
|
|
1185
|
+
const openEditor = await p.select({
|
|
1186
|
+
message: "Open project in editor?",
|
|
1187
|
+
options: [
|
|
1188
|
+
{ value: "skip", label: "Skip" },
|
|
1189
|
+
{ value: "cursor", label: "Cursor" },
|
|
1190
|
+
{ value: "code", label: "VS Code" },
|
|
1191
|
+
{ value: "webstorm", label: "WebStorm" }
|
|
1192
|
+
],
|
|
1193
|
+
initialValue: "skip"
|
|
1194
|
+
});
|
|
1195
|
+
if (!p.isCancel(openEditor)) {
|
|
1196
|
+
selectedEditor = openEditor;
|
|
1197
|
+
const saveChoice = await p.confirm({
|
|
1198
|
+
message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
|
|
1199
|
+
initialValue: true
|
|
1200
|
+
});
|
|
1201
|
+
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1202
|
+
setPreferredEditor(selectedEditor);
|
|
1203
|
+
if (selectedEditor === "cursor" || selectedEditor === "code") {
|
|
1204
|
+
const reuseChoice = await p.confirm({
|
|
1205
|
+
message: "Reuse current window when opening projects?",
|
|
1206
|
+
initialValue: false
|
|
853
1207
|
});
|
|
854
|
-
if (!p.isCancel(
|
|
855
|
-
|
|
856
|
-
const packagePath = join(targetDir, packageName);
|
|
857
|
-
const packageOptions = await promptForPackageOptions(packageName, initialPackage);
|
|
858
|
-
packageOptions.workspaceRoot = "../..";
|
|
859
|
-
packageOptions.name = packageName;
|
|
860
|
-
const pkgManager = packageOptions.packageManager || "pnpm";
|
|
861
|
-
const versions2 = {};
|
|
862
|
-
const versionPromises2 = [];
|
|
863
|
-
const initPkgIsLibrary = packageOptions.projectType === "library";
|
|
864
|
-
const initPkgTesting = packageOptions.testing ?? (initPkgIsLibrary ? "vitest" : "none");
|
|
865
|
-
if (initPkgTesting === "vitest") {
|
|
866
|
-
versionPromises2.push(
|
|
867
|
-
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
868
|
-
versions2.vitest = v;
|
|
869
|
-
})
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
if (!initPkgIsLibrary) {
|
|
873
|
-
versionPromises2.push(
|
|
874
|
-
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
875
|
-
versions2.vite = v;
|
|
876
|
-
})
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
await Promise.all(versionPromises2);
|
|
880
|
-
packageOptions.versions = versions2;
|
|
881
|
-
s2.start("Creating initial package...");
|
|
882
|
-
const packageFiles = generate(packageOptions);
|
|
883
|
-
const packageFilePaths = Object.keys(packageFiles).sort();
|
|
884
|
-
const packageBasePath = join(basePath2, packagePath);
|
|
885
|
-
for (const filePath of packageFilePaths) {
|
|
886
|
-
const fullFilePath = join(packageBasePath, filePath);
|
|
887
|
-
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
888
|
-
const file = packageFiles[filePath];
|
|
889
|
-
if (file.type === "text") {
|
|
890
|
-
await writeFile(fullFilePath, file.content);
|
|
891
|
-
} else {
|
|
892
|
-
const response = await fetch(file.url);
|
|
893
|
-
await writeFile(fullFilePath, response.body);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
s2.stop("Initial package created!");
|
|
1208
|
+
if (!p.isCancel(reuseChoice)) {
|
|
1209
|
+
setReuseWindow(reuseChoice);
|
|
897
1210
|
}
|
|
898
1211
|
}
|
|
899
|
-
const nextSteps = [
|
|
900
|
-
`cd ${generateOptions.name}`,
|
|
901
|
-
`${packageManager2} install`,
|
|
902
|
-
`${packageManager2} run dev`
|
|
903
|
-
].join("\n");
|
|
904
|
-
p.note(nextSteps, "Next steps");
|
|
905
|
-
p.outro(color.green("Happy coding! \u2728"));
|
|
906
|
-
process.exit(0);
|
|
907
|
-
} catch (error) {
|
|
908
|
-
s2.stop("Failed to create monorepo workspace");
|
|
909
|
-
p.log.error(String(error));
|
|
910
|
-
process.exit(1);
|
|
911
1212
|
}
|
|
912
1213
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1214
|
+
}
|
|
1215
|
+
if (selectedEditor && selectedEditor !== "skip") {
|
|
1216
|
+
try {
|
|
1217
|
+
await openInEditor(
|
|
1218
|
+
selectedEditor,
|
|
1219
|
+
projectPath,
|
|
1220
|
+
getReuseWindow()
|
|
1221
|
+
);
|
|
1222
|
+
p.log.success(`Opening in ${editorNames[selectedEditor]}...`);
|
|
1223
|
+
} catch {
|
|
1224
|
+
p.log.warn(
|
|
1225
|
+
`Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
|
|
1226
|
+
);
|
|
919
1227
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
async function handleCheckCommand() {
|
|
1231
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1232
|
+
if (!monorepoRoot) {
|
|
1233
|
+
console.log(color.red("\u2717") + " Not a monorepo workspace");
|
|
1234
|
+
process.exit(1);
|
|
1235
|
+
}
|
|
1236
|
+
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1237
|
+
if (valid) {
|
|
1238
|
+
console.log(color.green("\u2713") + " Valid monorepo workspace");
|
|
1239
|
+
console.log(color.dim(` ${monorepoRoot}`));
|
|
1240
|
+
} else {
|
|
1241
|
+
console.log(color.red("\u2717") + " Invalid monorepo workspace");
|
|
1242
|
+
console.log(color.dim(` ${monorepoRoot}`));
|
|
1243
|
+
for (const error of errors) {
|
|
1244
|
+
console.log(color.red(` \u2022 ${error}`));
|
|
923
1245
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1246
|
+
}
|
|
1247
|
+
process.exit(valid ? 0 : 1);
|
|
1248
|
+
}
|
|
1249
|
+
async function handleFixCommand(options) {
|
|
1250
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1251
|
+
if (!monorepoRoot) {
|
|
1252
|
+
console.log(color.red("\u2717") + " Not a monorepo workspace");
|
|
1253
|
+
console.log(color.dim(" Run this command from within a monorepo"));
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1257
|
+
if (valid) {
|
|
1258
|
+
console.log(color.green("\u2713") + " Workspace is already valid");
|
|
1259
|
+
console.log(color.dim(` ${monorepoRoot}`));
|
|
1260
|
+
process.exit(0);
|
|
1261
|
+
}
|
|
1262
|
+
console.log(color.yellow("!") + " Invalid monorepo workspace");
|
|
1263
|
+
for (const error of errors) {
|
|
1264
|
+
console.log(color.dim(` \u2022 ${error}`));
|
|
1265
|
+
}
|
|
1266
|
+
console.log();
|
|
1267
|
+
const tooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1268
|
+
const existingConfigs = await detectExistingConfigs(monorepoRoot);
|
|
1269
|
+
const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
|
|
1270
|
+
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
|
|
1271
|
+
const isNonInteractive = options.linter && options.formatter;
|
|
1272
|
+
let linter;
|
|
1273
|
+
let formatter;
|
|
1274
|
+
if (isNonInteractive) {
|
|
1275
|
+
linter = options.linter;
|
|
1276
|
+
formatter = options.formatter;
|
|
1277
|
+
} else {
|
|
1278
|
+
const linterChoice = await p.select({
|
|
1279
|
+
message: "Linter",
|
|
1280
|
+
options: [
|
|
1281
|
+
{
|
|
1282
|
+
value: "oxlint",
|
|
1283
|
+
label: "oxlint" + (tooling.linter === "oxlint" ? color.dim(" (installed)") : "")
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
value: "eslint",
|
|
1287
|
+
label: "eslint" + (tooling.linter === "eslint" || existingConfigs.linter === "eslint" ? color.dim(" (installed)") : "")
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
value: "biome",
|
|
1291
|
+
label: "biome" + (tooling.linter === "biome" ? color.dim(" (installed)") : "")
|
|
1292
|
+
}
|
|
1293
|
+
],
|
|
1294
|
+
initialValue: detectedLinter
|
|
1295
|
+
});
|
|
1296
|
+
if (p.isCancel(linterChoice)) {
|
|
1297
|
+
p.cancel("Operation cancelled.");
|
|
1298
|
+
process.exit(0);
|
|
934
1299
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1300
|
+
const formatterChoice = await p.select({
|
|
1301
|
+
message: "Formatter",
|
|
1302
|
+
options: [
|
|
1303
|
+
{
|
|
1304
|
+
value: "oxfmt",
|
|
1305
|
+
label: "oxfmt" + (tooling.formatter === "oxfmt" ? color.dim(" (installed)") : "")
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
value: "prettier",
|
|
1309
|
+
label: "prettier" + (tooling.formatter === "prettier" || existingConfigs.formatter === "prettier" ? color.dim(" (installed)") : "")
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
value: "biome",
|
|
1313
|
+
label: "biome" + (tooling.formatter === "biome" ? color.dim(" (installed)") : "")
|
|
1314
|
+
}
|
|
1315
|
+
],
|
|
1316
|
+
initialValue: detectedFormatter
|
|
1317
|
+
});
|
|
1318
|
+
if (p.isCancel(formatterChoice)) {
|
|
1319
|
+
p.cancel("Operation cancelled.");
|
|
1320
|
+
process.exit(0);
|
|
941
1321
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1322
|
+
linter = linterChoice;
|
|
1323
|
+
formatter = formatterChoice;
|
|
1324
|
+
}
|
|
1325
|
+
console.log();
|
|
1326
|
+
const spinner = p.spinner();
|
|
1327
|
+
spinner.start("Fixing workspace...");
|
|
1328
|
+
try {
|
|
1329
|
+
const files = {};
|
|
1330
|
+
const tsConfigExists = await fileExists(
|
|
1331
|
+
join(monorepoRoot, ".config/typescript/package.json")
|
|
1332
|
+
);
|
|
1333
|
+
if (!tsConfigExists) {
|
|
1334
|
+
generateTypescriptConfigPackage(files);
|
|
1335
|
+
}
|
|
1336
|
+
if (linter === "oxlint") {
|
|
1337
|
+
const oxlintExists = await fileExists(
|
|
1338
|
+
join(monorepoRoot, ".config/oxlint/package.json")
|
|
954
1339
|
);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
})
|
|
1340
|
+
if (!oxlintExists) generateOxlintConfigPackage(files);
|
|
1341
|
+
} else if (linter === "eslint") {
|
|
1342
|
+
const eslintPkgExists = await fileExists(
|
|
1343
|
+
join(monorepoRoot, ".config/eslint/package.json")
|
|
960
1344
|
);
|
|
1345
|
+
if (!eslintPkgExists) {
|
|
1346
|
+
if (existingConfigs.eslintConfigPath) {
|
|
1347
|
+
await migrateEslintConfig(monorepoRoot, files);
|
|
1348
|
+
} else {
|
|
1349
|
+
generateEslintConfigPackage(files);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
961
1352
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
966
|
-
versions.prettier = v;
|
|
967
|
-
})
|
|
968
|
-
);
|
|
969
|
-
} else if (formatter === "oxfmt") {
|
|
970
|
-
versionPromises.push(
|
|
971
|
-
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
972
|
-
versions.oxfmt = v;
|
|
973
|
-
})
|
|
1353
|
+
if (formatter === "oxfmt") {
|
|
1354
|
+
const oxfmtExists = await fileExists(
|
|
1355
|
+
join(monorepoRoot, ".config/oxfmt/package.json")
|
|
974
1356
|
);
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
})
|
|
1357
|
+
if (!oxfmtExists) generateOxfmtConfigPackage(files);
|
|
1358
|
+
} else if (formatter === "prettier") {
|
|
1359
|
+
const prettierPkgExists = await fileExists(
|
|
1360
|
+
join(monorepoRoot, ".config/prettier/package.json")
|
|
980
1361
|
);
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
const basePath = join(cwd(), generateOptions.name);
|
|
985
|
-
const s = p.spinner();
|
|
986
|
-
s.start("Creating project...");
|
|
987
|
-
try {
|
|
988
|
-
const files = generate(generateOptions);
|
|
989
|
-
const filePaths = Object.keys(files).sort();
|
|
990
|
-
for (const filePath of filePaths) {
|
|
991
|
-
const fullFilePath = join(basePath, filePath);
|
|
992
|
-
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
993
|
-
const file = files[filePath];
|
|
994
|
-
if (file.type === "text") {
|
|
995
|
-
await writeFile(fullFilePath, file.content);
|
|
1362
|
+
if (!prettierPkgExists) {
|
|
1363
|
+
if (existingConfigs.prettierConfigPath) {
|
|
1364
|
+
await migratePrettierConfig(monorepoRoot, files);
|
|
996
1365
|
} else {
|
|
997
|
-
|
|
998
|
-
|
|
1366
|
+
generatePrettierConfigPackage(files);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
|
|
1371
|
+
const biomeConfig = {
|
|
1372
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1373
|
+
vcs: {
|
|
1374
|
+
enabled: true,
|
|
1375
|
+
clientKind: "git",
|
|
1376
|
+
useIgnoreFile: true
|
|
1377
|
+
},
|
|
1378
|
+
linter: {
|
|
1379
|
+
enabled: linter === "biome",
|
|
1380
|
+
rules: {
|
|
1381
|
+
recommended: true
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
formatter: {
|
|
1385
|
+
enabled: formatter === "biome"
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
files["biome.json"] = {
|
|
1389
|
+
type: "text",
|
|
1390
|
+
content: JSON.stringify(biomeConfig, null, 2)
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
for (const [filePath, file] of Object.entries(files)) {
|
|
1394
|
+
const fullPath = join(monorepoRoot, filePath);
|
|
1395
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1396
|
+
await writeFile(fullPath, file.content);
|
|
1397
|
+
}
|
|
1398
|
+
await ensureConfigInWorkspace(monorepoRoot);
|
|
1399
|
+
if (existingConfigs.eslintConfigPath && linter === "eslint") {
|
|
1400
|
+
try {
|
|
1401
|
+
await unlink(existingConfigs.eslintConfigPath);
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (existingConfigs.prettierConfigPath && formatter === "prettier") {
|
|
1406
|
+
try {
|
|
1407
|
+
await unlink(existingConfigs.prettierConfigPath);
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
spinner.stop(color.green("\u2713") + " Workspace fixed!");
|
|
1412
|
+
const generated = Object.keys(files).filter(
|
|
1413
|
+
(f) => f.endsWith("package.json")
|
|
1414
|
+
);
|
|
1415
|
+
for (const pkgFile of generated) {
|
|
1416
|
+
const pkgName = pkgFile.replace("/package.json", "");
|
|
1417
|
+
console.log(color.dim(` Generated ${pkgName}`));
|
|
1418
|
+
}
|
|
1419
|
+
const vscodeSettingsExists = await fileExists(
|
|
1420
|
+
join(monorepoRoot, ".vscode/settings.json")
|
|
1421
|
+
);
|
|
1422
|
+
const vscodeExtensionsExists = await fileExists(
|
|
1423
|
+
join(monorepoRoot, ".vscode/extensions.json")
|
|
1424
|
+
);
|
|
1425
|
+
const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
|
|
1426
|
+
if (!vscodeExists) {
|
|
1427
|
+
let addVscode = false;
|
|
1428
|
+
if (isNonInteractive) {
|
|
1429
|
+
addVscode = true;
|
|
1430
|
+
} else {
|
|
1431
|
+
const vscodeChoice = await p.confirm({
|
|
1432
|
+
message: "Generate VS Code settings?",
|
|
1433
|
+
initialValue: true
|
|
1434
|
+
});
|
|
1435
|
+
addVscode = !p.isCancel(vscodeChoice) && vscodeChoice;
|
|
1436
|
+
}
|
|
1437
|
+
if (addVscode) {
|
|
1438
|
+
const vscodeFiles = {};
|
|
1439
|
+
generateVscodeFiles(vscodeFiles, linter, formatter);
|
|
1440
|
+
for (const [filePath, file] of Object.entries(vscodeFiles)) {
|
|
1441
|
+
const fullPath = join(monorepoRoot, filePath);
|
|
1442
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1443
|
+
await writeFile(fullPath, file.content);
|
|
999
1444
|
}
|
|
1445
|
+
console.log(color.dim(" Generated .vscode/settings.json"));
|
|
1446
|
+
console.log(color.dim(" Generated .vscode/extensions.json"));
|
|
1000
1447
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1448
|
+
}
|
|
1449
|
+
const aiFilePaths = {
|
|
1450
|
+
"cursor-rules": ".cursor/rules",
|
|
1451
|
+
"agents-md": "AGENTS.md",
|
|
1452
|
+
"claude-md": "CLAUDE.md",
|
|
1453
|
+
"copilot-md": ".github/copilot-instructions.md"
|
|
1454
|
+
};
|
|
1455
|
+
const existingAiFiles = [];
|
|
1456
|
+
for (const [choice, path] of Object.entries(aiFilePaths)) {
|
|
1457
|
+
if (await fileExists(join(monorepoRoot, path))) {
|
|
1458
|
+
existingAiFiles.push(choice);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
let selectedAiFiles = [];
|
|
1462
|
+
const savedAiFiles = getAiFiles();
|
|
1463
|
+
const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
|
|
1464
|
+
if (availableChoices.length === 0) {
|
|
1465
|
+
} else if (isNonInteractive) {
|
|
1466
|
+
const preferred = savedAiFiles ?? ["cursor-rules"];
|
|
1467
|
+
selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
|
|
1468
|
+
} else if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1469
|
+
const availableSaved = savedAiFiles.filter(
|
|
1470
|
+
(f) => availableChoices.includes(f)
|
|
1471
|
+
);
|
|
1472
|
+
if (availableSaved.length > 0) {
|
|
1473
|
+
const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
|
|
1016
1474
|
const useDefault = await p.confirm({
|
|
1017
|
-
message: `
|
|
1475
|
+
message: `Generate AI instruction files? ${color.dim(
|
|
1476
|
+
`(${savedLabels})`
|
|
1477
|
+
)}`,
|
|
1018
1478
|
initialValue: true
|
|
1019
1479
|
});
|
|
1020
|
-
if (p.isCancel(useDefault)) {
|
|
1021
|
-
|
|
1022
|
-
} else if (useDefault) {
|
|
1023
|
-
selectedEditor = savedEditor;
|
|
1024
|
-
} else {
|
|
1025
|
-
selectedEditor = "skip";
|
|
1480
|
+
if (!p.isCancel(useDefault) && useDefault) {
|
|
1481
|
+
selectedAiFiles = availableSaved;
|
|
1026
1482
|
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1483
|
+
}
|
|
1484
|
+
} else {
|
|
1485
|
+
const aiFilesChoice = await p.multiselect({
|
|
1486
|
+
message: "Generate AI instruction files?",
|
|
1487
|
+
options: availableChoices.map((c) => ({
|
|
1488
|
+
value: c,
|
|
1489
|
+
label: aiFilePaths[c],
|
|
1490
|
+
hint: c === "cursor-rules" ? "Cursor AI" : c === "agents-md" ? "GitHub Copilot, general" : c === "claude-md" ? "Claude" : "GitHub Copilot"
|
|
1491
|
+
})),
|
|
1492
|
+
required: false
|
|
1493
|
+
});
|
|
1494
|
+
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1495
|
+
selectedAiFiles = aiFilesChoice;
|
|
1496
|
+
const saveChoice = await p.confirm({
|
|
1497
|
+
message: "Save as default for future?",
|
|
1498
|
+
initialValue: true
|
|
1037
1499
|
});
|
|
1038
|
-
if (!p.isCancel(
|
|
1039
|
-
|
|
1040
|
-
const saveChoice = await p.confirm({
|
|
1041
|
-
message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
|
|
1042
|
-
initialValue: true
|
|
1043
|
-
});
|
|
1044
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1045
|
-
setPreferredEditor(selectedEditor);
|
|
1046
|
-
if (selectedEditor === "cursor" || selectedEditor === "code") {
|
|
1047
|
-
const reuseChoice = await p.confirm({
|
|
1048
|
-
message: "Reuse current window when opening projects?",
|
|
1049
|
-
initialValue: false
|
|
1050
|
-
});
|
|
1051
|
-
if (!p.isCancel(reuseChoice)) {
|
|
1052
|
-
setReuseWindow(reuseChoice);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1500
|
+
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1501
|
+
setAiFiles(selectedAiFiles);
|
|
1056
1502
|
}
|
|
1057
1503
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1504
|
+
}
|
|
1505
|
+
if (selectedAiFiles.length > 0) {
|
|
1506
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
1507
|
+
const aiFilesOutput = {};
|
|
1508
|
+
generateAiFiles(aiFilesOutput, {
|
|
1509
|
+
name: scope,
|
|
1510
|
+
packageManager: "pnpm",
|
|
1511
|
+
linter,
|
|
1512
|
+
formatter,
|
|
1513
|
+
aiFiles: selectedAiFiles
|
|
1514
|
+
});
|
|
1515
|
+
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
1516
|
+
const fullPath = join(monorepoRoot, filePath);
|
|
1517
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
1518
|
+
await writeFile(fullPath, file.content);
|
|
1519
|
+
console.log(color.dim(` Generated ${filePath}`));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
process.exit(0);
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
spinner.stop(color.red("\u2717") + " Failed to fix workspace");
|
|
1525
|
+
console.error(error);
|
|
1526
|
+
process.exit(1);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
async function handleWorkspaceCommand(name, options) {
|
|
1530
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1531
|
+
if (!monorepoRoot) {
|
|
1532
|
+
console.error(
|
|
1533
|
+
color.red("Error:") + " --workspace flag requires being inside a monorepo"
|
|
1534
|
+
);
|
|
1535
|
+
process.exit(1);
|
|
1536
|
+
}
|
|
1537
|
+
if (!name) {
|
|
1538
|
+
console.error(
|
|
1539
|
+
color.red("Error:") + " Package name is required with --workspace flag"
|
|
1540
|
+
);
|
|
1541
|
+
console.log(
|
|
1542
|
+
color.dim(
|
|
1543
|
+
" Example: pnpm create krispya my-lib --workspace --type library"
|
|
1544
|
+
)
|
|
1545
|
+
);
|
|
1546
|
+
process.exit(1);
|
|
1547
|
+
}
|
|
1548
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
1549
|
+
const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1550
|
+
const projectType = options.type ?? "app";
|
|
1551
|
+
const defaultDir = projectType === "library" ? "packages" : "apps";
|
|
1552
|
+
const targetDir = options.dir ?? defaultDir;
|
|
1553
|
+
const template = options.template ?? "vanilla";
|
|
1554
|
+
const baseTemplate = getBaseTemplate(template);
|
|
1555
|
+
const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
|
|
1556
|
+
const fullPackagePath = join(monorepoRoot, targetDir, name);
|
|
1557
|
+
try {
|
|
1558
|
+
await access(fullPackagePath, constants$1.F_OK);
|
|
1559
|
+
console.error(
|
|
1560
|
+
color.red("Error:") + ` Directory ${targetDir}/${name} already exists`
|
|
1561
|
+
);
|
|
1562
|
+
process.exit(1);
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
const versions = {};
|
|
1566
|
+
const versionPromises = [];
|
|
1567
|
+
const isLibrary = projectType === "library";
|
|
1568
|
+
if (!isLibrary) {
|
|
1569
|
+
versionPromises.push(
|
|
1570
|
+
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1571
|
+
versions.vite = v;
|
|
1572
|
+
})
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
|
|
1576
|
+
const formatter = inheritedTooling.formatter ?? options.formatter ?? "oxfmt";
|
|
1577
|
+
await Promise.all(versionPromises);
|
|
1578
|
+
const relativePkgPath = join(targetDir, name);
|
|
1579
|
+
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
1580
|
+
const generateOptions = {
|
|
1581
|
+
name: scopedName,
|
|
1582
|
+
projectType,
|
|
1583
|
+
libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
|
|
1584
|
+
template,
|
|
1585
|
+
linter,
|
|
1586
|
+
formatter,
|
|
1587
|
+
workspaceRoot,
|
|
1588
|
+
versions,
|
|
1589
|
+
...baseTemplate === "r3f" && {
|
|
1590
|
+
drei: options.drei ? {} : void 0,
|
|
1591
|
+
handle: options.handle ? {} : void 0,
|
|
1592
|
+
leva: options.leva ? {} : void 0,
|
|
1593
|
+
postprocessing: options.postprocessing ? {} : void 0,
|
|
1594
|
+
rapier: options.rapier ? {} : void 0,
|
|
1595
|
+
xr: options.xr ? {} : void 0,
|
|
1596
|
+
uikit: options.uikit ? {} : void 0,
|
|
1597
|
+
offscreen: options.offscreen ? {} : void 0,
|
|
1598
|
+
zustand: options.zustand ? {} : void 0,
|
|
1599
|
+
koota: options.koota ? {} : void 0,
|
|
1600
|
+
viverse: options.viverse ? {} : void 0,
|
|
1601
|
+
triplex: options.triplex ? {} : void 0
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
console.log(
|
|
1605
|
+
color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`
|
|
1606
|
+
);
|
|
1607
|
+
try {
|
|
1608
|
+
const files = generate(generateOptions);
|
|
1609
|
+
await writeGeneratedFiles(fullPackagePath, files);
|
|
1610
|
+
console.log(
|
|
1611
|
+
color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`
|
|
1612
|
+
);
|
|
1613
|
+
process.exit(0);
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
console.error(color.red("Error:") + " Failed to create package");
|
|
1616
|
+
console.error(String(error));
|
|
1617
|
+
process.exit(1);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async function handleMonorepoCreation(generateOptions) {
|
|
1621
|
+
const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.s; });
|
|
1622
|
+
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1623
|
+
if (packageManager === "pnpm") {
|
|
1624
|
+
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1625
|
+
} else if (packageManager === "yarn") {
|
|
1626
|
+
generateOptions.yarnVersion = await getLatestYarnVersion();
|
|
1627
|
+
} else if (packageManager === "npm") {
|
|
1628
|
+
generateOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1629
|
+
}
|
|
1630
|
+
const nodeVersion = generateOptions.nodeVersion ?? "latest";
|
|
1631
|
+
if (nodeVersion === "latest") {
|
|
1632
|
+
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1633
|
+
}
|
|
1634
|
+
const savedAiFiles = getAiFiles();
|
|
1635
|
+
let selectedAiFiles = [];
|
|
1636
|
+
if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1637
|
+
const aiFileLabels = {
|
|
1638
|
+
"cursor-rules": ".cursor/rules",
|
|
1639
|
+
"agents-md": "AGENTS.md",
|
|
1640
|
+
"claude-md": "CLAUDE.md",
|
|
1641
|
+
"copilot-md": ".github/copilot-instructions.md"
|
|
1642
|
+
};
|
|
1643
|
+
const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
|
|
1644
|
+
const useDefault = await p.confirm({
|
|
1645
|
+
message: `Generate AI instruction files? ${color.dim(
|
|
1646
|
+
`(${savedLabels})`
|
|
1647
|
+
)}`,
|
|
1648
|
+
initialValue: true
|
|
1649
|
+
});
|
|
1650
|
+
if (!p.isCancel(useDefault) && useDefault) {
|
|
1651
|
+
selectedAiFiles = savedAiFiles;
|
|
1652
|
+
}
|
|
1653
|
+
} else {
|
|
1654
|
+
const aiFilesChoice = await p.multiselect({
|
|
1655
|
+
message: "Generate AI instruction files?",
|
|
1656
|
+
options: [
|
|
1657
|
+
{ value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
|
|
1658
|
+
{
|
|
1659
|
+
value: "agents-md",
|
|
1660
|
+
label: "AGENTS.md",
|
|
1661
|
+
hint: "GitHub Copilot, general"
|
|
1662
|
+
},
|
|
1663
|
+
{ value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
|
|
1664
|
+
{
|
|
1665
|
+
value: "copilot-md",
|
|
1666
|
+
label: ".github/copilot-instructions.md",
|
|
1667
|
+
hint: "GitHub Copilot"
|
|
1070
1668
|
}
|
|
1669
|
+
],
|
|
1670
|
+
required: false
|
|
1671
|
+
});
|
|
1672
|
+
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1673
|
+
selectedAiFiles = aiFilesChoice;
|
|
1674
|
+
const saveChoice = await p.confirm({
|
|
1675
|
+
message: "Save as default for future monorepos?",
|
|
1676
|
+
initialValue: true
|
|
1677
|
+
});
|
|
1678
|
+
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1679
|
+
setAiFiles(selectedAiFiles);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const projectPath = join(cwd(), generateOptions.name);
|
|
1684
|
+
const spinner = p.spinner();
|
|
1685
|
+
spinner.start("Creating monorepo workspace...");
|
|
1686
|
+
try {
|
|
1687
|
+
const { files } = generateMonorepo({
|
|
1688
|
+
name: generateOptions.name,
|
|
1689
|
+
linter: generateOptions.linter ?? "oxlint",
|
|
1690
|
+
formatter: generateOptions.formatter ?? "oxfmt",
|
|
1691
|
+
packageManager,
|
|
1692
|
+
pnpmVersion: generateOptions.pnpmVersion,
|
|
1693
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
1694
|
+
nodeVersion: generateOptions.nodeVersion,
|
|
1695
|
+
aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
|
|
1696
|
+
});
|
|
1697
|
+
const filePaths = Object.keys(files).sort();
|
|
1698
|
+
for (const filePath of filePaths) {
|
|
1699
|
+
const fullFilePath = join(projectPath, filePath);
|
|
1700
|
+
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
1701
|
+
const file = files[filePath];
|
|
1702
|
+
if (file.type === "text") {
|
|
1703
|
+
await writeFile(fullFilePath, file.content);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
|
|
1707
|
+
const newMonorepoTooling = {
|
|
1708
|
+
linter: generateOptions.linter,
|
|
1709
|
+
formatter: generateOptions.formatter
|
|
1710
|
+
};
|
|
1711
|
+
const scope = generateOptions.name;
|
|
1712
|
+
let addMore = true;
|
|
1713
|
+
while (addMore) {
|
|
1714
|
+
addMore = await createPackageInWorkspace(
|
|
1715
|
+
projectPath,
|
|
1716
|
+
packageManager,
|
|
1717
|
+
newMonorepoTooling,
|
|
1718
|
+
scope
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
const nextSteps = [
|
|
1722
|
+
`cd ${generateOptions.name}`,
|
|
1723
|
+
`${packageManager} install`,
|
|
1724
|
+
`${packageManager} run dev`
|
|
1725
|
+
].join("\n");
|
|
1726
|
+
p.note(nextSteps, "Next steps");
|
|
1727
|
+
await promptAndOpenEditor(projectPath);
|
|
1728
|
+
p.outro(color.green("Happy coding! \u2728"));
|
|
1729
|
+
process.exit(0);
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
spinner.stop("Failed to create monorepo workspace");
|
|
1732
|
+
p.log.error(String(error));
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
async function handleStandaloneProjectCreation(generateOptions) {
|
|
1737
|
+
const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
|
|
1738
|
+
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
1739
|
+
generateOptions.name ??= defaultFallbackName;
|
|
1740
|
+
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1741
|
+
if (packageManager === "pnpm") {
|
|
1742
|
+
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1743
|
+
} else if (packageManager === "yarn") {
|
|
1744
|
+
generateOptions.yarnVersion = await getLatestYarnVersion();
|
|
1745
|
+
} else if (packageManager === "npm") {
|
|
1746
|
+
generateOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1747
|
+
}
|
|
1748
|
+
const nodeVersion = generateOptions.nodeVersion ?? "latest";
|
|
1749
|
+
if (nodeVersion === "latest") {
|
|
1750
|
+
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1751
|
+
}
|
|
1752
|
+
const versions = {};
|
|
1753
|
+
const versionPromises = [];
|
|
1754
|
+
const isLibrary = generateOptions.projectType === "library";
|
|
1755
|
+
const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
|
|
1756
|
+
if (testing === "vitest") {
|
|
1757
|
+
versionPromises.push(
|
|
1758
|
+
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
1759
|
+
versions.vitest = v;
|
|
1760
|
+
})
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
if (!isLibrary) {
|
|
1764
|
+
versionPromises.push(
|
|
1765
|
+
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1766
|
+
versions.vite = v;
|
|
1767
|
+
})
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
const linter = generateOptions.linter ?? "oxlint";
|
|
1771
|
+
if (linter === "eslint") {
|
|
1772
|
+
versionPromises.push(
|
|
1773
|
+
getLatestNpmVersion("eslint", "9.17.0").then((v) => {
|
|
1774
|
+
versions.eslint = v;
|
|
1775
|
+
})
|
|
1776
|
+
);
|
|
1777
|
+
} else if (linter === "oxlint") {
|
|
1778
|
+
versionPromises.push(
|
|
1779
|
+
getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
|
|
1780
|
+
versions.oxlint = v;
|
|
1781
|
+
})
|
|
1782
|
+
);
|
|
1783
|
+
} else if (linter === "biome") {
|
|
1784
|
+
versionPromises.push(
|
|
1785
|
+
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1786
|
+
versions.biome = v;
|
|
1787
|
+
})
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
const formatter = generateOptions.formatter ?? "oxfmt";
|
|
1791
|
+
if (formatter === "prettier") {
|
|
1792
|
+
versionPromises.push(
|
|
1793
|
+
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
1794
|
+
versions.prettier = v;
|
|
1795
|
+
})
|
|
1796
|
+
);
|
|
1797
|
+
} else if (formatter === "oxfmt") {
|
|
1798
|
+
versionPromises.push(
|
|
1799
|
+
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
1800
|
+
versions.oxfmt = v;
|
|
1801
|
+
})
|
|
1802
|
+
);
|
|
1803
|
+
} else if (formatter === "biome" && linter !== "biome") {
|
|
1804
|
+
versionPromises.push(
|
|
1805
|
+
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1806
|
+
versions.biome = v;
|
|
1807
|
+
})
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
await Promise.all(versionPromises);
|
|
1811
|
+
generateOptions.versions = versions;
|
|
1812
|
+
const projectPath = join(cwd(), generateOptions.name);
|
|
1813
|
+
const spinner = p.spinner();
|
|
1814
|
+
spinner.start("Creating project...");
|
|
1815
|
+
try {
|
|
1816
|
+
const files = generate(generateOptions);
|
|
1817
|
+
await writeGeneratedFiles(projectPath, files);
|
|
1818
|
+
spinner.stop(color.green.inverse(" \u2713 Project created! "));
|
|
1819
|
+
const nextSteps = isLibrary ? [
|
|
1820
|
+
`cd ${generateOptions.name}`,
|
|
1821
|
+
`${packageManager} install`,
|
|
1822
|
+
`${packageManager} run build`
|
|
1823
|
+
].join("\n") : [
|
|
1824
|
+
`cd ${generateOptions.name}`,
|
|
1825
|
+
`${packageManager} install`,
|
|
1826
|
+
`${packageManager} run dev`
|
|
1827
|
+
].join("\n");
|
|
1828
|
+
p.note(nextSteps, "Next steps");
|
|
1829
|
+
await promptAndOpenEditor(projectPath);
|
|
1830
|
+
p.outro(color.green("Happy coding! \u2728"));
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
spinner.stop("Failed to create project");
|
|
1833
|
+
p.log.error(String(error));
|
|
1834
|
+
process.exit(1);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
async function handleInteractiveMonorepoMode(monorepoRoot) {
|
|
1838
|
+
const choice = await p.select({
|
|
1839
|
+
message: "Detected monorepo workspace",
|
|
1840
|
+
options: [
|
|
1841
|
+
{ value: "add", label: "Add new package to this workspace" },
|
|
1842
|
+
{ value: "standalone", label: "Create standalone project" }
|
|
1843
|
+
],
|
|
1844
|
+
initialValue: "add"
|
|
1845
|
+
});
|
|
1846
|
+
if (p.isCancel(choice)) {
|
|
1847
|
+
p.cancel("Operation cancelled.");
|
|
1848
|
+
process.exit(0);
|
|
1849
|
+
}
|
|
1850
|
+
if (choice === "add") {
|
|
1851
|
+
const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1852
|
+
if (inheritedTooling.linter || inheritedTooling.formatter) {
|
|
1853
|
+
const toolingInfo = [
|
|
1854
|
+
inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
|
|
1855
|
+
inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
|
|
1856
|
+
].filter(Boolean).join(", ");
|
|
1857
|
+
p.log.info(`Using workspace tooling (${toolingInfo})`);
|
|
1858
|
+
}
|
|
1859
|
+
const scope = await getMonorepoScope(monorepoRoot);
|
|
1860
|
+
let addMore = true;
|
|
1861
|
+
while (addMore) {
|
|
1862
|
+
addMore = await createPackageInWorkspace(
|
|
1863
|
+
monorepoRoot,
|
|
1864
|
+
"pnpm",
|
|
1865
|
+
inheritedTooling,
|
|
1866
|
+
scope
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
p.note(
|
|
1870
|
+
[`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"),
|
|
1871
|
+
"Next steps"
|
|
1872
|
+
);
|
|
1873
|
+
await promptAndOpenEditor(monorepoRoot);
|
|
1874
|
+
p.outro(color.green("Happy coding! \u2728"));
|
|
1875
|
+
process.exit(0);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
async function main() {
|
|
1879
|
+
const program = new Command().name("create-krispya").description(
|
|
1880
|
+
"CLI for creating Vanilla, React, and React Three Fiber projects"
|
|
1881
|
+
).argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
|
|
1882
|
+
"--bundler <bundler>",
|
|
1883
|
+
"library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
|
|
1884
|
+
).option(
|
|
1885
|
+
"--template <type>",
|
|
1886
|
+
"project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
|
|
1887
|
+
).option(
|
|
1888
|
+
"--linter <type>",
|
|
1889
|
+
"linter: eslint, oxlint, or biome (default: oxlint)"
|
|
1890
|
+
).option(
|
|
1891
|
+
"--formatter <type>",
|
|
1892
|
+
"formatter: prettier, oxfmt, or biome (default: oxfmt)"
|
|
1893
|
+
).option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option(
|
|
1894
|
+
"--package-manager <manager>",
|
|
1895
|
+
"specify package manager (e.g. npm, yarn, pnpm)"
|
|
1896
|
+
).option(
|
|
1897
|
+
"--pnpm-manage-versions",
|
|
1898
|
+
"enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
|
|
1899
|
+
).option(
|
|
1900
|
+
"--no-pnpm-manage-versions",
|
|
1901
|
+
"disable manage-package-manager-versions in pnpm-workspace.yaml"
|
|
1902
|
+
).option(
|
|
1903
|
+
"--node-version <version>",
|
|
1904
|
+
'set Node.js version for engines.node field (default: "latest")'
|
|
1905
|
+
).option(
|
|
1906
|
+
"--workspace",
|
|
1907
|
+
"Add package to current monorepo workspace (non-interactive)"
|
|
1908
|
+
).option(
|
|
1909
|
+
"--dir <directory>",
|
|
1910
|
+
"Target directory for --workspace (default: apps/ or packages/)"
|
|
1911
|
+
).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
|
|
1912
|
+
"--check",
|
|
1913
|
+
"Check if current directory is in a valid monorepo workspace"
|
|
1914
|
+
).option("--fix", "Fix monorepo by generating missing .config packages").option(
|
|
1915
|
+
"--path <directory>",
|
|
1916
|
+
"Run in specified directory instead of current working directory"
|
|
1917
|
+
).action(async (name, options) => {
|
|
1918
|
+
if (options.path) {
|
|
1919
|
+
process.chdir(options.path);
|
|
1920
|
+
}
|
|
1921
|
+
if (options.clearConfig) {
|
|
1922
|
+
clearConfig();
|
|
1923
|
+
console.log("Configuration cleared.");
|
|
1924
|
+
process.exit(0);
|
|
1925
|
+
}
|
|
1926
|
+
if (options.configPath) {
|
|
1927
|
+
console.log(getConfigPath());
|
|
1928
|
+
process.exit(0);
|
|
1929
|
+
}
|
|
1930
|
+
if (name?.startsWith("-")) {
|
|
1931
|
+
switch (name) {
|
|
1932
|
+
case "--version":
|
|
1933
|
+
case "-V":
|
|
1934
|
+
console.log(pkg.version);
|
|
1935
|
+
process.exit(0);
|
|
1936
|
+
case "--help":
|
|
1937
|
+
case "-h":
|
|
1938
|
+
program.help();
|
|
1939
|
+
break;
|
|
1940
|
+
case "--clear-config":
|
|
1941
|
+
clearConfig();
|
|
1942
|
+
console.log("Configuration cleared.");
|
|
1943
|
+
process.exit(0);
|
|
1944
|
+
case "--config-path":
|
|
1945
|
+
console.log(getConfigPath());
|
|
1946
|
+
process.exit(0);
|
|
1947
|
+
case "--check":
|
|
1948
|
+
await handleCheckCommand();
|
|
1949
|
+
break;
|
|
1950
|
+
case "--fix":
|
|
1951
|
+
options.fix = true;
|
|
1952
|
+
break;
|
|
1953
|
+
default:
|
|
1954
|
+
console.error(color.red(`Unknown option: ${name}`));
|
|
1955
|
+
process.exit(1);
|
|
1071
1956
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1957
|
+
}
|
|
1958
|
+
if (options.check) {
|
|
1959
|
+
await handleCheckCommand();
|
|
1960
|
+
}
|
|
1961
|
+
if (options.fix) {
|
|
1962
|
+
await handleFixCommand(options);
|
|
1963
|
+
}
|
|
1964
|
+
if (options.dir && !options.workspace) {
|
|
1965
|
+
console.error(color.red("Error:") + " --dir requires --workspace flag");
|
|
1966
|
+
console.log(
|
|
1967
|
+
color.dim(
|
|
1968
|
+
" Example: pnpm create krispya my-lib --workspace --dir examples"
|
|
1969
|
+
)
|
|
1970
|
+
);
|
|
1076
1971
|
process.exit(1);
|
|
1077
1972
|
}
|
|
1973
|
+
if (options.workspace) {
|
|
1974
|
+
await handleWorkspaceCommand(name, options);
|
|
1975
|
+
}
|
|
1976
|
+
console.clear();
|
|
1977
|
+
p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
|
|
1978
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
1979
|
+
if (monorepoRoot && Object.keys(options).length === 0) {
|
|
1980
|
+
await handleInteractiveMonorepoMode(monorepoRoot);
|
|
1981
|
+
}
|
|
1982
|
+
let generateOptions;
|
|
1983
|
+
if (Object.keys(options).length > 0) {
|
|
1984
|
+
const template = options.template ?? "vanilla";
|
|
1985
|
+
const baseTemplate = getBaseTemplate(template);
|
|
1986
|
+
const defaultName = getDefaultProjectName(template);
|
|
1987
|
+
const projectType = options.type ?? "app";
|
|
1988
|
+
generateOptions = {
|
|
1989
|
+
name: name || defaultName,
|
|
1990
|
+
projectType,
|
|
1991
|
+
libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
|
|
1992
|
+
template,
|
|
1993
|
+
linter: options.linter ?? "oxlint",
|
|
1994
|
+
formatter: options.formatter ?? "oxfmt",
|
|
1995
|
+
...baseTemplate === "r3f" && {
|
|
1996
|
+
drei: options.drei ? {} : void 0,
|
|
1997
|
+
handle: options.handle ? {} : void 0,
|
|
1998
|
+
leva: options.leva ? {} : void 0,
|
|
1999
|
+
postprocessing: options.postprocessing ? {} : void 0,
|
|
2000
|
+
rapier: options.rapier ? {} : void 0,
|
|
2001
|
+
xr: options.xr ? {} : void 0,
|
|
2002
|
+
uikit: options.uikit ? {} : void 0,
|
|
2003
|
+
offscreen: options.offscreen ? {} : void 0,
|
|
2004
|
+
zustand: options.zustand ? {} : void 0,
|
|
2005
|
+
koota: options.koota ? {} : void 0,
|
|
2006
|
+
viverse: options.viverse ? {} : void 0,
|
|
2007
|
+
triplex: options.triplex ? {} : void 0
|
|
2008
|
+
},
|
|
2009
|
+
packageManager: options.packageManager,
|
|
2010
|
+
pnpmManageVersions: options.pnpmManageVersions,
|
|
2011
|
+
nodeVersion: options.nodeVersion ?? "latest"
|
|
2012
|
+
};
|
|
2013
|
+
} else {
|
|
2014
|
+
generateOptions = await promptForOptions(name);
|
|
2015
|
+
}
|
|
2016
|
+
if (generateOptions.projectType === "monorepo") {
|
|
2017
|
+
await handleMonorepoCreation(generateOptions);
|
|
2018
|
+
} else {
|
|
2019
|
+
await handleStandaloneProjectCreation(generateOptions);
|
|
2020
|
+
}
|
|
1078
2021
|
});
|
|
1079
2022
|
await program.parseAsync();
|
|
1080
2023
|
}
|