create-krispya 0.4.7 → 0.5.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 +89 -30
- package/dist/chunks/index.cjs +2379 -0
- package/dist/chunks/index.mjs +2365 -0
- package/dist/cli.cjs +610 -111
- package/dist/cli.mjs +611 -113
- package/dist/index.cjs +10 -1714
- package/dist/index.d.cts +83 -79
- package/dist/index.d.mts +83 -79
- package/dist/index.d.ts +83 -79
- package/dist/index.mjs +2 -1707
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,60 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { cwd } from 'process';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { join, dirname, resolve } from 'path';
|
|
5
|
+
import { mkdir, writeFile, access, readFile } from 'fs/promises';
|
|
6
|
+
import { constants } 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 getLatestPnpmVersion, d as getLatestNodeVersion, e as getLatestNpmVersion, f as generate } from './chunks/index.mjs';
|
|
13
|
+
import Conf from 'conf';
|
|
12
14
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
linter: "oxlint",
|
|
37
|
-
formatter: "oxfmt"
|
|
38
|
-
};
|
|
39
|
-
if (baseTemplate === "r3f") {
|
|
40
|
-
return {
|
|
41
|
-
...base,
|
|
42
|
-
drei: {},
|
|
43
|
-
handle: {},
|
|
44
|
-
leva: {},
|
|
45
|
-
postprocessing: {},
|
|
46
|
-
rapier: {},
|
|
47
|
-
xr: {},
|
|
48
|
-
uikit: {},
|
|
49
|
-
offscreen: {},
|
|
50
|
-
zustand: {},
|
|
51
|
-
koota: {},
|
|
52
|
-
triplex: {},
|
|
53
|
-
viverse: {}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
return base;
|
|
15
|
+
const editorNames = {
|
|
16
|
+
cursor: "Cursor",
|
|
17
|
+
code: "VS Code",
|
|
18
|
+
webstorm: "WebStorm",
|
|
19
|
+
skip: "Skip"
|
|
20
|
+
};
|
|
21
|
+
function openInEditor(editor, path, reuseWindow) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const isWindows = process.platform === "win32";
|
|
24
|
+
const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
|
|
25
|
+
const args = useReuseFlag ? ["-r", path] : [path];
|
|
26
|
+
const child = isWindows ? spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
|
|
27
|
+
detached: true,
|
|
28
|
+
stdio: "ignore",
|
|
29
|
+
shell: true
|
|
30
|
+
}) : spawn(editor, args, {
|
|
31
|
+
detached: true,
|
|
32
|
+
stdio: "ignore"
|
|
33
|
+
});
|
|
34
|
+
child.on("error", reject);
|
|
35
|
+
child.unref();
|
|
36
|
+
setTimeout(resolve, 100);
|
|
37
|
+
});
|
|
57
38
|
}
|
|
39
|
+
|
|
58
40
|
function formatConfigSummary(options) {
|
|
59
41
|
const lines = [];
|
|
60
42
|
const VALUE_COL = 27;
|
|
@@ -68,6 +50,12 @@ function formatConfigSummary(options) {
|
|
|
68
50
|
return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
|
|
69
51
|
};
|
|
70
52
|
const projectType = options.projectType ?? "app";
|
|
53
|
+
const baseTemplate = options.template ? getBaseTemplate(options.template) : "vanilla";
|
|
54
|
+
if (baseTemplate === "react") {
|
|
55
|
+
lines.push(formatRow("Framework", "React"));
|
|
56
|
+
} else if (baseTemplate === "r3f") {
|
|
57
|
+
lines.push(formatRow("Framework", "React Three Fiber"));
|
|
58
|
+
}
|
|
71
59
|
const language = options.template ? getLanguageFromTemplate(options.template) : "typescript";
|
|
72
60
|
lines.push(formatRow("Language", formatLanguage(language)));
|
|
73
61
|
if (projectType === "library") {
|
|
@@ -87,7 +75,8 @@ function formatConfigSummary(options) {
|
|
|
87
75
|
if (options.formatter) {
|
|
88
76
|
lines.push(formatRow("Formatter", options.formatter));
|
|
89
77
|
}
|
|
90
|
-
|
|
78
|
+
const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
|
|
79
|
+
lines.push(formatRow("Testing", testing));
|
|
91
80
|
if (options.template && getBaseTemplate(options.template) === "r3f") {
|
|
92
81
|
const integrationNames = [
|
|
93
82
|
options.drei && "drei",
|
|
@@ -114,6 +103,71 @@ function formatConfigSummary(options) {
|
|
|
114
103
|
}
|
|
115
104
|
return lines.join("\n");
|
|
116
105
|
}
|
|
106
|
+
function formatMonorepoConfigSummary(options) {
|
|
107
|
+
const lines = [];
|
|
108
|
+
const VALUE_COL = 27;
|
|
109
|
+
const formatRow = (label, value, indent = "") => {
|
|
110
|
+
const fullLabel = indent + label;
|
|
111
|
+
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
112
|
+
const dots = color.gray(".".repeat(dotCount));
|
|
113
|
+
return `${indent}${label} ${dots} ${value}`;
|
|
114
|
+
};
|
|
115
|
+
lines.push(formatRow("Node version", options.nodeVersion || "latest"));
|
|
116
|
+
lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
|
|
117
|
+
if (options.packageManager === "pnpm") {
|
|
118
|
+
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
119
|
+
lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
|
|
120
|
+
}
|
|
121
|
+
lines.push(formatRow("Linter", options.linter));
|
|
122
|
+
lines.push(formatRow("Formatter", options.formatter));
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getDefaultOptions(template, name, projectType = "app", libraryBundler) {
|
|
127
|
+
const baseTemplate = getBaseTemplate(template);
|
|
128
|
+
const base = {
|
|
129
|
+
name,
|
|
130
|
+
template,
|
|
131
|
+
projectType,
|
|
132
|
+
libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
|
|
133
|
+
packageManager: "pnpm",
|
|
134
|
+
pnpmManageVersions: true,
|
|
135
|
+
nodeVersion: "latest",
|
|
136
|
+
linter: "oxlint",
|
|
137
|
+
formatter: "oxfmt",
|
|
138
|
+
// Libraries get vitest by default, apps don't
|
|
139
|
+
testing: projectType === "library" ? "vitest" : "none"
|
|
140
|
+
};
|
|
141
|
+
if (baseTemplate === "r3f") {
|
|
142
|
+
return {
|
|
143
|
+
...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: {}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return base;
|
|
159
|
+
}
|
|
160
|
+
function getDefaultProjectName(template) {
|
|
161
|
+
const base = getBaseTemplate(template);
|
|
162
|
+
switch (base) {
|
|
163
|
+
case "vanilla":
|
|
164
|
+
return `vanilla-${generateRandomName()}`;
|
|
165
|
+
case "react":
|
|
166
|
+
return `react-${generateRandomName()}`;
|
|
167
|
+
case "r3f":
|
|
168
|
+
return `react-three-${generateRandomName()}`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
117
171
|
async function promptForCustomization(template, name, projectType) {
|
|
118
172
|
let libraryBundler;
|
|
119
173
|
if (projectType === "library") {
|
|
@@ -212,6 +266,18 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
212
266
|
p.cancel("Operation cancelled.");
|
|
213
267
|
process.exit(0);
|
|
214
268
|
}
|
|
269
|
+
const testing = await p.select({
|
|
270
|
+
message: "Testing",
|
|
271
|
+
options: [
|
|
272
|
+
{ value: "vitest", label: "Vitest", hint: "fast, Vite-native" },
|
|
273
|
+
{ value: "none", label: "None" }
|
|
274
|
+
],
|
|
275
|
+
initialValue: projectType === "library" ? "vitest" : "none"
|
|
276
|
+
});
|
|
277
|
+
if (p.isCancel(testing)) {
|
|
278
|
+
p.cancel("Operation cancelled.");
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
215
281
|
const language = await p.select({
|
|
216
282
|
message: "Language",
|
|
217
283
|
options: [
|
|
@@ -276,6 +342,7 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
276
342
|
pnpmManageVersions,
|
|
277
343
|
linter,
|
|
278
344
|
formatter,
|
|
345
|
+
testing,
|
|
279
346
|
...baseTemplate === "r3f" && {
|
|
280
347
|
drei: integrations.includes("drei") ? {} : void 0,
|
|
281
348
|
handle: integrations.includes("handle") ? {} : void 0,
|
|
@@ -292,6 +359,140 @@ async function promptForCustomization(template, name, projectType) {
|
|
|
292
359
|
}
|
|
293
360
|
};
|
|
294
361
|
}
|
|
362
|
+
async function promptForInitialPackage() {
|
|
363
|
+
const choice = await p.select({
|
|
364
|
+
message: "Add an initial package?",
|
|
365
|
+
options: [
|
|
366
|
+
{ value: "app", label: "Application" },
|
|
367
|
+
{ value: "library", label: "Library" },
|
|
368
|
+
{ value: "skip", label: "Skip" }
|
|
369
|
+
],
|
|
370
|
+
initialValue: "app"
|
|
371
|
+
});
|
|
372
|
+
if (p.isCancel(choice)) {
|
|
373
|
+
p.cancel("Operation cancelled.");
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
return choice;
|
|
377
|
+
}
|
|
378
|
+
function getDefaultMonorepoOptions(name) {
|
|
379
|
+
return {
|
|
380
|
+
name,
|
|
381
|
+
projectType: "monorepo",
|
|
382
|
+
packageManager: "pnpm",
|
|
383
|
+
pnpmManageVersions: true,
|
|
384
|
+
nodeVersion: "latest",
|
|
385
|
+
linter: "oxlint",
|
|
386
|
+
formatter: "oxfmt"
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async function promptForMonorepoCustomization(name) {
|
|
390
|
+
const nodeVersion = await p.text({
|
|
391
|
+
message: "Node.js version",
|
|
392
|
+
placeholder: "latest",
|
|
393
|
+
defaultValue: "latest",
|
|
394
|
+
validate: (value) => {
|
|
395
|
+
if (!value.length) return "Required";
|
|
396
|
+
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
397
|
+
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
if (p.isCancel(nodeVersion)) {
|
|
402
|
+
p.cancel("Operation cancelled.");
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
const packageManager = await p.select({
|
|
406
|
+
message: "Package manager",
|
|
407
|
+
options: [
|
|
408
|
+
{ value: "pnpm", label: "pnpm" },
|
|
409
|
+
{ value: "npm", label: "npm" },
|
|
410
|
+
{ value: "yarn", label: "yarn" }
|
|
411
|
+
],
|
|
412
|
+
initialValue: "pnpm"
|
|
413
|
+
});
|
|
414
|
+
if (p.isCancel(packageManager)) {
|
|
415
|
+
p.cancel("Operation cancelled.");
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
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
|
+
const linter = await p.select({
|
|
431
|
+
message: "Linter",
|
|
432
|
+
options: [
|
|
433
|
+
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
434
|
+
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
435
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
436
|
+
],
|
|
437
|
+
initialValue: "oxlint"
|
|
438
|
+
});
|
|
439
|
+
if (p.isCancel(linter)) {
|
|
440
|
+
p.cancel("Operation cancelled.");
|
|
441
|
+
process.exit(0);
|
|
442
|
+
}
|
|
443
|
+
const formatter = await p.select({
|
|
444
|
+
message: "Formatter",
|
|
445
|
+
options: [
|
|
446
|
+
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
447
|
+
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
448
|
+
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
449
|
+
],
|
|
450
|
+
initialValue: "oxfmt"
|
|
451
|
+
});
|
|
452
|
+
if (p.isCancel(formatter)) {
|
|
453
|
+
p.cancel("Operation cancelled.");
|
|
454
|
+
process.exit(0);
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
name,
|
|
458
|
+
projectType: "monorepo",
|
|
459
|
+
nodeVersion,
|
|
460
|
+
packageManager,
|
|
461
|
+
pnpmManageVersions,
|
|
462
|
+
linter,
|
|
463
|
+
formatter
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async function promptForMonorepo(workspaceName) {
|
|
467
|
+
const defaultOptions = getDefaultMonorepoOptions(workspaceName);
|
|
468
|
+
p.note(
|
|
469
|
+
formatMonorepoConfigSummary({
|
|
470
|
+
name: defaultOptions.name,
|
|
471
|
+
nodeVersion: defaultOptions.nodeVersion ?? "latest",
|
|
472
|
+
packageManager: defaultOptions.packageManager ?? "pnpm",
|
|
473
|
+
pnpmManageVersions: defaultOptions.pnpmManageVersions,
|
|
474
|
+
linter: defaultOptions.linter ?? "oxlint",
|
|
475
|
+
formatter: defaultOptions.formatter ?? "oxfmt"
|
|
476
|
+
}),
|
|
477
|
+
"Workspace Configuration"
|
|
478
|
+
);
|
|
479
|
+
const action = await p.select({
|
|
480
|
+
message: "Proceed with these settings?",
|
|
481
|
+
options: [
|
|
482
|
+
{ value: "confirm", label: "Yes, create workspace" },
|
|
483
|
+
{ value: "customize", label: "No, let me customize" }
|
|
484
|
+
],
|
|
485
|
+
initialValue: "confirm"
|
|
486
|
+
});
|
|
487
|
+
if (p.isCancel(action)) {
|
|
488
|
+
p.cancel("Operation cancelled.");
|
|
489
|
+
process.exit(0);
|
|
490
|
+
}
|
|
491
|
+
if (action === "confirm") {
|
|
492
|
+
return defaultOptions;
|
|
493
|
+
}
|
|
494
|
+
return promptForMonorepoCustomization(workspaceName);
|
|
495
|
+
}
|
|
295
496
|
async function promptForOptions(name) {
|
|
296
497
|
let projectName = name;
|
|
297
498
|
if (!projectName) {
|
|
@@ -313,7 +514,8 @@ async function promptForOptions(name) {
|
|
|
313
514
|
message: "Project type",
|
|
314
515
|
options: [
|
|
315
516
|
{ value: "app", label: "Application" },
|
|
316
|
-
{ value: "library", label: "Library" }
|
|
517
|
+
{ value: "library", label: "Library" },
|
|
518
|
+
{ value: "monorepo", label: "Monorepo" }
|
|
317
519
|
],
|
|
318
520
|
initialValue: "app"
|
|
319
521
|
});
|
|
@@ -321,6 +523,12 @@ async function promptForOptions(name) {
|
|
|
321
523
|
p.cancel("Operation cancelled.");
|
|
322
524
|
process.exit(0);
|
|
323
525
|
}
|
|
526
|
+
if (projectType === "monorepo") {
|
|
527
|
+
return promptForMonorepo(projectName);
|
|
528
|
+
}
|
|
529
|
+
return promptForPackageOptions(projectName, projectType);
|
|
530
|
+
}
|
|
531
|
+
async function promptForPackageOptions(projectName, projectType) {
|
|
324
532
|
const template = await p.select({
|
|
325
533
|
message: "Select a template",
|
|
326
534
|
options: [
|
|
@@ -361,46 +569,53 @@ async function promptForOptions(name) {
|
|
|
361
569
|
projectType
|
|
362
570
|
);
|
|
363
571
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
572
|
+
|
|
573
|
+
const config = new Conf({
|
|
574
|
+
projectName: "create-krispya"
|
|
575
|
+
});
|
|
576
|
+
function getPreferredEditor() {
|
|
577
|
+
return config.get("preferredEditor");
|
|
578
|
+
}
|
|
579
|
+
function setPreferredEditor(editor) {
|
|
580
|
+
config.set("preferredEditor", editor);
|
|
581
|
+
}
|
|
582
|
+
function getReuseWindow() {
|
|
583
|
+
return config.get("reuseWindow") ?? false;
|
|
584
|
+
}
|
|
585
|
+
function setReuseWindow(reuse) {
|
|
586
|
+
config.set("reuseWindow", reuse);
|
|
587
|
+
}
|
|
588
|
+
function clearConfig() {
|
|
589
|
+
config.clear();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const require$1 = createRequire(import.meta.url);
|
|
593
|
+
const pkg = require$1("../package.json");
|
|
594
|
+
async function detectMonorepoRoot() {
|
|
595
|
+
let currentDir = cwd();
|
|
596
|
+
const root = resolve("/");
|
|
597
|
+
while (currentDir !== root) {
|
|
598
|
+
const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
|
|
599
|
+
try {
|
|
600
|
+
await access(workspaceFile, constants.F_OK);
|
|
601
|
+
const content = await readFile(workspaceFile, "utf-8");
|
|
602
|
+
if (content.includes("packages:")) {
|
|
603
|
+
return currentDir;
|
|
604
|
+
}
|
|
605
|
+
} catch {
|
|
606
|
+
}
|
|
607
|
+
currentDir = dirname(currentDir);
|
|
608
|
+
}
|
|
609
|
+
return null;
|
|
381
610
|
}
|
|
382
611
|
async function main() {
|
|
383
|
-
const program = new Command().name("create-krispya").description(
|
|
384
|
-
"CLI for creating Vanilla, React, and React Three Fiber projects"
|
|
385
|
-
).argument("[name]", "name for the project").option(
|
|
386
|
-
"--type <type>",
|
|
387
|
-
"project type: app or library (default: app)"
|
|
388
|
-
).option(
|
|
612
|
+
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(
|
|
389
613
|
"--bundler <bundler>",
|
|
390
614
|
"library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
|
|
391
615
|
).option(
|
|
392
616
|
"--template <type>",
|
|
393
617
|
"project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
|
|
394
|
-
).option(
|
|
395
|
-
"--linter <type>",
|
|
396
|
-
"linter: eslint, oxlint, or biome (default: oxlint)"
|
|
397
|
-
).option(
|
|
398
|
-
"--formatter <type>",
|
|
399
|
-
"formatter: prettier, oxfmt, or biome (default: oxfmt)"
|
|
400
|
-
).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(
|
|
401
|
-
"--package-manager <manager>",
|
|
402
|
-
"specify package manager (e.g. npm, yarn, pnpm)"
|
|
403
|
-
).option(
|
|
618
|
+
).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: oxfmt)").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(
|
|
404
619
|
"--pnpm-manage-versions",
|
|
405
620
|
"enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
|
|
406
621
|
).option(
|
|
@@ -409,9 +624,157 @@ async function main() {
|
|
|
409
624
|
).option(
|
|
410
625
|
"--node-version <version>",
|
|
411
626
|
'set Node.js version for engines.node field (default: "latest")'
|
|
412
|
-
).option("-y, --yes", "Skip prompts and use default values").action(async (name, options) => {
|
|
627
|
+
).option("-y, --yes", "Skip prompts and use default values").option("--clear-config", "Clear saved preferences (e.g. editor choice)").action(async (name, options) => {
|
|
628
|
+
if (options.clearConfig) {
|
|
629
|
+
clearConfig();
|
|
630
|
+
console.log("Configuration cleared.");
|
|
631
|
+
process.exit(0);
|
|
632
|
+
}
|
|
413
633
|
console.clear();
|
|
414
634
|
p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
|
|
635
|
+
const monorepoRoot = await detectMonorepoRoot();
|
|
636
|
+
if (monorepoRoot && Object.keys(options).length === 0) {
|
|
637
|
+
const choice = await p.select({
|
|
638
|
+
message: "Detected monorepo workspace",
|
|
639
|
+
options: [
|
|
640
|
+
{ value: "add", label: "Add new package to this workspace" },
|
|
641
|
+
{ value: "standalone", label: "Create standalone project" }
|
|
642
|
+
],
|
|
643
|
+
initialValue: "add"
|
|
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...");
|
|
743
|
+
try {
|
|
744
|
+
const files = generate(packageOptions);
|
|
745
|
+
const filePaths = Object.keys(files).sort();
|
|
746
|
+
for (const filePath of filePaths) {
|
|
747
|
+
const fullFilePath = join(basePath2, filePath);
|
|
748
|
+
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
749
|
+
const file = files[filePath];
|
|
750
|
+
if (file.type === "text") {
|
|
751
|
+
await writeFile(fullFilePath, file.content);
|
|
752
|
+
} else {
|
|
753
|
+
const response = await fetch(file.url);
|
|
754
|
+
await writeFile(fullFilePath, response.body);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
s2.stop("Package created!");
|
|
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);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
415
778
|
let generateOptions;
|
|
416
779
|
if (Object.keys(options).length > 0) {
|
|
417
780
|
const template = options.template ?? "vanilla";
|
|
@@ -446,6 +809,107 @@ async function main() {
|
|
|
446
809
|
} else {
|
|
447
810
|
generateOptions = await promptForOptions(name);
|
|
448
811
|
}
|
|
812
|
+
if (generateOptions.projectType === "monorepo") {
|
|
813
|
+
const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.m; });
|
|
814
|
+
const packageManager2 = generateOptions.packageManager || "pnpm";
|
|
815
|
+
if (packageManager2 === "pnpm") {
|
|
816
|
+
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
817
|
+
}
|
|
818
|
+
const nodeVersion2 = generateOptions.nodeVersion ?? "latest";
|
|
819
|
+
if (nodeVersion2 === "latest") {
|
|
820
|
+
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
821
|
+
}
|
|
822
|
+
const basePath2 = join(cwd(), generateOptions.name);
|
|
823
|
+
const s2 = p.spinner();
|
|
824
|
+
s2.start("Creating monorepo workspace...");
|
|
825
|
+
try {
|
|
826
|
+
const { files } = generateMonorepo({
|
|
827
|
+
name: generateOptions.name,
|
|
828
|
+
linter: generateOptions.linter ?? "oxlint",
|
|
829
|
+
formatter: generateOptions.formatter ?? "oxfmt",
|
|
830
|
+
packageManager: packageManager2,
|
|
831
|
+
pnpmVersion: generateOptions.pnpmVersion,
|
|
832
|
+
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
833
|
+
nodeVersion: generateOptions.nodeVersion
|
|
834
|
+
});
|
|
835
|
+
const filePaths = Object.keys(files).sort();
|
|
836
|
+
for (const filePath of filePaths) {
|
|
837
|
+
const fullFilePath = join(basePath2, filePath);
|
|
838
|
+
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
839
|
+
const file = files[filePath];
|
|
840
|
+
if (file.type === "text") {
|
|
841
|
+
await writeFile(fullFilePath, file.content);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
s2.stop("Monorepo workspace created!");
|
|
845
|
+
const initialPackage = await promptForInitialPackage();
|
|
846
|
+
if (initialPackage !== "skip") {
|
|
847
|
+
const packageName = await p.text({
|
|
848
|
+
message: "Package name?",
|
|
849
|
+
placeholder: initialPackage === "app" ? "my-app" : "my-package",
|
|
850
|
+
validate: (value) => {
|
|
851
|
+
if (!value.length) return "Package name is required";
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
if (!p.isCancel(packageName)) {
|
|
855
|
+
const targetDir = initialPackage === "app" ? "apps" : "packages";
|
|
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!");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
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
|
+
}
|
|
912
|
+
}
|
|
449
913
|
const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
|
|
450
914
|
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
451
915
|
generateOptions.name ??= defaultFallbackName;
|
|
@@ -458,12 +922,17 @@ async function main() {
|
|
|
458
922
|
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
459
923
|
}
|
|
460
924
|
const versions = {};
|
|
461
|
-
const versionPromises = [
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
925
|
+
const versionPromises = [];
|
|
926
|
+
const isLibrary = generateOptions.projectType === "library";
|
|
927
|
+
const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
|
|
928
|
+
if (testing === "vitest") {
|
|
929
|
+
versionPromises.push(
|
|
930
|
+
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
931
|
+
versions.vitest = v;
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
if (!isLibrary) {
|
|
467
936
|
versionPromises.push(
|
|
468
937
|
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
469
938
|
versions.vite = v;
|
|
@@ -530,8 +999,8 @@ async function main() {
|
|
|
530
999
|
}
|
|
531
1000
|
}
|
|
532
1001
|
s.stop("Project created!");
|
|
533
|
-
const
|
|
534
|
-
const nextSteps =
|
|
1002
|
+
const isLibrary2 = generateOptions.projectType === "library";
|
|
1003
|
+
const nextSteps = isLibrary2 ? [
|
|
535
1004
|
`cd ${generateOptions.name}`,
|
|
536
1005
|
`${packageManager} install`,
|
|
537
1006
|
`${packageManager} run build`
|
|
@@ -541,33 +1010,62 @@ async function main() {
|
|
|
541
1010
|
`${packageManager} run dev`
|
|
542
1011
|
].join("\n");
|
|
543
1012
|
p.note(nextSteps, "Next steps");
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1013
|
+
const savedEditor = getPreferredEditor();
|
|
1014
|
+
let selectedEditor;
|
|
1015
|
+
if (savedEditor && savedEditor !== "skip") {
|
|
1016
|
+
const useDefault = await p.confirm({
|
|
1017
|
+
message: `Open in editor? ${color.dim(`(${editorNames[savedEditor]})`)}`,
|
|
1018
|
+
initialValue: true
|
|
1019
|
+
});
|
|
1020
|
+
if (p.isCancel(useDefault)) {
|
|
1021
|
+
selectedEditor = void 0;
|
|
1022
|
+
} else if (useDefault) {
|
|
1023
|
+
selectedEditor = savedEditor;
|
|
1024
|
+
} else {
|
|
1025
|
+
selectedEditor = "skip";
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
const openEditor = await p.select({
|
|
1029
|
+
message: "Open project in editor?",
|
|
1030
|
+
options: [
|
|
1031
|
+
{ value: "skip", label: "Skip" },
|
|
1032
|
+
{ value: "cursor", label: "Cursor" },
|
|
1033
|
+
{ value: "code", label: "VS Code" },
|
|
1034
|
+
{ value: "webstorm", label: "WebStorm" }
|
|
1035
|
+
],
|
|
1036
|
+
initialValue: "skip"
|
|
1037
|
+
});
|
|
1038
|
+
if (!p.isCancel(openEditor)) {
|
|
1039
|
+
selectedEditor = openEditor;
|
|
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
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (selectedEditor && selectedEditor !== "skip") {
|
|
560
1059
|
try {
|
|
561
1060
|
await openInEditor(
|
|
562
|
-
|
|
563
|
-
basePath
|
|
564
|
-
|
|
565
|
-
p.log.success(
|
|
566
|
-
`Opening in ${editorNames[openEditor]}...`
|
|
1061
|
+
selectedEditor,
|
|
1062
|
+
basePath,
|
|
1063
|
+
getReuseWindow()
|
|
567
1064
|
);
|
|
1065
|
+
p.log.success(`Opening in ${editorNames[selectedEditor]}...`);
|
|
568
1066
|
} catch {
|
|
569
1067
|
p.log.warn(
|
|
570
|
-
`Could not open ${editorNames[
|
|
1068
|
+
`Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
|
|
571
1069
|
);
|
|
572
1070
|
}
|
|
573
1071
|
}
|