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