create-krispya 0.5.0 → 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
@@ -144,7 +144,32 @@ function formatMonorepoConfigSummary(options) {
144
144
  return lines.join("\n");
145
145
  }
146
146
 
147
- function getDefaultOptions(template, name, projectType = "app", libraryBundler) {
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) {
148
173
  const baseTemplate = index.getBaseTemplate(template);
149
174
  const base = {
150
175
  name,
@@ -154,26 +179,26 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler)
154
179
  packageManager: "pnpm",
155
180
  pnpmManageVersions: true,
156
181
  nodeVersion: "latest",
157
- linter: "oxlint",
158
- formatter: "oxfmt",
182
+ linter: inheritedTooling?.linter ?? "oxlint",
183
+ formatter: inheritedTooling?.formatter ?? "oxfmt",
159
184
  // Libraries get vitest by default, apps don't
160
185
  testing: projectType === "library" ? "vitest" : "none"
161
186
  };
162
- if (baseTemplate === "r3f") {
187
+ if (baseTemplate === "r3f" && integrations) {
163
188
  return {
164
189
  ...base,
165
- drei: {},
166
- handle: {},
167
- leva: {},
168
- postprocessing: {},
169
- rapier: {},
170
- xr: {},
171
- uikit: {},
172
- offscreen: {},
173
- zustand: {},
174
- koota: {},
175
- triplex: {},
176
- viverse: {}
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
177
202
  };
178
203
  }
179
204
  return base;
@@ -189,7 +214,33 @@ function getDefaultProjectName(template) {
189
214
  return `react-three-${index.generateRandomName()}`;
190
215
  }
191
216
  }
192
- async function promptForCustomization(template, name, projectType) {
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) {
193
244
  let libraryBundler;
194
245
  if (projectType === "library") {
195
246
  const bundler = await p__namespace.select({
@@ -261,31 +312,39 @@ async function promptForCustomization(template, name, projectType) {
261
312
  }
262
313
  pnpmManageVersions = managePnpm;
263
314
  }
264
- const linter = await p__namespace.select({
265
- message: "Linter",
266
- options: [
267
- { value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
268
- { value: "eslint", label: "ESLint", hint: "classic" },
269
- { value: "biome", label: "Biome", hint: "all-in-one" }
270
- ],
271
- initialValue: "oxlint"
272
- });
273
- if (p__namespace.isCancel(linter)) {
274
- p__namespace.cancel("Operation cancelled.");
275
- 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;
276
332
  }
277
- const formatter = await p__namespace.select({
278
- message: "Formatter",
279
- options: [
280
- { value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
281
- { value: "prettier", label: "Prettier", hint: "classic" },
282
- { value: "biome", label: "Biome", hint: "all-in-one" }
283
- ],
284
- initialValue: "oxfmt"
285
- });
286
- if (p__namespace.isCancel(formatter)) {
287
- p__namespace.cancel("Operation cancelled.");
288
- process.exit(0);
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;
289
348
  }
290
349
  const testing = await p__namespace.select({
291
350
  message: "Testing",
@@ -313,47 +372,7 @@ async function promptForCustomization(template, name, projectType) {
313
372
  }
314
373
  const baseTemplate = index.getBaseTemplate(template);
315
374
  const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
316
- let integrations = [];
317
- if (baseTemplate === "r3f") {
318
- const selected = await p__namespace.multiselect({
319
- message: "R3F integrations",
320
- options: [
321
- { value: "drei", label: "Drei" },
322
- { value: "handle", label: "Handle" },
323
- { value: "leva", label: "Leva" },
324
- { value: "postprocessing", label: "Postprocessing" },
325
- { value: "rapier", label: "Rapier" },
326
- { value: "xr", label: "XR" },
327
- { value: "uikit", label: "UIKit" },
328
- { value: "offscreen", label: "Offscreen" },
329
- { value: "zustand", label: "Zustand" },
330
- { value: "koota", label: "Koota" },
331
- { value: "triplex", label: "Triplex" },
332
- { value: "viverse", label: "Viverse" }
333
- ],
334
- initialValues: [
335
- "drei",
336
- "handle",
337
- "leva",
338
- "postprocessing",
339
- "rapier",
340
- "xr",
341
- "uikit",
342
- "offscreen",
343
- "zustand",
344
- "koota",
345
- "triplex",
346
- "viverse"
347
- ],
348
- required: false
349
- });
350
- if (p__namespace.isCancel(selected)) {
351
- p__namespace.cancel("Operation cancelled.");
352
- process.exit(0);
353
- }
354
- integrations = selected;
355
- }
356
- return {
375
+ const base = {
357
376
  name,
358
377
  template: finalTemplate,
359
378
  projectType,
@@ -363,8 +382,11 @@ async function promptForCustomization(template, name, projectType) {
363
382
  pnpmManageVersions,
364
383
  linter,
365
384
  formatter,
366
- testing,
367
- ...baseTemplate === "r3f" && {
385
+ testing
386
+ };
387
+ if (baseTemplate === "r3f" && integrations) {
388
+ return {
389
+ ...base,
368
390
  drei: integrations.includes("drei") ? {} : void 0,
369
391
  handle: integrations.includes("handle") ? {} : void 0,
370
392
  leva: integrations.includes("leva") ? {} : void 0,
@@ -377,8 +399,9 @@ async function promptForCustomization(template, name, projectType) {
377
399
  koota: integrations.includes("koota") ? {} : void 0,
378
400
  triplex: integrations.includes("triplex") ? {} : void 0,
379
401
  viverse: integrations.includes("viverse") ? {} : void 0
380
- }
381
- };
402
+ };
403
+ }
404
+ return base;
382
405
  }
383
406
  async function promptForInitialPackage() {
384
407
  const choice = await p__namespace.select({
@@ -497,19 +520,15 @@ async function promptForMonorepo(workspaceName) {
497
520
  }),
498
521
  "Workspace Configuration"
499
522
  );
500
- const action = await p__namespace.select({
523
+ const proceed = await p__namespace.confirm({
501
524
  message: "Proceed with these settings?",
502
- options: [
503
- { value: "confirm", label: "Yes, create workspace" },
504
- { value: "customize", label: "No, let me customize" }
505
- ],
506
- initialValue: "confirm"
525
+ initialValue: true
507
526
  });
508
- if (p__namespace.isCancel(action)) {
527
+ if (p__namespace.isCancel(proceed)) {
509
528
  p__namespace.cancel("Operation cancelled.");
510
529
  process.exit(0);
511
530
  }
512
- if (action === "confirm") {
531
+ if (proceed) {
513
532
  return defaultOptions;
514
533
  }
515
534
  return promptForMonorepoCustomization(workspaceName);
@@ -549,65 +568,122 @@ async function promptForOptions(name) {
549
568
  }
550
569
  return promptForPackageOptions(projectName, projectType);
551
570
  }
552
- async function promptForPackageOptions(projectName, projectType) {
553
- const template = await p__namespace.select({
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({
554
619
  message: "Select a template",
555
- options: [
556
- { value: "vanilla", label: "Vanilla" },
557
- { value: "react", label: "React" },
558
- { value: "r3f", label: "React Three Fiber" }
559
- ],
620
+ options: allOptions,
560
621
  initialValue: "vanilla"
561
622
  });
562
- if (p__namespace.isCancel(template)) {
623
+ if (p__namespace.isCancel(templateSelection)) {
563
624
  p__namespace.cancel("Operation cancelled.");
564
625
  process.exit(0);
565
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
+ }
566
665
  const defaultOptions = getDefaultOptions(
567
666
  template,
568
667
  projectName,
569
- projectType
668
+ projectType,
669
+ void 0,
670
+ integrations,
671
+ inheritedTooling
570
672
  );
571
- p__namespace.note(formatConfigSummary(defaultOptions), "Template Configuration");
572
- 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({
573
676
  message: "Proceed with these settings?",
574
- options: [
575
- { value: "confirm", label: "Yes, create project" },
576
- { value: "customize", label: "No, let me customize" }
577
- ],
578
- initialValue: "confirm"
677
+ initialValue: true
579
678
  });
580
- if (p__namespace.isCancel(action)) {
679
+ if (p__namespace.isCancel(proceed)) {
581
680
  p__namespace.cancel("Operation cancelled.");
582
681
  process.exit(0);
583
682
  }
584
- if (action === "confirm") {
683
+ if (proceed) {
585
684
  return defaultOptions;
586
685
  }
587
- return promptForCustomization(
588
- template,
589
- projectName,
590
- projectType
591
- );
592
- }
593
-
594
- const config = new Conf__default({
595
- projectName: "create-krispya"
596
- });
597
- function getPreferredEditor() {
598
- return config.get("preferredEditor");
599
- }
600
- function setPreferredEditor(editor) {
601
- config.set("preferredEditor", editor);
602
- }
603
- function getReuseWindow() {
604
- return config.get("reuseWindow") ?? false;
605
- }
606
- function setReuseWindow(reuse) {
607
- config.set("reuseWindow", reuse);
608
- }
609
- function clearConfig() {
610
- config.clear();
686
+ return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
611
687
  }
612
688
 
613
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)));
@@ -629,6 +705,258 @@ async function detectMonorepoRoot() {
629
705
  }
630
706
  return null;
631
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
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
+ }
898
+ }
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
906
+ });
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
+ }
959
+ }
632
960
  async function main() {
633
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(
634
962
  "--bundler <bundler>",
@@ -645,12 +973,16 @@ async function main() {
645
973
  ).option(
646
974
  "--node-version <version>",
647
975
  'set Node.js version for engines.node field (default: "latest")'
648
- ).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) => {
649
977
  if (options.clearConfig) {
650
978
  clearConfig();
651
979
  console.log("Configuration cleared.");
652
980
  process.exit(0);
653
981
  }
982
+ if (options.configPath) {
983
+ console.log(getConfigPath());
984
+ process.exit(0);
985
+ }
654
986
  console.clear();
655
987
  p__namespace.intro(color__default.bgCyan(color__default.black(` create-krispya v${pkg.version} `)));
656
988
  const monorepoRoot = await detectMonorepoRoot();
@@ -668,132 +1000,26 @@ async function main() {
668
1000
  process.exit(0);
669
1001
  }
670
1002
  if (choice === "add") {
671
- const packageType = await promptForInitialPackage();
672
- if (packageType === "skip") {
673
- p__namespace.cancel("Operation cancelled.");
674
- process.exit(0);
675
- }
676
- const packageName = await p__namespace.text({
677
- message: "Package name?",
678
- placeholder: packageType === "app" ? "my-app" : "my-package",
679
- validate: (value) => {
680
- if (!value.length) return "Package name is required";
681
- }
682
- });
683
- if (p__namespace.isCancel(packageName)) {
684
- p__namespace.cancel("Operation cancelled.");
685
- process.exit(0);
686
- }
687
- const targetDir = packageType === "app" ? "apps" : "packages";
688
- const packagePath = path.join(targetDir, packageName);
689
- const workspaceRoot = "../..";
690
- const packageOptions = await promptForPackageOptions(packageName, packageType);
691
- packageOptions.workspaceRoot = workspaceRoot;
692
- packageOptions.name = packageName;
693
- const packageManager2 = packageOptions.packageManager || "pnpm";
694
- if (packageManager2 === "pnpm") {
695
- packageOptions.pnpmVersion = await index.getLatestPnpmVersion();
696
- }
697
- const nodeVersion2 = packageOptions.nodeVersion ?? "latest";
698
- if (nodeVersion2 === "latest") {
699
- packageOptions.nodeVersion = await index.getLatestNodeVersion();
700
- }
701
- const versions2 = {};
702
- const versionPromises2 = [];
703
- const pkgIsLibrary = packageOptions.projectType === "library";
704
- const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
705
- if (pkgTesting === "vitest") {
706
- versionPromises2.push(
707
- index.getLatestNpmVersion("vitest", "4.0.0").then((v) => {
708
- versions2.vitest = v;
709
- })
710
- );
711
- }
712
- if (!pkgIsLibrary) {
713
- versionPromises2.push(
714
- index.getLatestNpmVersion("vite", "6.3.4").then((v) => {
715
- versions2.vite = v;
716
- })
717
- );
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})`);
718
1010
  }
719
- const linter2 = packageOptions.linter ?? "oxlint";
720
- if (linter2 === "eslint") {
721
- versionPromises2.push(
722
- index.getLatestNpmVersion("eslint", "9.17.0").then((v) => {
723
- versions2.eslint = v;
724
- })
725
- );
726
- } else if (linter2 === "oxlint") {
727
- versionPromises2.push(
728
- index.getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
729
- versions2.oxlint = v;
730
- })
731
- );
732
- } else if (linter2 === "biome") {
733
- versionPromises2.push(
734
- index.getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
735
- versions2.biome = v;
736
- })
737
- );
738
- }
739
- const formatter2 = packageOptions.formatter ?? "oxfmt";
740
- if (formatter2 === "prettier") {
741
- versionPromises2.push(
742
- index.getLatestNpmVersion("prettier", "3.4.2").then((v) => {
743
- versions2.prettier = v;
744
- })
745
- );
746
- } else if (formatter2 === "oxfmt") {
747
- versionPromises2.push(
748
- index.getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
749
- versions2.oxfmt = v;
750
- })
751
- );
752
- } else if (formatter2 === "biome" && linter2 !== "biome") {
753
- versionPromises2.push(
754
- index.getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
755
- versions2.biome = v;
756
- })
757
- );
758
- }
759
- await Promise.all(versionPromises2);
760
- packageOptions.versions = versions2;
761
- const basePath2 = path.join(monorepoRoot, packagePath);
762
- const s2 = p__namespace.spinner();
763
- s2.start("Creating package...");
764
- try {
765
- const files = index.generate(packageOptions);
766
- const filePaths = Object.keys(files).sort();
767
- for (const filePath of filePaths) {
768
- const fullFilePath = path.join(basePath2, filePath);
769
- await promises.mkdir(path.dirname(fullFilePath), { recursive: true });
770
- const file = files[filePath];
771
- if (file.type === "text") {
772
- await promises.writeFile(fullFilePath, file.content);
773
- } else {
774
- const response = await undici.fetch(file.url);
775
- await promises.writeFile(fullFilePath, response.body);
776
- }
777
- }
778
- s2.stop("Package created!");
779
- const isLibrary2 = packageOptions.projectType === "library";
780
- const nextSteps = isLibrary2 ? [
781
- `cd ${packagePath}`,
782
- `${packageManager2} install`,
783
- `${packageManager2} run build`
784
- ].join("\n") : [
785
- `cd ${packagePath}`,
786
- `${packageManager2} install`,
787
- `${packageManager2} run dev`
788
- ].join("\n");
789
- p__namespace.note(nextSteps, "Next steps");
790
- p__namespace.outro(color__default.green("Happy coding! \u2728"));
791
- process.exit(0);
792
- } catch (error) {
793
- s2.stop("Failed to create package");
794
- p__namespace.log.error(String(error));
795
- process.exit(1);
1011
+ const scope = await getMonorepoScope(monorepoRoot);
1012
+ let addMore = true;
1013
+ while (addMore) {
1014
+ addMore = await createPackageInWorkspace(monorepoRoot, "pnpm", inheritedTooling, scope);
796
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);
797
1023
  }
798
1024
  }
799
1025
  let generateOptions;
@@ -862,60 +1088,15 @@ async function main() {
862
1088
  await promises.writeFile(fullFilePath, file.content);
863
1089
  }
864
1090
  }
865
- s2.stop("Monorepo workspace created!");
866
- const initialPackage = await promptForInitialPackage();
867
- if (initialPackage !== "skip") {
868
- const packageName = await p__namespace.text({
869
- message: "Package name?",
870
- placeholder: initialPackage === "app" ? "my-app" : "my-package",
871
- validate: (value) => {
872
- if (!value.length) return "Package name is required";
873
- }
874
- });
875
- if (!p__namespace.isCancel(packageName)) {
876
- const targetDir = initialPackage === "app" ? "apps" : "packages";
877
- const packagePath = path.join(targetDir, packageName);
878
- const packageOptions = await promptForPackageOptions(packageName, initialPackage);
879
- packageOptions.workspaceRoot = "../..";
880
- packageOptions.name = packageName;
881
- const pkgManager = packageOptions.packageManager || "pnpm";
882
- const versions2 = {};
883
- const versionPromises2 = [];
884
- const initPkgIsLibrary = packageOptions.projectType === "library";
885
- const initPkgTesting = packageOptions.testing ?? (initPkgIsLibrary ? "vitest" : "none");
886
- if (initPkgTesting === "vitest") {
887
- versionPromises2.push(
888
- index.getLatestNpmVersion("vitest", "4.0.0").then((v) => {
889
- versions2.vitest = v;
890
- })
891
- );
892
- }
893
- if (!initPkgIsLibrary) {
894
- versionPromises2.push(
895
- index.getLatestNpmVersion("vite", "6.3.4").then((v) => {
896
- versions2.vite = v;
897
- })
898
- );
899
- }
900
- await Promise.all(versionPromises2);
901
- packageOptions.versions = versions2;
902
- s2.start("Creating initial package...");
903
- const packageFiles = index.generate(packageOptions);
904
- const packageFilePaths = Object.keys(packageFiles).sort();
905
- const packageBasePath = path.join(basePath2, packagePath);
906
- for (const filePath of packageFilePaths) {
907
- const fullFilePath = path.join(packageBasePath, filePath);
908
- await promises.mkdir(path.dirname(fullFilePath), { recursive: true });
909
- const file = packageFiles[filePath];
910
- if (file.type === "text") {
911
- await promises.writeFile(fullFilePath, file.content);
912
- } else {
913
- const response = await undici.fetch(file.url);
914
- await promises.writeFile(fullFilePath, response.body);
915
- }
916
- }
917
- s2.stop("Initial package created!");
918
- }
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);
919
1100
  }
920
1101
  const nextSteps = [
921
1102
  `cd ${generateOptions.name}`,
@@ -923,6 +1104,7 @@ async function main() {
923
1104
  `${packageManager2} run dev`
924
1105
  ].join("\n");
925
1106
  p__namespace.note(nextSteps, "Next steps");
1107
+ await promptAndOpenEditor(basePath2);
926
1108
  p__namespace.outro(color__default.green("Happy coding! \u2728"));
927
1109
  process.exit(0);
928
1110
  } catch (error) {
@@ -1019,7 +1201,7 @@ async function main() {
1019
1201
  await promises.writeFile(fullFilePath, response.body);
1020
1202
  }
1021
1203
  }
1022
- s.stop("Project created!");
1204
+ s.stop(color__default.green.inverse(" \u2713 Project created! "));
1023
1205
  const isLibrary2 = generateOptions.projectType === "library";
1024
1206
  const nextSteps = isLibrary2 ? [
1025
1207
  `cd ${generateOptions.name}`,