create-krispya 0.4.8 → 0.5.1

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/dist/cli.cjs CHANGED
@@ -3,14 +3,15 @@
3
3
 
4
4
  const module$1 = require('module');
5
5
  const process$1 = require('process');
6
- const index = require('./index.cjs');
7
6
  const path = require('path');
8
7
  const promises = require('fs/promises');
8
+ const fs = require('fs');
9
9
  const commander = require('commander');
10
10
  const p = require('@clack/prompts');
11
11
  const color = require('chalk');
12
12
  const undici = require('undici');
13
13
  const child_process = require('child_process');
14
+ const index = require('./chunks/index.cjs');
14
15
  const Conf = require('conf');
15
16
 
16
17
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -32,70 +33,31 @@ const p__namespace = /*#__PURE__*/_interopNamespaceCompat(p);
32
33
  const color__default = /*#__PURE__*/_interopDefaultCompat(color);
33
34
  const Conf__default = /*#__PURE__*/_interopDefaultCompat(Conf);
34
35
 
35
- const config = new Conf__default({
36
- projectName: "create-krispya"
37
- });
38
- function getPreferredEditor() {
39
- return config.get("preferredEditor");
40
- }
41
- function setPreferredEditor(editor) {
42
- config.set("preferredEditor", editor);
43
- }
44
- function getReuseWindow() {
45
- return config.get("reuseWindow") ?? false;
46
- }
47
- function setReuseWindow(reuse) {
48
- config.set("reuseWindow", reuse);
49
- }
50
- function clearConfig() {
51
- config.clear();
36
+ const editorNames = {
37
+ cursor: "Cursor",
38
+ code: "VS Code",
39
+ webstorm: "WebStorm",
40
+ skip: "Skip"
41
+ };
42
+ function openInEditor(editor, path, reuseWindow) {
43
+ return new Promise((resolve, reject) => {
44
+ const isWindows = process.platform === "win32";
45
+ const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
46
+ const args = useReuseFlag ? ["-r", path] : [path];
47
+ const child = isWindows ? child_process.spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
48
+ detached: true,
49
+ stdio: "ignore",
50
+ shell: true
51
+ }) : child_process.spawn(editor, args, {
52
+ detached: true,
53
+ stdio: "ignore"
54
+ });
55
+ child.on("error", reject);
56
+ child.unref();
57
+ setTimeout(resolve, 100);
58
+ });
52
59
  }
53
60
 
54
- const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
55
- const pkg = require$1("../package.json");
56
- function getDefaultProjectName(template) {
57
- const base = index.getBaseTemplate(template);
58
- switch (base) {
59
- case "vanilla":
60
- return `vanilla-${index.generateRandomName()}`;
61
- case "react":
62
- return `react-${index.generateRandomName()}`;
63
- case "r3f":
64
- return `react-three-${index.generateRandomName()}`;
65
- }
66
- }
67
- function getDefaultOptions(template, name, projectType = "app", libraryBundler) {
68
- const baseTemplate = index.getBaseTemplate(template);
69
- const base = {
70
- name,
71
- template,
72
- projectType,
73
- libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
74
- packageManager: "pnpm",
75
- pnpmManageVersions: true,
76
- nodeVersion: "latest",
77
- linter: "oxlint",
78
- formatter: "oxfmt"
79
- };
80
- if (baseTemplate === "r3f") {
81
- return {
82
- ...base,
83
- drei: {},
84
- handle: {},
85
- leva: {},
86
- postprocessing: {},
87
- rapier: {},
88
- xr: {},
89
- uikit: {},
90
- offscreen: {},
91
- zustand: {},
92
- koota: {},
93
- triplex: {},
94
- viverse: {}
95
- };
96
- }
97
- return base;
98
- }
99
61
  function formatConfigSummary(options) {
100
62
  const lines = [];
101
63
  const VALUE_COL = 27;
@@ -109,6 +71,12 @@ function formatConfigSummary(options) {
109
71
  return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
110
72
  };
111
73
  const projectType = options.projectType ?? "app";
74
+ const baseTemplate = options.template ? index.getBaseTemplate(options.template) : "vanilla";
75
+ if (baseTemplate === "react") {
76
+ lines.push(formatRow("Framework", "React"));
77
+ } else if (baseTemplate === "r3f") {
78
+ lines.push(formatRow("Framework", "React Three Fiber"));
79
+ }
112
80
  const language = options.template ? index.getLanguageFromTemplate(options.template) : "typescript";
113
81
  lines.push(formatRow("Language", formatLanguage(language)));
114
82
  if (projectType === "library") {
@@ -128,7 +96,8 @@ function formatConfigSummary(options) {
128
96
  if (options.formatter) {
129
97
  lines.push(formatRow("Formatter", options.formatter));
130
98
  }
131
- lines.push(formatRow("Testing", "vitest"));
99
+ const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
100
+ lines.push(formatRow("Testing", testing));
132
101
  if (options.template && index.getBaseTemplate(options.template) === "r3f") {
133
102
  const integrationNames = [
134
103
  options.drei && "drei",
@@ -155,7 +124,123 @@ function formatConfigSummary(options) {
155
124
  }
156
125
  return lines.join("\n");
157
126
  }
158
- async function promptForCustomization(template, name, projectType) {
127
+ function formatMonorepoConfigSummary(options) {
128
+ const lines = [];
129
+ const VALUE_COL = 27;
130
+ const formatRow = (label, value, indent = "") => {
131
+ const fullLabel = indent + label;
132
+ const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
133
+ const dots = color__default.gray(".".repeat(dotCount));
134
+ return `${indent}${label} ${dots} ${value}`;
135
+ };
136
+ lines.push(formatRow("Node version", options.nodeVersion || "latest"));
137
+ lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
138
+ if (options.packageManager === "pnpm") {
139
+ const versionManaged = options.pnpmManageVersions ? "yes" : "no";
140
+ lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
141
+ }
142
+ lines.push(formatRow("Linter", options.linter));
143
+ lines.push(formatRow("Formatter", options.formatter));
144
+ return lines.join("\n");
145
+ }
146
+
147
+ const config = new Conf__default({
148
+ projectName: "create-krispya"
149
+ });
150
+ function getPreferredEditor() {
151
+ return config.get("preferredEditor");
152
+ }
153
+ function setPreferredEditor(editor) {
154
+ config.set("preferredEditor", editor);
155
+ }
156
+ function getReuseWindow() {
157
+ return config.get("reuseWindow") ?? false;
158
+ }
159
+ function setReuseWindow(reuse) {
160
+ config.set("reuseWindow", reuse);
161
+ }
162
+ function clearConfig() {
163
+ config.clear();
164
+ }
165
+ function getConfigPath() {
166
+ return config.path;
167
+ }
168
+ function getCustomTemplates() {
169
+ return config.get("customTemplates") ?? {};
170
+ }
171
+
172
+ function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedTooling) {
173
+ const baseTemplate = index.getBaseTemplate(template);
174
+ const base = {
175
+ name,
176
+ template,
177
+ projectType,
178
+ libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
179
+ packageManager: "pnpm",
180
+ pnpmManageVersions: true,
181
+ nodeVersion: "latest",
182
+ linter: inheritedTooling?.linter ?? "oxlint",
183
+ formatter: inheritedTooling?.formatter ?? "oxfmt",
184
+ // Libraries get vitest by default, apps don't
185
+ testing: projectType === "library" ? "vitest" : "none"
186
+ };
187
+ if (baseTemplate === "r3f" && integrations) {
188
+ return {
189
+ ...base,
190
+ drei: integrations.includes("drei") ? {} : void 0,
191
+ handle: integrations.includes("handle") ? {} : void 0,
192
+ leva: integrations.includes("leva") ? {} : void 0,
193
+ postprocessing: integrations.includes("postprocessing") ? {} : void 0,
194
+ rapier: integrations.includes("rapier") ? {} : void 0,
195
+ xr: integrations.includes("xr") ? {} : void 0,
196
+ uikit: integrations.includes("uikit") ? {} : void 0,
197
+ offscreen: integrations.includes("offscreen") ? {} : void 0,
198
+ zustand: integrations.includes("zustand") ? {} : void 0,
199
+ koota: integrations.includes("koota") ? {} : void 0,
200
+ triplex: integrations.includes("triplex") ? {} : void 0,
201
+ viverse: integrations.includes("viverse") ? {} : void 0
202
+ };
203
+ }
204
+ return base;
205
+ }
206
+ function getDefaultProjectName(template) {
207
+ const base = index.getBaseTemplate(template);
208
+ switch (base) {
209
+ case "vanilla":
210
+ return `vanilla-${index.generateRandomName()}`;
211
+ case "react":
212
+ return `react-${index.generateRandomName()}`;
213
+ case "r3f":
214
+ return `react-three-${index.generateRandomName()}`;
215
+ }
216
+ }
217
+ async function promptForR3fIntegrations() {
218
+ const selected = await p__namespace.multiselect({
219
+ message: "R3F integrations",
220
+ options: [
221
+ { value: "drei", label: "Drei" },
222
+ { value: "handle", label: "Handle" },
223
+ { value: "leva", label: "Leva" },
224
+ { value: "postprocessing", label: "Postprocessing" },
225
+ { value: "rapier", label: "Rapier" },
226
+ { value: "xr", label: "XR" },
227
+ { value: "uikit", label: "UIKit" },
228
+ { value: "offscreen", label: "Offscreen" },
229
+ { value: "zustand", label: "Zustand" },
230
+ { value: "koota", label: "Koota" },
231
+ { value: "triplex", label: "Triplex" },
232
+ { value: "viverse", label: "Viverse" }
233
+ ],
234
+ initialValues: ["drei"],
235
+ required: false
236
+ });
237
+ if (p__namespace.isCancel(selected)) {
238
+ p__namespace.cancel("Operation cancelled.");
239
+ process.exit(0);
240
+ }
241
+ return selected;
242
+ }
243
+ async function promptForCustomization(template, name, projectType, integrations, inheritedTooling) {
159
244
  let libraryBundler;
160
245
  if (projectType === "library") {
161
246
  const bundler = await p__namespace.select({
@@ -227,29 +312,49 @@ async function promptForCustomization(template, name, projectType) {
227
312
  }
228
313
  pnpmManageVersions = managePnpm;
229
314
  }
230
- const linter = await p__namespace.select({
231
- message: "Linter",
232
- options: [
233
- { value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
234
- { value: "eslint", label: "ESLint", hint: "classic" },
235
- { value: "biome", label: "Biome", hint: "all-in-one" }
236
- ],
237
- initialValue: "oxlint"
238
- });
239
- if (p__namespace.isCancel(linter)) {
240
- p__namespace.cancel("Operation cancelled.");
241
- process.exit(0);
315
+ let linter = inheritedTooling?.linter ?? "oxlint";
316
+ let formatter = inheritedTooling?.formatter ?? "oxfmt";
317
+ if (!inheritedTooling?.linter) {
318
+ const linterChoice = await p__namespace.select({
319
+ message: "Linter",
320
+ options: [
321
+ { value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
322
+ { value: "eslint", label: "ESLint", hint: "classic" },
323
+ { value: "biome", label: "Biome", hint: "all-in-one" }
324
+ ],
325
+ initialValue: "oxlint"
326
+ });
327
+ if (p__namespace.isCancel(linterChoice)) {
328
+ p__namespace.cancel("Operation cancelled.");
329
+ process.exit(0);
330
+ }
331
+ linter = linterChoice;
242
332
  }
243
- const formatter = await p__namespace.select({
244
- message: "Formatter",
333
+ if (!inheritedTooling?.formatter) {
334
+ const formatterChoice = await p__namespace.select({
335
+ message: "Formatter",
336
+ options: [
337
+ { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
338
+ { value: "prettier", label: "Prettier", hint: "classic" },
339
+ { value: "biome", label: "Biome", hint: "all-in-one" }
340
+ ],
341
+ initialValue: "oxfmt"
342
+ });
343
+ if (p__namespace.isCancel(formatterChoice)) {
344
+ p__namespace.cancel("Operation cancelled.");
345
+ process.exit(0);
346
+ }
347
+ formatter = formatterChoice;
348
+ }
349
+ const testing = await p__namespace.select({
350
+ message: "Testing",
245
351
  options: [
246
- { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
247
- { value: "prettier", label: "Prettier", hint: "classic" },
248
- { value: "biome", label: "Biome", hint: "all-in-one" }
352
+ { value: "vitest", label: "Vitest", hint: "fast, Vite-native" },
353
+ { value: "none", label: "None" }
249
354
  ],
250
- initialValue: "oxfmt"
355
+ initialValue: projectType === "library" ? "vitest" : "none"
251
356
  });
252
- if (p__namespace.isCancel(formatter)) {
357
+ if (p__namespace.isCancel(testing)) {
253
358
  p__namespace.cancel("Operation cancelled.");
254
359
  process.exit(0);
255
360
  }
@@ -267,47 +372,7 @@ async function promptForCustomization(template, name, projectType) {
267
372
  }
268
373
  const baseTemplate = index.getBaseTemplate(template);
269
374
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
270
- let integrations = [];
271
- if (baseTemplate === "r3f") {
272
- const selected = await p__namespace.multiselect({
273
- message: "R3F integrations",
274
- options: [
275
- { value: "drei", label: "Drei" },
276
- { value: "handle", label: "Handle" },
277
- { value: "leva", label: "Leva" },
278
- { value: "postprocessing", label: "Postprocessing" },
279
- { value: "rapier", label: "Rapier" },
280
- { value: "xr", label: "XR" },
281
- { value: "uikit", label: "UIKit" },
282
- { value: "offscreen", label: "Offscreen" },
283
- { value: "zustand", label: "Zustand" },
284
- { value: "koota", label: "Koota" },
285
- { value: "triplex", label: "Triplex" },
286
- { value: "viverse", label: "Viverse" }
287
- ],
288
- initialValues: [
289
- "drei",
290
- "handle",
291
- "leva",
292
- "postprocessing",
293
- "rapier",
294
- "xr",
295
- "uikit",
296
- "offscreen",
297
- "zustand",
298
- "koota",
299
- "triplex",
300
- "viverse"
301
- ],
302
- required: false
303
- });
304
- if (p__namespace.isCancel(selected)) {
305
- p__namespace.cancel("Operation cancelled.");
306
- process.exit(0);
307
- }
308
- integrations = selected;
309
- }
310
- return {
375
+ const base = {
311
376
  name,
312
377
  template: finalTemplate,
313
378
  projectType,
@@ -317,7 +382,11 @@ async function promptForCustomization(template, name, projectType) {
317
382
  pnpmManageVersions,
318
383
  linter,
319
384
  formatter,
320
- ...baseTemplate === "r3f" && {
385
+ testing
386
+ };
387
+ if (baseTemplate === "r3f" && integrations) {
388
+ return {
389
+ ...base,
321
390
  drei: integrations.includes("drei") ? {} : void 0,
322
391
  handle: integrations.includes("handle") ? {} : void 0,
323
392
  leva: integrations.includes("leva") ? {} : void 0,
@@ -330,9 +399,140 @@ async function promptForCustomization(template, name, projectType) {
330
399
  koota: integrations.includes("koota") ? {} : void 0,
331
400
  triplex: integrations.includes("triplex") ? {} : void 0,
332
401
  viverse: integrations.includes("viverse") ? {} : void 0
402
+ };
403
+ }
404
+ return base;
405
+ }
406
+ async function promptForInitialPackage() {
407
+ const choice = await p__namespace.select({
408
+ message: "Add an initial package?",
409
+ options: [
410
+ { value: "app", label: "Application" },
411
+ { value: "library", label: "Library" },
412
+ { value: "skip", label: "Skip" }
413
+ ],
414
+ initialValue: "app"
415
+ });
416
+ if (p__namespace.isCancel(choice)) {
417
+ p__namespace.cancel("Operation cancelled.");
418
+ process.exit(0);
419
+ }
420
+ return choice;
421
+ }
422
+ function getDefaultMonorepoOptions(name) {
423
+ return {
424
+ name,
425
+ projectType: "monorepo",
426
+ packageManager: "pnpm",
427
+ pnpmManageVersions: true,
428
+ nodeVersion: "latest",
429
+ linter: "oxlint",
430
+ formatter: "oxfmt"
431
+ };
432
+ }
433
+ async function promptForMonorepoCustomization(name) {
434
+ const nodeVersion = await p__namespace.text({
435
+ message: "Node.js version",
436
+ placeholder: "latest",
437
+ defaultValue: "latest",
438
+ validate: (value) => {
439
+ if (!value.length) return "Required";
440
+ if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
441
+ return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
442
+ }
443
+ }
444
+ });
445
+ if (p__namespace.isCancel(nodeVersion)) {
446
+ p__namespace.cancel("Operation cancelled.");
447
+ process.exit(0);
448
+ }
449
+ const packageManager = await p__namespace.select({
450
+ message: "Package manager",
451
+ options: [
452
+ { value: "pnpm", label: "pnpm" },
453
+ { value: "npm", label: "npm" },
454
+ { value: "yarn", label: "yarn" }
455
+ ],
456
+ initialValue: "pnpm"
457
+ });
458
+ if (p__namespace.isCancel(packageManager)) {
459
+ p__namespace.cancel("Operation cancelled.");
460
+ process.exit(0);
461
+ }
462
+ let pnpmManageVersions = true;
463
+ if (packageManager === "pnpm") {
464
+ const managePnpm = await p__namespace.confirm({
465
+ message: "Enable manage-package-manager-versions?",
466
+ initialValue: true
467
+ });
468
+ if (p__namespace.isCancel(managePnpm)) {
469
+ p__namespace.cancel("Operation cancelled.");
470
+ process.exit(0);
333
471
  }
472
+ pnpmManageVersions = managePnpm;
473
+ }
474
+ const linter = await p__namespace.select({
475
+ message: "Linter",
476
+ options: [
477
+ { value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
478
+ { value: "eslint", label: "ESLint", hint: "classic" },
479
+ { value: "biome", label: "Biome", hint: "all-in-one" }
480
+ ],
481
+ initialValue: "oxlint"
482
+ });
483
+ if (p__namespace.isCancel(linter)) {
484
+ p__namespace.cancel("Operation cancelled.");
485
+ process.exit(0);
486
+ }
487
+ const formatter = await p__namespace.select({
488
+ message: "Formatter",
489
+ options: [
490
+ { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
491
+ { value: "prettier", label: "Prettier", hint: "classic" },
492
+ { value: "biome", label: "Biome", hint: "all-in-one" }
493
+ ],
494
+ initialValue: "oxfmt"
495
+ });
496
+ if (p__namespace.isCancel(formatter)) {
497
+ p__namespace.cancel("Operation cancelled.");
498
+ process.exit(0);
499
+ }
500
+ return {
501
+ name,
502
+ projectType: "monorepo",
503
+ nodeVersion,
504
+ packageManager,
505
+ pnpmManageVersions,
506
+ linter,
507
+ formatter
334
508
  };
335
509
  }
510
+ async function promptForMonorepo(workspaceName) {
511
+ const defaultOptions = getDefaultMonorepoOptions(workspaceName);
512
+ p__namespace.note(
513
+ formatMonorepoConfigSummary({
514
+ name: defaultOptions.name,
515
+ nodeVersion: defaultOptions.nodeVersion ?? "latest",
516
+ packageManager: defaultOptions.packageManager ?? "pnpm",
517
+ pnpmManageVersions: defaultOptions.pnpmManageVersions,
518
+ linter: defaultOptions.linter ?? "oxlint",
519
+ formatter: defaultOptions.formatter ?? "oxfmt"
520
+ }),
521
+ "Workspace Configuration"
522
+ );
523
+ const proceed = await p__namespace.confirm({
524
+ message: "Proceed with these settings?",
525
+ initialValue: true
526
+ });
527
+ if (p__namespace.isCancel(proceed)) {
528
+ p__namespace.cancel("Operation cancelled.");
529
+ process.exit(0);
530
+ }
531
+ if (proceed) {
532
+ return defaultOptions;
533
+ }
534
+ return promptForMonorepoCustomization(workspaceName);
535
+ }
336
536
  async function promptForOptions(name) {
337
537
  let projectName = name;
338
538
  if (!projectName) {
@@ -354,7 +554,8 @@ async function promptForOptions(name) {
354
554
  message: "Project type",
355
555
  options: [
356
556
  { value: "app", label: "Application" },
357
- { value: "library", label: "Library" }
557
+ { value: "library", label: "Library" },
558
+ { value: "monorepo", label: "Monorepo" }
358
559
  ],
359
560
  initialValue: "app"
360
561
  });
@@ -362,91 +563,408 @@ async function promptForOptions(name) {
362
563
  p__namespace.cancel("Operation cancelled.");
363
564
  process.exit(0);
364
565
  }
365
- const template = await p__namespace.select({
566
+ if (projectType === "monorepo") {
567
+ return promptForMonorepo(projectName);
568
+ }
569
+ return promptForPackageOptions(projectName, projectType);
570
+ }
571
+ function customTemplateToOptions(customTemplate, name, projectType) {
572
+ const baseTemplate = customTemplate.baseTemplate;
573
+ const template = baseTemplate;
574
+ const base = {
575
+ name,
576
+ template,
577
+ projectType,
578
+ packageManager: "pnpm",
579
+ pnpmManageVersions: true,
580
+ nodeVersion: "latest",
581
+ linter: customTemplate.linter,
582
+ formatter: customTemplate.formatter,
583
+ testing: customTemplate.testing
584
+ };
585
+ if (baseTemplate === "r3f" && customTemplate.integrations) {
586
+ const integrations = customTemplate.integrations;
587
+ return {
588
+ ...base,
589
+ drei: integrations.includes("drei") ? {} : void 0,
590
+ handle: integrations.includes("handle") ? {} : void 0,
591
+ leva: integrations.includes("leva") ? {} : void 0,
592
+ postprocessing: integrations.includes("postprocessing") ? {} : void 0,
593
+ rapier: integrations.includes("rapier") ? {} : void 0,
594
+ xr: integrations.includes("xr") ? {} : void 0,
595
+ uikit: integrations.includes("uikit") ? {} : void 0,
596
+ offscreen: integrations.includes("offscreen") ? {} : void 0,
597
+ zustand: integrations.includes("zustand") ? {} : void 0,
598
+ koota: integrations.includes("koota") ? {} : void 0,
599
+ triplex: integrations.includes("triplex") ? {} : void 0,
600
+ viverse: integrations.includes("viverse") ? {} : void 0
601
+ };
602
+ }
603
+ return base;
604
+ }
605
+ async function promptForPackageOptions(projectName, projectType, inheritedTooling) {
606
+ const builtInOptions = [
607
+ { value: "vanilla", label: "Vanilla" },
608
+ { value: "react", label: "React" },
609
+ { value: "r3f", label: "React Three Fiber" }
610
+ ];
611
+ const customTemplates = getCustomTemplates();
612
+ const customOptions = Object.keys(customTemplates).map((name) => ({
613
+ value: `custom:${name}`,
614
+ label: name,
615
+ hint: "saved template"
616
+ }));
617
+ const allOptions = [...builtInOptions, ...customOptions];
618
+ const templateSelection = await p__namespace.select({
366
619
  message: "Select a template",
367
- options: [
368
- { value: "vanilla", label: "Vanilla" },
369
- { value: "react", label: "React" },
370
- { value: "r3f", label: "React Three Fiber" }
371
- ],
620
+ options: allOptions,
372
621
  initialValue: "vanilla"
373
622
  });
374
- if (p__namespace.isCancel(template)) {
623
+ if (p__namespace.isCancel(templateSelection)) {
375
624
  p__namespace.cancel("Operation cancelled.");
376
625
  process.exit(0);
377
626
  }
627
+ const selection = templateSelection;
628
+ if (selection.startsWith("custom:")) {
629
+ const customName = selection.slice(7);
630
+ const customTemplate = customTemplates[customName];
631
+ const defaultOptions2 = customTemplateToOptions(customTemplate, projectName, projectType);
632
+ if (inheritedTooling?.linter) {
633
+ defaultOptions2.linter = inheritedTooling.linter;
634
+ }
635
+ if (inheritedTooling?.formatter) {
636
+ defaultOptions2.formatter = inheritedTooling.formatter;
637
+ }
638
+ const configTitle2 = inheritedTooling ? `Template: ${customName} (using workspace tooling)` : `Template: ${customName}`;
639
+ p__namespace.note(formatConfigSummary(defaultOptions2), configTitle2);
640
+ const proceed2 = await p__namespace.confirm({
641
+ message: "Proceed with these settings?",
642
+ initialValue: true
643
+ });
644
+ if (p__namespace.isCancel(proceed2)) {
645
+ p__namespace.cancel("Operation cancelled.");
646
+ process.exit(0);
647
+ }
648
+ if (proceed2) {
649
+ return defaultOptions2;
650
+ }
651
+ return promptForCustomization(
652
+ customTemplate.baseTemplate,
653
+ projectName,
654
+ projectType,
655
+ customTemplate.integrations,
656
+ inheritedTooling
657
+ );
658
+ }
659
+ const template = selection;
660
+ const baseTemplate = index.getBaseTemplate(template);
661
+ let integrations;
662
+ if (baseTemplate === "r3f") {
663
+ integrations = await promptForR3fIntegrations();
664
+ }
378
665
  const defaultOptions = getDefaultOptions(
379
666
  template,
380
667
  projectName,
381
- projectType
668
+ projectType,
669
+ void 0,
670
+ integrations,
671
+ inheritedTooling
382
672
  );
383
- p__namespace.note(formatConfigSummary(defaultOptions), "Template Configuration");
384
- const action = await p__namespace.select({
673
+ const configTitle = inheritedTooling ? "Template Configuration (using workspace tooling)" : "Template Configuration";
674
+ p__namespace.note(formatConfigSummary(defaultOptions), configTitle);
675
+ const proceed = await p__namespace.confirm({
385
676
  message: "Proceed with these settings?",
386
- options: [
387
- { value: "confirm", label: "Yes, create project" },
388
- { value: "customize", label: "No, let me customize" }
389
- ],
390
- initialValue: "confirm"
677
+ initialValue: true
391
678
  });
392
- if (p__namespace.isCancel(action)) {
679
+ if (p__namespace.isCancel(proceed)) {
393
680
  p__namespace.cancel("Operation cancelled.");
394
681
  process.exit(0);
395
682
  }
396
- if (action === "confirm") {
683
+ if (proceed) {
397
684
  return defaultOptions;
398
685
  }
399
- return promptForCustomization(
400
- template,
401
- projectName,
402
- projectType
686
+ return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
687
+ }
688
+
689
+ const require$1 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.cjs', document.baseURI).href)));
690
+ const pkg = require$1("../package.json");
691
+ async function detectMonorepoRoot() {
692
+ let currentDir = process$1.cwd();
693
+ const root = path.resolve("/");
694
+ while (currentDir !== root) {
695
+ const workspaceFile = path.join(currentDir, "pnpm-workspace.yaml");
696
+ try {
697
+ await promises.access(workspaceFile, fs.constants.F_OK);
698
+ const content = await promises.readFile(workspaceFile, "utf-8");
699
+ if (content.includes("packages:")) {
700
+ return currentDir;
701
+ }
702
+ } catch {
703
+ }
704
+ currentDir = path.dirname(currentDir);
705
+ }
706
+ return null;
707
+ }
708
+ async function detectWorkspaceTooling(monorepoRoot) {
709
+ try {
710
+ const pkgPath = path.join(monorepoRoot, "package.json");
711
+ const content = await promises.readFile(pkgPath, "utf-8");
712
+ const pkg2 = JSON.parse(content);
713
+ const devDeps = pkg2.devDependencies ?? {};
714
+ const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
715
+ const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
716
+ return { linter, formatter };
717
+ } catch {
718
+ return {};
719
+ }
720
+ }
721
+ async function getMonorepoScope(monorepoRoot) {
722
+ try {
723
+ const pkgPath = path.join(monorepoRoot, "package.json");
724
+ const content = await promises.readFile(pkgPath, "utf-8");
725
+ const pkg2 = JSON.parse(content);
726
+ if (pkg2.name) {
727
+ return pkg2.name.replace(/^@/, "").replace(/\/.*$/, "");
728
+ }
729
+ } catch {
730
+ }
731
+ return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
732
+ }
733
+ async function getWorkspacePackages(monorepoRoot) {
734
+ const packagesDir = path.join(monorepoRoot, "packages");
735
+ const packages = [];
736
+ try {
737
+ const { readdir } = await import('fs/promises');
738
+ const entries = await readdir(packagesDir, { withFileTypes: true });
739
+ for (const entry of entries) {
740
+ if (entry.isDirectory()) {
741
+ try {
742
+ const pkgJsonPath = path.join(packagesDir, entry.name, "package.json");
743
+ const content = await promises.readFile(pkgJsonPath, "utf-8");
744
+ const pkg2 = JSON.parse(content);
745
+ if (pkg2.name) {
746
+ packages.push({ name: pkg2.name, path: `packages/${entry.name}` });
747
+ }
748
+ } catch {
749
+ }
750
+ }
751
+ }
752
+ } catch {
753
+ }
754
+ return packages;
755
+ }
756
+ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
757
+ const packageType = await promptForInitialPackage();
758
+ if (packageType === "skip") {
759
+ return false;
760
+ }
761
+ const packageNameInput = await p__namespace.text({
762
+ message: "Package name?",
763
+ placeholder: `Scoped to @${scope}/`,
764
+ validate: (value) => {
765
+ if (!value.length) return "Package name is required";
766
+ }
767
+ });
768
+ if (p__namespace.isCancel(packageNameInput)) {
769
+ return false;
770
+ }
771
+ const shortName = packageNameInput;
772
+ const scopedName = `@${scope}/${shortName}`;
773
+ const targetDir = packageType === "app" ? "apps" : "packages";
774
+ const packagePath = path.join(targetDir, shortName);
775
+ const workspaceRoot = "../..";
776
+ const packageOptions = await promptForPackageOptions(
777
+ scopedName,
778
+ packageType,
779
+ inheritedTooling
403
780
  );
781
+ packageOptions.workspaceRoot = workspaceRoot;
782
+ packageOptions.name = scopedName;
783
+ if (packageManager === "pnpm") {
784
+ packageOptions.pnpmVersion = await index.getLatestPnpmVersion();
785
+ }
786
+ const nodeVersion = packageOptions.nodeVersion ?? "latest";
787
+ if (nodeVersion === "latest") {
788
+ packageOptions.nodeVersion = await index.getLatestNodeVersion();
789
+ }
790
+ const versions = {};
791
+ const versionPromises = [];
792
+ const pkgIsLibrary = packageOptions.projectType === "library";
793
+ const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
794
+ if (pkgTesting === "vitest") {
795
+ versionPromises.push(
796
+ index.getLatestNpmVersion("vitest", "4.0.0").then((v) => {
797
+ versions.vitest = v;
798
+ })
799
+ );
800
+ }
801
+ if (!pkgIsLibrary) {
802
+ versionPromises.push(
803
+ index.getLatestNpmVersion("vite", "6.3.4").then((v) => {
804
+ versions.vite = v;
805
+ })
806
+ );
807
+ }
808
+ const linter = packageOptions.linter ?? "oxlint";
809
+ if (linter === "eslint") {
810
+ versionPromises.push(
811
+ index.getLatestNpmVersion("eslint", "9.17.0").then((v) => {
812
+ versions.eslint = v;
813
+ })
814
+ );
815
+ } else if (linter === "oxlint") {
816
+ versionPromises.push(
817
+ index.getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
818
+ versions.oxlint = v;
819
+ })
820
+ );
821
+ } else if (linter === "biome") {
822
+ versionPromises.push(
823
+ index.getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
824
+ versions.biome = v;
825
+ })
826
+ );
827
+ }
828
+ const formatter = packageOptions.formatter ?? "oxfmt";
829
+ if (formatter === "prettier") {
830
+ versionPromises.push(
831
+ index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
832
+ versions.prettier = v;
833
+ })
834
+ );
835
+ } else if (formatter === "oxfmt") {
836
+ versionPromises.push(
837
+ index.getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
838
+ versions.oxfmt = v;
839
+ })
840
+ );
841
+ } else if (formatter === "biome" && linter !== "biome") {
842
+ versionPromises.push(
843
+ index.getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
844
+ versions.biome = v;
845
+ })
846
+ );
847
+ }
848
+ await Promise.all(versionPromises);
849
+ packageOptions.versions = versions;
850
+ if (packageType === "app") {
851
+ const workspacePackages = await getWorkspacePackages(monorepoRoot);
852
+ if (workspacePackages.length > 0) {
853
+ const selectedDeps = await p__namespace.multiselect({
854
+ message: "Add workspace dependencies?",
855
+ options: workspacePackages.map((pkg2) => ({
856
+ value: pkg2.name,
857
+ label: pkg2.name.replace(/^@[^/]+\//, "")
858
+ })),
859
+ required: false
860
+ });
861
+ if (!p__namespace.isCancel(selectedDeps) && selectedDeps.length > 0) {
862
+ packageOptions.workspaceDependencies = selectedDeps;
863
+ }
864
+ }
865
+ }
866
+ const basePath = path.join(monorepoRoot, packagePath);
867
+ const s = p__namespace.spinner();
868
+ s.start("Creating package...");
869
+ try {
870
+ const files = index.generate(packageOptions);
871
+ const filePaths = Object.keys(files).sort();
872
+ for (const filePath of filePaths) {
873
+ const fullFilePath = path.join(basePath, filePath);
874
+ await promises.mkdir(path.dirname(fullFilePath), { recursive: true });
875
+ const file = files[filePath];
876
+ if (file.type === "text") {
877
+ await promises.writeFile(fullFilePath, file.content);
878
+ } else {
879
+ const response = await undici.fetch(file.url);
880
+ await promises.writeFile(fullFilePath, response.body);
881
+ }
882
+ }
883
+ s.stop(color__default.green.inverse(` \u2713 Package created at ${packagePath}! `));
884
+ const addAnother = await p__namespace.select({
885
+ message: "Add another package?",
886
+ options: [
887
+ { value: "no", label: "No, I'm done" },
888
+ { value: "yes", label: "Yes, add another" }
889
+ ],
890
+ initialValue: "no"
891
+ });
892
+ return !p__namespace.isCancel(addAnother) && addAnother === "yes";
893
+ } catch (error) {
894
+ s.stop("Failed to create package");
895
+ p__namespace.log.error(String(error));
896
+ return false;
897
+ }
404
898
  }
405
- const editorNames = {
406
- cursor: "Cursor",
407
- code: "VS Code",
408
- webstorm: "WebStorm"
409
- };
410
- function openInEditor(editor, path, reuseWindow) {
411
- return new Promise((resolve, reject) => {
412
- const isWindows = process.platform === "win32";
413
- const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
414
- const args = useReuseFlag ? ["-r", path] : [path];
415
- const child = isWindows ? child_process.spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
416
- detached: true,
417
- stdio: "ignore",
418
- shell: true
419
- }) : child_process.spawn(editor, args, {
420
- detached: true,
421
- stdio: "ignore"
899
+ async function promptAndOpenEditor(basePath) {
900
+ const savedEditor = getPreferredEditor();
901
+ let selectedEditor;
902
+ if (savedEditor && savedEditor !== "skip") {
903
+ const useDefault = await p__namespace.confirm({
904
+ message: `Open in editor? ${color__default.dim(`(${editorNames[savedEditor]})`)}`,
905
+ initialValue: true
422
906
  });
423
- child.on("error", reject);
424
- child.unref();
425
- setTimeout(resolve, 100);
426
- });
907
+ if (p__namespace.isCancel(useDefault)) {
908
+ selectedEditor = void 0;
909
+ } else if (useDefault) {
910
+ selectedEditor = savedEditor;
911
+ } else {
912
+ selectedEditor = "skip";
913
+ }
914
+ } else {
915
+ const openEditor = await p__namespace.select({
916
+ message: "Open project in editor?",
917
+ options: [
918
+ { value: "skip", label: "Skip" },
919
+ { value: "cursor", label: "Cursor" },
920
+ { value: "code", label: "VS Code" },
921
+ { value: "webstorm", label: "WebStorm" }
922
+ ],
923
+ initialValue: "skip"
924
+ });
925
+ if (!p__namespace.isCancel(openEditor)) {
926
+ selectedEditor = openEditor;
927
+ const saveChoice = await p__namespace.confirm({
928
+ message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
929
+ initialValue: true
930
+ });
931
+ if (!p__namespace.isCancel(saveChoice) && saveChoice) {
932
+ setPreferredEditor(selectedEditor);
933
+ if (selectedEditor === "cursor" || selectedEditor === "code") {
934
+ const reuseChoice = await p__namespace.confirm({
935
+ message: "Reuse current window when opening projects?",
936
+ initialValue: false
937
+ });
938
+ if (!p__namespace.isCancel(reuseChoice)) {
939
+ setReuseWindow(reuseChoice);
940
+ }
941
+ }
942
+ }
943
+ }
944
+ }
945
+ if (selectedEditor && selectedEditor !== "skip") {
946
+ try {
947
+ await openInEditor(
948
+ selectedEditor,
949
+ basePath,
950
+ getReuseWindow()
951
+ );
952
+ p__namespace.log.success(`Opening in ${editorNames[selectedEditor]}...`);
953
+ } catch {
954
+ p__namespace.log.warn(
955
+ `Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
956
+ );
957
+ }
958
+ }
427
959
  }
428
960
  async function main() {
429
- const program = new commander.Command().name("create-krispya").description(
430
- "CLI for creating Vanilla, React, and React Three Fiber projects"
431
- ).argument("[name]", "name for the project").option(
432
- "--type <type>",
433
- "project type: app or library (default: app)"
434
- ).option(
961
+ const program = new commander.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(
435
962
  "--bundler <bundler>",
436
963
  "library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
437
964
  ).option(
438
965
  "--template <type>",
439
966
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
440
- ).option(
441
- "--linter <type>",
442
- "linter: eslint, oxlint, or biome (default: oxlint)"
443
- ).option(
444
- "--formatter <type>",
445
- "formatter: prettier, oxfmt, or biome (default: oxfmt)"
446
- ).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(
447
- "--package-manager <manager>",
448
- "specify package manager (e.g. npm, yarn, pnpm)"
449
- ).option(
967
+ ).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(
450
968
  "--pnpm-manage-versions",
451
969
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
452
970
  ).option(
@@ -455,14 +973,55 @@ async function main() {
455
973
  ).option(
456
974
  "--node-version <version>",
457
975
  'set Node.js version for engines.node field (default: "latest")'
458
- ).option("-y, --yes", "Skip prompts and use default values").option("--clear-config", "Clear saved preferences (e.g. editor choice)").action(async (name, options) => {
976
+ ).option("-y, --yes", "Skip prompts and use default values").option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").action(async (name, options) => {
459
977
  if (options.clearConfig) {
460
978
  clearConfig();
461
979
  console.log("Configuration cleared.");
462
980
  process.exit(0);
463
981
  }
982
+ if (options.configPath) {
983
+ console.log(getConfigPath());
984
+ process.exit(0);
985
+ }
464
986
  console.clear();
465
987
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
988
+ const monorepoRoot = await detectMonorepoRoot();
989
+ if (monorepoRoot && Object.keys(options).length === 0) {
990
+ const choice = await p__namespace.select({
991
+ message: "Detected monorepo workspace",
992
+ options: [
993
+ { value: "add", label: "Add new package to this workspace" },
994
+ { value: "standalone", label: "Create standalone project" }
995
+ ],
996
+ initialValue: "add"
997
+ });
998
+ if (p__namespace.isCancel(choice)) {
999
+ p__namespace.cancel("Operation cancelled.");
1000
+ process.exit(0);
1001
+ }
1002
+ if (choice === "add") {
1003
+ const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1004
+ if (inheritedTooling.linter || inheritedTooling.formatter) {
1005
+ const toolingInfo = [
1006
+ inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
1007
+ inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
1008
+ ].filter(Boolean).join(", ");
1009
+ p__namespace.log.info(`Using workspace tooling (${toolingInfo})`);
1010
+ }
1011
+ const scope = await getMonorepoScope(monorepoRoot);
1012
+ let addMore = true;
1013
+ while (addMore) {
1014
+ addMore = await createPackageInWorkspace(monorepoRoot, "pnpm", inheritedTooling, scope);
1015
+ }
1016
+ p__namespace.note(
1017
+ [`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"),
1018
+ "Next steps"
1019
+ );
1020
+ await promptAndOpenEditor(monorepoRoot);
1021
+ p__namespace.outro(color__default.green("Happy coding! \u2728"));
1022
+ process.exit(0);
1023
+ }
1024
+ }
466
1025
  let generateOptions;
467
1026
  if (Object.keys(options).length > 0) {
468
1027
  const template = options.template ?? "vanilla";
@@ -497,6 +1056,63 @@ async function main() {
497
1056
  } else {
498
1057
  generateOptions = await promptForOptions(name);
499
1058
  }
1059
+ if (generateOptions.projectType === "monorepo") {
1060
+ const { generateMonorepo } = await import('./chunks/index.cjs').then(function (n) { return n.monorepo; });
1061
+ const packageManager2 = generateOptions.packageManager || "pnpm";
1062
+ if (packageManager2 === "pnpm") {
1063
+ generateOptions.pnpmVersion = await index.getLatestPnpmVersion();
1064
+ }
1065
+ const nodeVersion2 = generateOptions.nodeVersion ?? "latest";
1066
+ if (nodeVersion2 === "latest") {
1067
+ generateOptions.nodeVersion = await index.getLatestNodeVersion();
1068
+ }
1069
+ const basePath2 = path.join(process$1.cwd(), generateOptions.name);
1070
+ const s2 = p__namespace.spinner();
1071
+ s2.start("Creating monorepo workspace...");
1072
+ try {
1073
+ const { files } = generateMonorepo({
1074
+ name: generateOptions.name,
1075
+ linter: generateOptions.linter ?? "oxlint",
1076
+ formatter: generateOptions.formatter ?? "oxfmt",
1077
+ packageManager: packageManager2,
1078
+ pnpmVersion: generateOptions.pnpmVersion,
1079
+ pnpmManageVersions: generateOptions.pnpmManageVersions,
1080
+ nodeVersion: generateOptions.nodeVersion
1081
+ });
1082
+ const filePaths = Object.keys(files).sort();
1083
+ for (const filePath of filePaths) {
1084
+ const fullFilePath = path.join(basePath2, filePath);
1085
+ await promises.mkdir(path.dirname(fullFilePath), { recursive: true });
1086
+ const file = files[filePath];
1087
+ if (file.type === "text") {
1088
+ await promises.writeFile(fullFilePath, file.content);
1089
+ }
1090
+ }
1091
+ s2.stop(color__default.green.inverse(" \u2713 Monorepo workspace created! "));
1092
+ const newMonorepoTooling = {
1093
+ linter: generateOptions.linter,
1094
+ formatter: generateOptions.formatter
1095
+ };
1096
+ const scope = generateOptions.name;
1097
+ let addMore = true;
1098
+ while (addMore) {
1099
+ addMore = await createPackageInWorkspace(basePath2, packageManager2, newMonorepoTooling, scope);
1100
+ }
1101
+ const nextSteps = [
1102
+ `cd ${generateOptions.name}`,
1103
+ `${packageManager2} install`,
1104
+ `${packageManager2} run dev`
1105
+ ].join("\n");
1106
+ p__namespace.note(nextSteps, "Next steps");
1107
+ await promptAndOpenEditor(basePath2);
1108
+ p__namespace.outro(color__default.green("Happy coding! \u2728"));
1109
+ process.exit(0);
1110
+ } catch (error) {
1111
+ s2.stop("Failed to create monorepo workspace");
1112
+ p__namespace.log.error(String(error));
1113
+ process.exit(1);
1114
+ }
1115
+ }
500
1116
  const base = generateOptions.template ? index.getBaseTemplate(generateOptions.template) : "vanilla";
501
1117
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
502
1118
  generateOptions.name ??= defaultFallbackName;
@@ -509,12 +1125,17 @@ async function main() {
509
1125
  generateOptions.nodeVersion = await index.getLatestNodeVersion();
510
1126
  }
511
1127
  const versions = {};
512
- const versionPromises = [
513
- index.getLatestNpmVersion("vitest", "4.0.0").then((v) => {
514
- versions.vitest = v;
515
- })
516
- ];
517
- if (generateOptions.projectType !== "library") {
1128
+ const versionPromises = [];
1129
+ const isLibrary = generateOptions.projectType === "library";
1130
+ const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
1131
+ if (testing === "vitest") {
1132
+ versionPromises.push(
1133
+ index.getLatestNpmVersion("vitest", "4.0.0").then((v) => {
1134
+ versions.vitest = v;
1135
+ })
1136
+ );
1137
+ }
1138
+ if (!isLibrary) {
518
1139
  versionPromises.push(
519
1140
  index.getLatestNpmVersion("vite", "6.3.4").then((v) => {
520
1141
  versions.vite = v;
@@ -580,9 +1201,9 @@ async function main() {
580
1201
  await promises.writeFile(fullFilePath, response.body);
581
1202
  }
582
1203
  }
583
- s.stop("Project created!");
584
- const isLibrary = generateOptions.projectType === "library";
585
- const nextSteps = isLibrary ? [
1204
+ s.stop(color__default.green.inverse(" \u2713 Project created! "));
1205
+ const isLibrary2 = generateOptions.projectType === "library";
1206
+ const nextSteps = isLibrary2 ? [
586
1207
  `cd ${generateOptions.name}`,
587
1208
  `${packageManager} install`,
588
1209
  `${packageManager} run build`