create-krispya 0.5.1 → 0.5.3

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
@@ -2,14 +2,14 @@
2
2
  import { createRequire } from 'module';
3
3
  import { cwd } from 'process';
4
4
  import { join, dirname, resolve } from 'path';
5
- import { mkdir, writeFile, access, readFile } from 'fs/promises';
6
- import { constants } from 'fs';
5
+ import { access, constants, mkdir, writeFile, unlink, readFile } from 'fs/promises';
6
+ import { constants as constants$1 } from 'fs';
7
7
  import { Command } from 'commander';
8
8
  import * as p from '@clack/prompts';
9
9
  import color from 'chalk';
10
10
  import { fetch } from 'undici';
11
11
  import { spawn } from 'child_process';
12
- import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as getLatestPnpmVersion, d as getLatestNodeVersion, e as getLatestNpmVersion, f as generate } from './chunks/index.mjs';
12
+ import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as generateTypescriptConfigPackage, d as generateOxlintConfigPackage, e as generateEslintConfigPackage, f as generateOxfmtConfigPackage, h as generatePrettierConfigPackage, i as generateVscodeFiles, j as generateAiFiles, k as getLatestNpmVersion, l as generate, m as getLatestPnpmVersion, n as getLatestYarnVersion, o as getLatestNpmCliVersion, p as getLatestNodeVersion, v as validatePackageName, q as parseWorkspaceYamlContent } from './chunks/index.mjs';
13
13
  import Conf from 'conf';
14
14
 
15
15
  const editorNames = {
@@ -138,6 +138,12 @@ function getReuseWindow() {
138
138
  function setReuseWindow(reuse) {
139
139
  config.set("reuseWindow", reuse);
140
140
  }
141
+ function getAiFiles() {
142
+ return config.get("aiFiles");
143
+ }
144
+ function setAiFiles(files) {
145
+ config.set("aiFiles", files);
146
+ }
141
147
  function clearConfig() {
142
148
  config.clear();
143
149
  }
@@ -425,31 +431,14 @@ async function promptForMonorepoCustomization(name) {
425
431
  p.cancel("Operation cancelled.");
426
432
  process.exit(0);
427
433
  }
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"
434
+ const managePnpm = await p.confirm({
435
+ message: "Enable manage-package-manager-versions?",
436
+ initialValue: true
436
437
  });
437
- if (p.isCancel(packageManager)) {
438
+ if (p.isCancel(managePnpm)) {
438
439
  p.cancel("Operation cancelled.");
439
440
  process.exit(0);
440
441
  }
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);
450
- }
451
- pnpmManageVersions = managePnpm;
452
- }
453
442
  const linter = await p.select({
454
443
  message: "Linter",
455
444
  options: [
@@ -480,8 +469,8 @@ async function promptForMonorepoCustomization(name) {
480
469
  name,
481
470
  projectType: "monorepo",
482
471
  nodeVersion,
483
- packageManager,
484
- pnpmManageVersions,
472
+ packageManager: "pnpm",
473
+ pnpmManageVersions: managePnpm,
485
474
  linter,
486
475
  formatter
487
476
  };
@@ -665,15 +654,86 @@ async function promptForPackageOptions(projectName, projectType, inheritedToolin
665
654
  return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
666
655
  }
667
656
 
657
+ async function checkAnyExists(paths) {
658
+ for (const path of paths) {
659
+ try {
660
+ await access(path, constants.F_OK);
661
+ return true;
662
+ } catch {
663
+ }
664
+ }
665
+ return false;
666
+ }
667
+ async function validateWorkspace(monorepoRoot) {
668
+ const errors = [];
669
+ const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
670
+ try {
671
+ await access(tsConfigPath, constants.F_OK);
672
+ } catch {
673
+ errors.push("Missing .config/typescript package");
674
+ }
675
+ const linterPaths = [
676
+ join(monorepoRoot, ".config/oxlint/package.json"),
677
+ join(monorepoRoot, ".config/eslint/package.json"),
678
+ join(monorepoRoot, "eslint.config.js"),
679
+ join(monorepoRoot, "biome.json")
680
+ ];
681
+ const hasLinter = await checkAnyExists(linterPaths);
682
+ if (!hasLinter) {
683
+ errors.push(
684
+ "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
685
+ );
686
+ }
687
+ const formatterPaths = [
688
+ join(monorepoRoot, ".config/oxfmt/package.json"),
689
+ join(monorepoRoot, ".config/prettier/package.json"),
690
+ join(monorepoRoot, ".prettierrc.json"),
691
+ join(monorepoRoot, "biome.json")
692
+ ];
693
+ const hasFormatter = await checkAnyExists(formatterPaths);
694
+ if (!hasFormatter) {
695
+ errors.push(
696
+ "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
697
+ );
698
+ }
699
+ return { valid: errors.length === 0, errors };
700
+ }
701
+
668
702
  const require$1 = createRequire(import.meta.url);
669
703
  const pkg = require$1("../package.json");
704
+ async function fileExists(path) {
705
+ try {
706
+ await access(path, constants$1.F_OK);
707
+ return true;
708
+ } catch {
709
+ return false;
710
+ }
711
+ }
712
+ async function writeGeneratedFiles(basePath, files) {
713
+ const filePaths = Object.keys(files).sort();
714
+ for (const filePath of filePaths) {
715
+ const fullFilePath = join(basePath, filePath);
716
+ await mkdir(dirname(fullFilePath), { recursive: true });
717
+ const file = files[filePath];
718
+ if (file.type === "text") {
719
+ await writeFile(fullFilePath, file.content);
720
+ } else {
721
+ const response = await fetch(file.url);
722
+ await writeFile(fullFilePath, response.body);
723
+ }
724
+ }
725
+ }
726
+ function calculateWorkspaceRoot(packagePath) {
727
+ const segments = packagePath.split(/[/\\]/).filter(Boolean);
728
+ return segments.map(() => "..").join("/");
729
+ }
670
730
  async function detectMonorepoRoot() {
671
731
  let currentDir = cwd();
672
732
  const root = resolve("/");
673
733
  while (currentDir !== root) {
674
734
  const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
675
735
  try {
676
- await access(workspaceFile, constants.F_OK);
736
+ await access(workspaceFile, constants$1.F_OK);
677
737
  const content = await readFile(workspaceFile, "utf-8");
678
738
  if (content.includes("packages:")) {
679
739
  return currentDir;
@@ -684,12 +744,21 @@ async function detectMonorepoRoot() {
684
744
  }
685
745
  return null;
686
746
  }
747
+ async function parseWorkspaceDirectories(monorepoRoot) {
748
+ try {
749
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
750
+ const content = await readFile(workspaceFile, "utf-8");
751
+ return parseWorkspaceYamlContent(content);
752
+ } catch {
753
+ return [];
754
+ }
755
+ }
687
756
  async function detectWorkspaceTooling(monorepoRoot) {
688
757
  try {
689
758
  const pkgPath = join(monorepoRoot, "package.json");
690
759
  const content = await readFile(pkgPath, "utf-8");
691
- const pkg2 = JSON.parse(content);
692
- const devDeps = pkg2.devDependencies ?? {};
760
+ const pkgJson = JSON.parse(content);
761
+ const devDeps = pkgJson.devDependencies ?? {};
693
762
  const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
694
763
  const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
695
764
  return { linter, formatter };
@@ -697,13 +766,33 @@ async function detectWorkspaceTooling(monorepoRoot) {
697
766
  return {};
698
767
  }
699
768
  }
769
+ async function detectExistingConfigs(monorepoRoot) {
770
+ const configs = {};
771
+ const eslintPath = join(monorepoRoot, "eslint.config.js");
772
+ if (await fileExists(eslintPath)) {
773
+ configs.linter = "eslint";
774
+ configs.eslintConfigPath = eslintPath;
775
+ }
776
+ const prettierPath = join(monorepoRoot, ".prettierrc.json");
777
+ if (await fileExists(prettierPath)) {
778
+ configs.formatter = "prettier";
779
+ configs.prettierConfigPath = prettierPath;
780
+ }
781
+ const biomePath = join(monorepoRoot, "biome.json");
782
+ if (await fileExists(biomePath)) {
783
+ configs.biomeConfigPath = biomePath;
784
+ if (!configs.linter) configs.linter = "biome";
785
+ if (!configs.formatter) configs.formatter = "biome";
786
+ }
787
+ return configs;
788
+ }
700
789
  async function getMonorepoScope(monorepoRoot) {
701
790
  try {
702
791
  const pkgPath = join(monorepoRoot, "package.json");
703
792
  const content = await readFile(pkgPath, "utf-8");
704
- const pkg2 = JSON.parse(content);
705
- if (pkg2.name) {
706
- return pkg2.name.replace(/^@/, "").replace(/\/.*$/, "");
793
+ const pkgJson = JSON.parse(content);
794
+ if (pkgJson.name) {
795
+ return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
707
796
  }
708
797
  } catch {
709
798
  }
@@ -711,56 +800,264 @@ async function getMonorepoScope(monorepoRoot) {
711
800
  }
712
801
  async function getWorkspacePackages(monorepoRoot) {
713
802
  const packagesDir = join(monorepoRoot, "packages");
714
- const packages = [];
715
803
  try {
716
804
  const { readdir } = await import('fs/promises');
717
805
  const entries = await readdir(packagesDir, { withFileTypes: true });
806
+ const names = [];
718
807
  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
- }
808
+ if (!entry.isDirectory()) continue;
809
+ try {
810
+ const content = await readFile(
811
+ join(packagesDir, entry.name, "package.json"),
812
+ "utf-8"
813
+ );
814
+ const pkg2 = JSON.parse(content);
815
+ if (pkg2.name) names.push(pkg2.name);
816
+ } catch {
729
817
  }
730
818
  }
819
+ return names;
820
+ } catch {
821
+ return [];
822
+ }
823
+ }
824
+ async function ensureConfigInWorkspace(monorepoRoot) {
825
+ const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
826
+ let content;
827
+ try {
828
+ content = await readFile(workspacePath, "utf-8");
829
+ } catch {
830
+ content = `packages:
831
+ - ".config/*"
832
+ - "packages/*"
833
+ `;
834
+ await writeFile(workspacePath, content);
835
+ return;
836
+ }
837
+ if (content.includes(".config/*") || content.includes('".config/*"')) {
838
+ return;
839
+ }
840
+ const lines = content.split("\n");
841
+ const packagesIndex = lines.findIndex(
842
+ (line) => line.trim().startsWith("packages:")
843
+ );
844
+ if (packagesIndex === -1) {
845
+ content = `packages:
846
+ - ".config/*"
847
+ ${content}`;
848
+ } else {
849
+ lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
850
+ content = lines.join("\n");
851
+ }
852
+ await writeFile(workspacePath, content);
853
+ }
854
+ async function migrateEslintConfig(monorepoRoot, files) {
855
+ const configBasePath = ".config/eslint";
856
+ const existingConfigPath = join(monorepoRoot, "eslint.config.js");
857
+ let existingContent;
858
+ try {
859
+ existingContent = await readFile(existingConfigPath, "utf-8");
860
+ } catch {
861
+ generateEslintConfigPackage(files);
862
+ return;
863
+ }
864
+ files[`${configBasePath}/package.json`] = {
865
+ type: "text",
866
+ content: JSON.stringify(
867
+ {
868
+ name: "@config/eslint",
869
+ version: "0.1.0",
870
+ private: true,
871
+ type: "module",
872
+ exports: {
873
+ "./base": "./base.js",
874
+ "./react": "./react.js"
875
+ }
876
+ },
877
+ null,
878
+ 2
879
+ )
880
+ };
881
+ files[`${configBasePath}/README.md`] = {
882
+ type: "text",
883
+ content: `# \`@config/eslint\`
884
+
885
+ Shared ESLint configurations.
886
+
887
+ ## Usage
888
+
889
+ In your package's \`eslint.config.js\`:
890
+
891
+ \`\`\`js
892
+ import base from "@config/eslint/base";
893
+
894
+ export default [...base];
895
+ \`\`\`
896
+
897
+ ## Available Configs
898
+
899
+ - \`base\` - Base ESLint rules (migrated from root)
900
+ - \`react\` - React-specific rules
901
+ `
902
+ };
903
+ files[`${configBasePath}/base.js`] = {
904
+ type: "text",
905
+ content: existingContent
906
+ };
907
+ files[`${configBasePath}/react.js`] = {
908
+ type: "text",
909
+ content: `import react from "eslint-plugin-react";
910
+ import reactHooks from "eslint-plugin-react-hooks";
911
+
912
+ export default [
913
+ {
914
+ plugins: {
915
+ react,
916
+ "react-hooks": reactHooks,
917
+ },
918
+ rules: {
919
+ ...react.configs.recommended.rules,
920
+ ...reactHooks.configs.recommended.rules,
921
+ "react/react-in-jsx-scope": "off",
922
+ },
923
+ settings: {
924
+ react: {
925
+ version: "detect",
926
+ },
927
+ },
928
+ },
929
+ ];
930
+ `
931
+ };
932
+ }
933
+ async function migratePrettierConfig(monorepoRoot, files) {
934
+ const configBasePath = ".config/prettier";
935
+ const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
936
+ let existingContent;
937
+ try {
938
+ existingContent = await readFile(existingConfigPath, "utf-8");
731
939
  } catch {
940
+ generatePrettierConfigPackage(files);
941
+ return;
732
942
  }
733
- return packages;
943
+ files[`${configBasePath}/package.json`] = {
944
+ type: "text",
945
+ content: JSON.stringify(
946
+ {
947
+ name: "@config/prettier",
948
+ version: "0.1.0",
949
+ private: true,
950
+ exports: {
951
+ "./base": "./base.json"
952
+ }
953
+ },
954
+ null,
955
+ 2
956
+ )
957
+ };
958
+ files[`${configBasePath}/README.md`] = {
959
+ type: "text",
960
+ content: `# \`@config/prettier\`
961
+
962
+ Shared Prettier configurations.
963
+
964
+ ## Usage
965
+
966
+ In your package's \`.prettierrc\`:
967
+
968
+ \`\`\`json
969
+ "@config/prettier/base"
970
+ \`\`\`
971
+
972
+ Or in \`package.json\`:
973
+
974
+ \`\`\`json
975
+ {
976
+ "prettier": "@config/prettier/base"
977
+ }
978
+ \`\`\`
979
+
980
+ ## Available Configs
981
+
982
+ - \`base\` - Base Prettier rules (migrated from root)
983
+ `
984
+ };
985
+ files[`${configBasePath}/base.json`] = {
986
+ type: "text",
987
+ content: existingContent
988
+ };
734
989
  }
735
990
  async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
991
+ const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
992
+ const defaultDirectories = ["apps", "packages"];
993
+ const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
736
994
  const packageType = await promptForInitialPackage();
737
995
  if (packageType === "skip") {
738
996
  return false;
739
997
  }
998
+ const defaultDir = packageType === "app" ? "apps" : "packages";
740
999
  const packageNameInput = await p.text({
741
1000
  message: "Package name?",
742
- placeholder: `Scoped to @${scope}/`,
1001
+ initialValue: `@${scope}/`,
743
1002
  validate: (value) => {
744
- if (!value.length) return "Package name is required";
1003
+ const validationError = validatePackageName(value);
1004
+ if (validationError) return validationError;
1005
+ const dirName = value.includes("/") ? value.split("/").pop() : value;
1006
+ if (!dirName) return "Package name is required";
1007
+ if (!hasCustomDirectories) {
1008
+ const targetPath = join(monorepoRoot, defaultDir, dirName);
1009
+ try {
1010
+ const { statSync } = require$1("fs");
1011
+ statSync(targetPath);
1012
+ return `Directory ${defaultDir}/${dirName} already exists`;
1013
+ } catch {
1014
+ }
1015
+ }
745
1016
  }
746
1017
  });
747
1018
  if (p.isCancel(packageNameInput)) {
748
1019
  return false;
749
1020
  }
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 = "../..";
1021
+ const scopedName = packageNameInput;
1022
+ const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
755
1023
  const packageOptions = await promptForPackageOptions(
756
1024
  scopedName,
757
1025
  packageType,
758
1026
  inheritedTooling
759
1027
  );
1028
+ let targetDir = defaultDir;
1029
+ if (hasCustomDirectories && workspaceDirectories.length > 0) {
1030
+ const dirChoice = await p.select({
1031
+ message: "Target directory",
1032
+ options: workspaceDirectories.map((dir) => ({
1033
+ value: dir,
1034
+ label: dir
1035
+ })),
1036
+ initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
1037
+ });
1038
+ if (p.isCancel(dirChoice)) {
1039
+ return false;
1040
+ }
1041
+ targetDir = dirChoice;
1042
+ const targetPath = join(monorepoRoot, targetDir, shortName);
1043
+ try {
1044
+ const { statSync } = require$1("fs");
1045
+ statSync(targetPath);
1046
+ p.log.error(`Directory ${targetDir}/${shortName} already exists`);
1047
+ return false;
1048
+ } catch {
1049
+ }
1050
+ }
1051
+ const relativePkgPath = join(targetDir, shortName);
1052
+ const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
760
1053
  packageOptions.workspaceRoot = workspaceRoot;
761
1054
  packageOptions.name = scopedName;
762
1055
  if (packageManager === "pnpm") {
763
1056
  packageOptions.pnpmVersion = await getLatestPnpmVersion();
1057
+ } else if (packageManager === "yarn") {
1058
+ packageOptions.yarnVersion = await getLatestYarnVersion();
1059
+ } else if (packageManager === "npm") {
1060
+ packageOptions.npmVersion = await getLatestNpmCliVersion();
764
1061
  }
765
1062
  const nodeVersion = packageOptions.nodeVersion ?? "latest";
766
1063
  if (nodeVersion === "latest") {
@@ -826,40 +1123,26 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
826
1123
  }
827
1124
  await Promise.all(versionPromises);
828
1125
  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
- }
1126
+ const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
1127
+ if (workspacePackages.length > 0) {
1128
+ const selectedDeps = await p.multiselect({
1129
+ message: "Add workspace dependencies?",
1130
+ options: workspacePackages.map((name) => ({ value: name, label: name })),
1131
+ required: false
1132
+ });
1133
+ if (!p.isCancel(selectedDeps) && selectedDeps.length > 0) {
1134
+ packageOptions.workspaceDependencies = selectedDeps;
843
1135
  }
844
1136
  }
845
- const basePath = join(monorepoRoot, packagePath);
846
- const s = p.spinner();
847
- s.start("Creating package...");
1137
+ const outputPath = join(monorepoRoot, relativePkgPath);
1138
+ const spinner = p.spinner();
1139
+ spinner.start("Creating package...");
848
1140
  try {
849
1141
  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}! `));
1142
+ await writeGeneratedFiles(outputPath, files);
1143
+ spinner.stop(
1144
+ color.green.inverse(` \u2713 Package created at ${relativePkgPath}! `)
1145
+ );
863
1146
  const addAnother = await p.select({
864
1147
  message: "Add another package?",
865
1148
  options: [
@@ -870,12 +1153,12 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedT
870
1153
  });
871
1154
  return !p.isCancel(addAnother) && addAnother === "yes";
872
1155
  } catch (error) {
873
- s.stop("Failed to create package");
1156
+ spinner.stop("Failed to create package");
874
1157
  p.log.error(String(error));
875
1158
  return false;
876
1159
  }
877
1160
  }
878
- async function promptAndOpenEditor(basePath) {
1161
+ async function promptAndOpenEditor(projectPath) {
879
1162
  const savedEditor = getPreferredEditor();
880
1163
  let selectedEditor;
881
1164
  if (savedEditor && savedEditor !== "skip") {
@@ -925,7 +1208,7 @@ async function promptAndOpenEditor(basePath) {
925
1208
  try {
926
1209
  await openInEditor(
927
1210
  selectedEditor,
928
- basePath,
1211
+ projectPath,
929
1212
  getReuseWindow()
930
1213
  );
931
1214
  p.log.success(`Opening in ${editorNames[selectedEditor]}...`);
@@ -936,14 +1219,673 @@ async function promptAndOpenEditor(basePath) {
936
1219
  }
937
1220
  }
938
1221
  }
1222
+ async function handleCheckCommand() {
1223
+ const monorepoRoot = await detectMonorepoRoot();
1224
+ if (!monorepoRoot) {
1225
+ console.log(color.red("\u2717") + " Not a monorepo workspace");
1226
+ process.exit(1);
1227
+ }
1228
+ const { valid, errors } = await validateWorkspace(monorepoRoot);
1229
+ if (valid) {
1230
+ console.log(color.green("\u2713") + " Valid monorepo workspace");
1231
+ console.log(color.dim(` ${monorepoRoot}`));
1232
+ } else {
1233
+ console.log(color.red("\u2717") + " Invalid monorepo workspace");
1234
+ console.log(color.dim(` ${monorepoRoot}`));
1235
+ for (const error of errors) {
1236
+ console.log(color.red(` \u2022 ${error}`));
1237
+ }
1238
+ }
1239
+ process.exit(valid ? 0 : 1);
1240
+ }
1241
+ async function handleFixCommand(options) {
1242
+ const monorepoRoot = await detectMonorepoRoot();
1243
+ if (!monorepoRoot) {
1244
+ console.log(color.red("\u2717") + " Not a monorepo workspace");
1245
+ console.log(color.dim(" Run this command from within a monorepo"));
1246
+ process.exit(1);
1247
+ }
1248
+ const { valid, errors } = await validateWorkspace(monorepoRoot);
1249
+ if (valid) {
1250
+ console.log(color.green("\u2713") + " Workspace is already valid");
1251
+ console.log(color.dim(` ${monorepoRoot}`));
1252
+ process.exit(0);
1253
+ }
1254
+ console.log(color.yellow("!") + " Invalid monorepo workspace");
1255
+ for (const error of errors) {
1256
+ console.log(color.dim(` \u2022 ${error}`));
1257
+ }
1258
+ console.log();
1259
+ const tooling = await detectWorkspaceTooling(monorepoRoot);
1260
+ const existingConfigs = await detectExistingConfigs(monorepoRoot);
1261
+ const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
1262
+ const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
1263
+ const isNonInteractive = options.linter && options.formatter;
1264
+ let linter;
1265
+ let formatter;
1266
+ if (isNonInteractive) {
1267
+ linter = options.linter;
1268
+ formatter = options.formatter;
1269
+ } else {
1270
+ const linterChoice = await p.select({
1271
+ message: "Linter",
1272
+ options: [
1273
+ {
1274
+ value: "oxlint",
1275
+ label: "oxlint" + (tooling.linter === "oxlint" ? color.dim(" (installed)") : "")
1276
+ },
1277
+ {
1278
+ value: "eslint",
1279
+ label: "eslint" + (tooling.linter === "eslint" || existingConfigs.linter === "eslint" ? color.dim(" (installed)") : "")
1280
+ },
1281
+ {
1282
+ value: "biome",
1283
+ label: "biome" + (tooling.linter === "biome" ? color.dim(" (installed)") : "")
1284
+ }
1285
+ ],
1286
+ initialValue: detectedLinter
1287
+ });
1288
+ if (p.isCancel(linterChoice)) {
1289
+ p.cancel("Operation cancelled.");
1290
+ process.exit(0);
1291
+ }
1292
+ const formatterChoice = await p.select({
1293
+ message: "Formatter",
1294
+ options: [
1295
+ {
1296
+ value: "oxfmt",
1297
+ label: "oxfmt" + (tooling.formatter === "oxfmt" ? color.dim(" (installed)") : "")
1298
+ },
1299
+ {
1300
+ value: "prettier",
1301
+ label: "prettier" + (tooling.formatter === "prettier" || existingConfigs.formatter === "prettier" ? color.dim(" (installed)") : "")
1302
+ },
1303
+ {
1304
+ value: "biome",
1305
+ label: "biome" + (tooling.formatter === "biome" ? color.dim(" (installed)") : "")
1306
+ }
1307
+ ],
1308
+ initialValue: detectedFormatter
1309
+ });
1310
+ if (p.isCancel(formatterChoice)) {
1311
+ p.cancel("Operation cancelled.");
1312
+ process.exit(0);
1313
+ }
1314
+ linter = linterChoice;
1315
+ formatter = formatterChoice;
1316
+ }
1317
+ console.log();
1318
+ const spinner = p.spinner();
1319
+ spinner.start("Fixing workspace...");
1320
+ try {
1321
+ const files = {};
1322
+ const tsConfigExists = await fileExists(
1323
+ join(monorepoRoot, ".config/typescript/package.json")
1324
+ );
1325
+ if (!tsConfigExists) {
1326
+ generateTypescriptConfigPackage(files);
1327
+ }
1328
+ if (linter === "oxlint") {
1329
+ const oxlintExists = await fileExists(
1330
+ join(monorepoRoot, ".config/oxlint/package.json")
1331
+ );
1332
+ if (!oxlintExists) generateOxlintConfigPackage(files);
1333
+ } else if (linter === "eslint") {
1334
+ const eslintPkgExists = await fileExists(
1335
+ join(monorepoRoot, ".config/eslint/package.json")
1336
+ );
1337
+ if (!eslintPkgExists) {
1338
+ if (existingConfigs.eslintConfigPath) {
1339
+ await migrateEslintConfig(monorepoRoot, files);
1340
+ } else {
1341
+ generateEslintConfigPackage(files);
1342
+ }
1343
+ }
1344
+ }
1345
+ if (formatter === "oxfmt") {
1346
+ const oxfmtExists = await fileExists(
1347
+ join(monorepoRoot, ".config/oxfmt/package.json")
1348
+ );
1349
+ if (!oxfmtExists) generateOxfmtConfigPackage(files);
1350
+ } else if (formatter === "prettier") {
1351
+ const prettierPkgExists = await fileExists(
1352
+ join(monorepoRoot, ".config/prettier/package.json")
1353
+ );
1354
+ if (!prettierPkgExists) {
1355
+ if (existingConfigs.prettierConfigPath) {
1356
+ await migratePrettierConfig(monorepoRoot, files);
1357
+ } else {
1358
+ generatePrettierConfigPackage(files);
1359
+ }
1360
+ }
1361
+ }
1362
+ if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
1363
+ const biomeConfig = {
1364
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1365
+ vcs: {
1366
+ enabled: true,
1367
+ clientKind: "git",
1368
+ useIgnoreFile: true
1369
+ },
1370
+ linter: {
1371
+ enabled: linter === "biome",
1372
+ rules: {
1373
+ recommended: true
1374
+ }
1375
+ },
1376
+ formatter: {
1377
+ enabled: formatter === "biome"
1378
+ }
1379
+ };
1380
+ files["biome.json"] = {
1381
+ type: "text",
1382
+ content: JSON.stringify(biomeConfig, null, 2)
1383
+ };
1384
+ }
1385
+ for (const [filePath, file] of Object.entries(files)) {
1386
+ const fullPath = join(monorepoRoot, filePath);
1387
+ await mkdir(dirname(fullPath), { recursive: true });
1388
+ await writeFile(fullPath, file.content);
1389
+ }
1390
+ await ensureConfigInWorkspace(monorepoRoot);
1391
+ if (existingConfigs.eslintConfigPath && linter === "eslint") {
1392
+ try {
1393
+ await unlink(existingConfigs.eslintConfigPath);
1394
+ } catch {
1395
+ }
1396
+ }
1397
+ if (existingConfigs.prettierConfigPath && formatter === "prettier") {
1398
+ try {
1399
+ await unlink(existingConfigs.prettierConfigPath);
1400
+ } catch {
1401
+ }
1402
+ }
1403
+ spinner.stop(color.green("\u2713") + " Workspace fixed!");
1404
+ const generated = Object.keys(files).filter(
1405
+ (f) => f.endsWith("package.json")
1406
+ );
1407
+ for (const pkgFile of generated) {
1408
+ const pkgName = pkgFile.replace("/package.json", "");
1409
+ console.log(color.dim(` Generated ${pkgName}`));
1410
+ }
1411
+ const vscodeSettingsExists = await fileExists(
1412
+ join(monorepoRoot, ".vscode/settings.json")
1413
+ );
1414
+ const vscodeExtensionsExists = await fileExists(
1415
+ join(monorepoRoot, ".vscode/extensions.json")
1416
+ );
1417
+ const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
1418
+ if (!vscodeExists) {
1419
+ let addVscode = false;
1420
+ if (isNonInteractive) {
1421
+ addVscode = true;
1422
+ } else {
1423
+ const vscodeChoice = await p.confirm({
1424
+ message: "Generate VS Code settings?",
1425
+ initialValue: true
1426
+ });
1427
+ addVscode = !p.isCancel(vscodeChoice) && vscodeChoice;
1428
+ }
1429
+ if (addVscode) {
1430
+ const vscodeFiles = {};
1431
+ generateVscodeFiles(vscodeFiles, linter, formatter);
1432
+ for (const [filePath, file] of Object.entries(vscodeFiles)) {
1433
+ const fullPath = join(monorepoRoot, filePath);
1434
+ await mkdir(dirname(fullPath), { recursive: true });
1435
+ await writeFile(fullPath, file.content);
1436
+ }
1437
+ console.log(color.dim(" Generated .vscode/settings.json"));
1438
+ console.log(color.dim(" Generated .vscode/extensions.json"));
1439
+ }
1440
+ }
1441
+ const aiFilePaths = {
1442
+ "cursor-rules": ".cursor/rules",
1443
+ "agents-md": "AGENTS.md",
1444
+ "claude-md": "CLAUDE.md",
1445
+ "copilot-md": ".github/copilot-instructions.md"
1446
+ };
1447
+ const existingAiFiles = [];
1448
+ for (const [choice, path] of Object.entries(aiFilePaths)) {
1449
+ if (await fileExists(join(monorepoRoot, path))) {
1450
+ existingAiFiles.push(choice);
1451
+ }
1452
+ }
1453
+ let selectedAiFiles = [];
1454
+ const savedAiFiles = getAiFiles();
1455
+ const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
1456
+ if (availableChoices.length === 0) {
1457
+ } else if (isNonInteractive) {
1458
+ const preferred = savedAiFiles ?? ["cursor-rules"];
1459
+ selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
1460
+ } else if (savedAiFiles && savedAiFiles.length > 0) {
1461
+ const availableSaved = savedAiFiles.filter(
1462
+ (f) => availableChoices.includes(f)
1463
+ );
1464
+ if (availableSaved.length > 0) {
1465
+ const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
1466
+ const useDefault = await p.confirm({
1467
+ message: `Generate AI instruction files? ${color.dim(
1468
+ `(${savedLabels})`
1469
+ )}`,
1470
+ initialValue: true
1471
+ });
1472
+ if (!p.isCancel(useDefault) && useDefault) {
1473
+ selectedAiFiles = availableSaved;
1474
+ }
1475
+ }
1476
+ } else {
1477
+ const aiFilesChoice = await p.multiselect({
1478
+ message: "Generate AI instruction files?",
1479
+ options: availableChoices.map((c) => ({
1480
+ value: c,
1481
+ label: aiFilePaths[c],
1482
+ hint: c === "cursor-rules" ? "Cursor AI" : c === "agents-md" ? "GitHub Copilot, general" : c === "claude-md" ? "Claude" : "GitHub Copilot"
1483
+ })),
1484
+ required: false
1485
+ });
1486
+ if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1487
+ selectedAiFiles = aiFilesChoice;
1488
+ const saveChoice = await p.confirm({
1489
+ message: "Save as default for future?",
1490
+ initialValue: true
1491
+ });
1492
+ if (!p.isCancel(saveChoice) && saveChoice) {
1493
+ setAiFiles(selectedAiFiles);
1494
+ }
1495
+ }
1496
+ }
1497
+ if (selectedAiFiles.length > 0) {
1498
+ const scope = await getMonorepoScope(monorepoRoot);
1499
+ const aiFilesOutput = {};
1500
+ generateAiFiles(aiFilesOutput, {
1501
+ name: scope,
1502
+ packageManager: "pnpm",
1503
+ linter,
1504
+ formatter,
1505
+ aiFiles: selectedAiFiles
1506
+ });
1507
+ for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1508
+ const fullPath = join(monorepoRoot, filePath);
1509
+ await mkdir(dirname(fullPath), { recursive: true });
1510
+ await writeFile(fullPath, file.content);
1511
+ console.log(color.dim(` Generated ${filePath}`));
1512
+ }
1513
+ }
1514
+ process.exit(0);
1515
+ } catch (error) {
1516
+ spinner.stop(color.red("\u2717") + " Failed to fix workspace");
1517
+ console.error(error);
1518
+ process.exit(1);
1519
+ }
1520
+ }
1521
+ async function handleWorkspaceCommand(name, options) {
1522
+ const monorepoRoot = await detectMonorepoRoot();
1523
+ if (!monorepoRoot) {
1524
+ console.error(
1525
+ color.red("Error:") + " --workspace flag requires being inside a monorepo"
1526
+ );
1527
+ process.exit(1);
1528
+ }
1529
+ if (!name) {
1530
+ console.error(
1531
+ color.red("Error:") + " Package name is required with --workspace flag"
1532
+ );
1533
+ console.log(
1534
+ color.dim(
1535
+ " Example: pnpm create krispya my-lib --workspace --type library"
1536
+ )
1537
+ );
1538
+ process.exit(1);
1539
+ }
1540
+ const scope = await getMonorepoScope(monorepoRoot);
1541
+ const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1542
+ const projectType = options.type ?? "app";
1543
+ const defaultDir = projectType === "library" ? "packages" : "apps";
1544
+ const targetDir = options.dir ?? defaultDir;
1545
+ const template = options.template ?? "vanilla";
1546
+ const baseTemplate = getBaseTemplate(template);
1547
+ const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
1548
+ const fullPackagePath = join(monorepoRoot, targetDir, name);
1549
+ try {
1550
+ await access(fullPackagePath, constants$1.F_OK);
1551
+ console.error(
1552
+ color.red("Error:") + ` Directory ${targetDir}/${name} already exists`
1553
+ );
1554
+ process.exit(1);
1555
+ } catch {
1556
+ }
1557
+ const versions = {};
1558
+ const versionPromises = [];
1559
+ const isLibrary = projectType === "library";
1560
+ if (!isLibrary) {
1561
+ versionPromises.push(
1562
+ getLatestNpmVersion("vite", "6.3.4").then((v) => {
1563
+ versions.vite = v;
1564
+ })
1565
+ );
1566
+ }
1567
+ const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
1568
+ const formatter = inheritedTooling.formatter ?? options.formatter ?? "oxfmt";
1569
+ await Promise.all(versionPromises);
1570
+ const relativePkgPath = join(targetDir, name);
1571
+ const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
1572
+ const generateOptions = {
1573
+ name: scopedName,
1574
+ projectType,
1575
+ libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
1576
+ template,
1577
+ linter,
1578
+ formatter,
1579
+ workspaceRoot,
1580
+ versions,
1581
+ ...baseTemplate === "r3f" && {
1582
+ drei: options.drei ? {} : void 0,
1583
+ handle: options.handle ? {} : void 0,
1584
+ leva: options.leva ? {} : void 0,
1585
+ postprocessing: options.postprocessing ? {} : void 0,
1586
+ rapier: options.rapier ? {} : void 0,
1587
+ xr: options.xr ? {} : void 0,
1588
+ uikit: options.uikit ? {} : void 0,
1589
+ offscreen: options.offscreen ? {} : void 0,
1590
+ zustand: options.zustand ? {} : void 0,
1591
+ koota: options.koota ? {} : void 0,
1592
+ viverse: options.viverse ? {} : void 0,
1593
+ triplex: options.triplex ? {} : void 0
1594
+ }
1595
+ };
1596
+ console.log(
1597
+ color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`
1598
+ );
1599
+ try {
1600
+ const files = generate(generateOptions);
1601
+ await writeGeneratedFiles(fullPackagePath, files);
1602
+ console.log(
1603
+ color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`
1604
+ );
1605
+ process.exit(0);
1606
+ } catch (error) {
1607
+ console.error(color.red("Error:") + " Failed to create package");
1608
+ console.error(String(error));
1609
+ process.exit(1);
1610
+ }
1611
+ }
1612
+ async function handleMonorepoCreation(generateOptions) {
1613
+ const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.s; });
1614
+ const packageManager = generateOptions.packageManager || "pnpm";
1615
+ if (packageManager === "pnpm") {
1616
+ generateOptions.pnpmVersion = await getLatestPnpmVersion();
1617
+ } else if (packageManager === "yarn") {
1618
+ generateOptions.yarnVersion = await getLatestYarnVersion();
1619
+ } else if (packageManager === "npm") {
1620
+ generateOptions.npmVersion = await getLatestNpmCliVersion();
1621
+ }
1622
+ const nodeVersion = generateOptions.nodeVersion ?? "latest";
1623
+ if (nodeVersion === "latest") {
1624
+ generateOptions.nodeVersion = await getLatestNodeVersion();
1625
+ }
1626
+ const savedAiFiles = getAiFiles();
1627
+ let selectedAiFiles = [];
1628
+ if (savedAiFiles && savedAiFiles.length > 0) {
1629
+ const aiFileLabels = {
1630
+ "cursor-rules": ".cursor/rules",
1631
+ "agents-md": "AGENTS.md",
1632
+ "claude-md": "CLAUDE.md",
1633
+ "copilot-md": ".github/copilot-instructions.md"
1634
+ };
1635
+ const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
1636
+ const useDefault = await p.confirm({
1637
+ message: `Generate AI instruction files? ${color.dim(
1638
+ `(${savedLabels})`
1639
+ )}`,
1640
+ initialValue: true
1641
+ });
1642
+ if (!p.isCancel(useDefault) && useDefault) {
1643
+ selectedAiFiles = savedAiFiles;
1644
+ }
1645
+ } else {
1646
+ const aiFilesChoice = await p.multiselect({
1647
+ message: "Generate AI instruction files?",
1648
+ options: [
1649
+ { value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
1650
+ {
1651
+ value: "agents-md",
1652
+ label: "AGENTS.md",
1653
+ hint: "GitHub Copilot, general"
1654
+ },
1655
+ { value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
1656
+ {
1657
+ value: "copilot-md",
1658
+ label: ".github/copilot-instructions.md",
1659
+ hint: "GitHub Copilot"
1660
+ }
1661
+ ],
1662
+ required: false
1663
+ });
1664
+ if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
1665
+ selectedAiFiles = aiFilesChoice;
1666
+ const saveChoice = await p.confirm({
1667
+ message: "Save as default for future monorepos?",
1668
+ initialValue: true
1669
+ });
1670
+ if (!p.isCancel(saveChoice) && saveChoice) {
1671
+ setAiFiles(selectedAiFiles);
1672
+ }
1673
+ }
1674
+ }
1675
+ const projectPath = join(cwd(), generateOptions.name);
1676
+ const spinner = p.spinner();
1677
+ spinner.start("Creating monorepo workspace...");
1678
+ try {
1679
+ const { files } = generateMonorepo({
1680
+ name: generateOptions.name,
1681
+ linter: generateOptions.linter ?? "oxlint",
1682
+ formatter: generateOptions.formatter ?? "oxfmt",
1683
+ packageManager,
1684
+ pnpmVersion: generateOptions.pnpmVersion,
1685
+ pnpmManageVersions: generateOptions.pnpmManageVersions,
1686
+ nodeVersion: generateOptions.nodeVersion,
1687
+ aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
1688
+ });
1689
+ const filePaths = Object.keys(files).sort();
1690
+ for (const filePath of filePaths) {
1691
+ const fullFilePath = join(projectPath, filePath);
1692
+ await mkdir(dirname(fullFilePath), { recursive: true });
1693
+ const file = files[filePath];
1694
+ if (file.type === "text") {
1695
+ await writeFile(fullFilePath, file.content);
1696
+ }
1697
+ }
1698
+ spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
1699
+ const newMonorepoTooling = {
1700
+ linter: generateOptions.linter,
1701
+ formatter: generateOptions.formatter
1702
+ };
1703
+ const scope = generateOptions.name;
1704
+ let addMore = true;
1705
+ while (addMore) {
1706
+ addMore = await createPackageInWorkspace(
1707
+ projectPath,
1708
+ packageManager,
1709
+ newMonorepoTooling,
1710
+ scope
1711
+ );
1712
+ }
1713
+ const nextSteps = [
1714
+ `cd ${generateOptions.name}`,
1715
+ `${packageManager} install`,
1716
+ `${packageManager} run dev`
1717
+ ].join("\n");
1718
+ p.note(nextSteps, "Next steps");
1719
+ await promptAndOpenEditor(projectPath);
1720
+ p.outro(color.green("Happy coding! \u2728"));
1721
+ process.exit(0);
1722
+ } catch (error) {
1723
+ spinner.stop("Failed to create monorepo workspace");
1724
+ p.log.error(String(error));
1725
+ process.exit(1);
1726
+ }
1727
+ }
1728
+ async function handleStandaloneProjectCreation(generateOptions) {
1729
+ const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
1730
+ const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
1731
+ generateOptions.name ??= defaultFallbackName;
1732
+ const packageManager = generateOptions.packageManager || "pnpm";
1733
+ if (packageManager === "pnpm") {
1734
+ generateOptions.pnpmVersion = await getLatestPnpmVersion();
1735
+ } else if (packageManager === "yarn") {
1736
+ generateOptions.yarnVersion = await getLatestYarnVersion();
1737
+ } else if (packageManager === "npm") {
1738
+ generateOptions.npmVersion = await getLatestNpmCliVersion();
1739
+ }
1740
+ const nodeVersion = generateOptions.nodeVersion ?? "latest";
1741
+ if (nodeVersion === "latest") {
1742
+ generateOptions.nodeVersion = await getLatestNodeVersion();
1743
+ }
1744
+ const versions = {};
1745
+ const versionPromises = [];
1746
+ const isLibrary = generateOptions.projectType === "library";
1747
+ const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
1748
+ if (testing === "vitest") {
1749
+ versionPromises.push(
1750
+ getLatestNpmVersion("vitest", "4.0.0").then((v) => {
1751
+ versions.vitest = v;
1752
+ })
1753
+ );
1754
+ }
1755
+ if (!isLibrary) {
1756
+ versionPromises.push(
1757
+ getLatestNpmVersion("vite", "6.3.4").then((v) => {
1758
+ versions.vite = v;
1759
+ })
1760
+ );
1761
+ }
1762
+ const linter = generateOptions.linter ?? "oxlint";
1763
+ if (linter === "eslint") {
1764
+ versionPromises.push(
1765
+ getLatestNpmVersion("eslint", "9.17.0").then((v) => {
1766
+ versions.eslint = v;
1767
+ })
1768
+ );
1769
+ } else if (linter === "oxlint") {
1770
+ versionPromises.push(
1771
+ getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
1772
+ versions.oxlint = v;
1773
+ })
1774
+ );
1775
+ } else if (linter === "biome") {
1776
+ versionPromises.push(
1777
+ getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1778
+ versions.biome = v;
1779
+ })
1780
+ );
1781
+ }
1782
+ const formatter = generateOptions.formatter ?? "oxfmt";
1783
+ if (formatter === "prettier") {
1784
+ versionPromises.push(
1785
+ getLatestNpmVersion("prettier", "3.4.2").then((v) => {
1786
+ versions.prettier = v;
1787
+ })
1788
+ );
1789
+ } else if (formatter === "oxfmt") {
1790
+ versionPromises.push(
1791
+ getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
1792
+ versions.oxfmt = v;
1793
+ })
1794
+ );
1795
+ } else if (formatter === "biome" && linter !== "biome") {
1796
+ versionPromises.push(
1797
+ getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1798
+ versions.biome = v;
1799
+ })
1800
+ );
1801
+ }
1802
+ await Promise.all(versionPromises);
1803
+ generateOptions.versions = versions;
1804
+ const projectPath = join(cwd(), generateOptions.name);
1805
+ const spinner = p.spinner();
1806
+ spinner.start("Creating project...");
1807
+ try {
1808
+ const files = generate(generateOptions);
1809
+ await writeGeneratedFiles(projectPath, files);
1810
+ spinner.stop(color.green.inverse(" \u2713 Project created! "));
1811
+ const nextSteps = isLibrary ? [
1812
+ `cd ${generateOptions.name}`,
1813
+ `${packageManager} install`,
1814
+ `${packageManager} run build`
1815
+ ].join("\n") : [
1816
+ `cd ${generateOptions.name}`,
1817
+ `${packageManager} install`,
1818
+ `${packageManager} run dev`
1819
+ ].join("\n");
1820
+ p.note(nextSteps, "Next steps");
1821
+ await promptAndOpenEditor(projectPath);
1822
+ p.outro(color.green("Happy coding! \u2728"));
1823
+ } catch (error) {
1824
+ spinner.stop("Failed to create project");
1825
+ p.log.error(String(error));
1826
+ process.exit(1);
1827
+ }
1828
+ }
1829
+ async function handleInteractiveMonorepoMode(monorepoRoot) {
1830
+ const choice = await p.select({
1831
+ message: "Detected monorepo workspace",
1832
+ options: [
1833
+ { value: "add", label: "Add new package to this workspace" },
1834
+ { value: "standalone", label: "Create standalone project" }
1835
+ ],
1836
+ initialValue: "add"
1837
+ });
1838
+ if (p.isCancel(choice)) {
1839
+ p.cancel("Operation cancelled.");
1840
+ process.exit(0);
1841
+ }
1842
+ if (choice === "add") {
1843
+ const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
1844
+ if (inheritedTooling.linter || inheritedTooling.formatter) {
1845
+ const toolingInfo = [
1846
+ inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
1847
+ inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
1848
+ ].filter(Boolean).join(", ");
1849
+ p.log.info(`Using workspace tooling (${toolingInfo})`);
1850
+ }
1851
+ const scope = await getMonorepoScope(monorepoRoot);
1852
+ let addMore = true;
1853
+ while (addMore) {
1854
+ addMore = await createPackageInWorkspace(
1855
+ monorepoRoot,
1856
+ "pnpm",
1857
+ inheritedTooling,
1858
+ scope
1859
+ );
1860
+ }
1861
+ p.note(
1862
+ [`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"),
1863
+ "Next steps"
1864
+ );
1865
+ await promptAndOpenEditor(monorepoRoot);
1866
+ p.outro(color.green("Happy coding! \u2728"));
1867
+ process.exit(0);
1868
+ }
1869
+ }
939
1870
  async function main() {
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(
1871
+ const program = new Command().name("create-krispya").description(
1872
+ "CLI for creating Vanilla, React, and React Three Fiber projects"
1873
+ ).argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
941
1874
  "--bundler <bundler>",
942
1875
  "library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
943
1876
  ).option(
944
1877
  "--template <type>",
945
1878
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
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(
1879
+ ).option(
1880
+ "--linter <type>",
1881
+ "linter: eslint, oxlint, or biome (default: oxlint)"
1882
+ ).option(
1883
+ "--formatter <type>",
1884
+ "formatter: prettier, oxfmt, or biome (default: oxfmt)"
1885
+ ).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(
1886
+ "--package-manager <manager>",
1887
+ "specify package manager (e.g. npm, yarn, pnpm)"
1888
+ ).option(
947
1889
  "--pnpm-manage-versions",
948
1890
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
949
1891
  ).option(
@@ -952,7 +1894,22 @@ async function main() {
952
1894
  ).option(
953
1895
  "--node-version <version>",
954
1896
  'set Node.js version for engines.node field (default: "latest")'
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) => {
1897
+ ).option(
1898
+ "--workspace",
1899
+ "Add package to current monorepo workspace (non-interactive)"
1900
+ ).option(
1901
+ "--dir <directory>",
1902
+ "Target directory for --workspace (default: apps/ or packages/)"
1903
+ ).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
1904
+ "--check",
1905
+ "Check if current directory is in a valid monorepo workspace"
1906
+ ).option("--fix", "Fix monorepo by generating missing .config packages").option(
1907
+ "--path <directory>",
1908
+ "Run in specified directory instead of current working directory"
1909
+ ).action(async (name, options) => {
1910
+ if (options.path) {
1911
+ process.chdir(options.path);
1912
+ }
956
1913
  if (options.clearConfig) {
957
1914
  clearConfig();
958
1915
  console.log("Configuration cleared.");
@@ -962,44 +1919,57 @@ async function main() {
962
1919
  console.log(getConfigPath());
963
1920
  process.exit(0);
964
1921
  }
1922
+ if (name?.startsWith("-")) {
1923
+ switch (name) {
1924
+ case "--version":
1925
+ case "-V":
1926
+ console.log(pkg.version);
1927
+ process.exit(0);
1928
+ case "--help":
1929
+ case "-h":
1930
+ program.help();
1931
+ break;
1932
+ case "--clear-config":
1933
+ clearConfig();
1934
+ console.log("Configuration cleared.");
1935
+ process.exit(0);
1936
+ case "--config-path":
1937
+ console.log(getConfigPath());
1938
+ process.exit(0);
1939
+ case "--check":
1940
+ await handleCheckCommand();
1941
+ break;
1942
+ case "--fix":
1943
+ options.fix = true;
1944
+ break;
1945
+ default:
1946
+ console.error(color.red(`Unknown option: ${name}`));
1947
+ process.exit(1);
1948
+ }
1949
+ }
1950
+ if (options.check) {
1951
+ await handleCheckCommand();
1952
+ }
1953
+ if (options.fix) {
1954
+ await handleFixCommand(options);
1955
+ }
1956
+ if (options.dir && !options.workspace) {
1957
+ console.error(color.red("Error:") + " --dir requires --workspace flag");
1958
+ console.log(
1959
+ color.dim(
1960
+ " Example: pnpm create krispya my-lib --workspace --dir examples"
1961
+ )
1962
+ );
1963
+ process.exit(1);
1964
+ }
1965
+ if (options.workspace) {
1966
+ await handleWorkspaceCommand(name, options);
1967
+ }
965
1968
  console.clear();
966
1969
  p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
967
1970
  const monorepoRoot = await detectMonorepoRoot();
968
1971
  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
- }
1972
+ await handleInteractiveMonorepoMode(monorepoRoot);
1003
1973
  }
1004
1974
  let generateOptions;
1005
1975
  if (Object.keys(options).length > 0) {
@@ -1036,226 +2006,9 @@ async function main() {
1036
2006
  generateOptions = await promptForOptions(name);
1037
2007
  }
1038
2008
  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
- }
1095
- const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
1096
- const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
1097
- generateOptions.name ??= defaultFallbackName;
1098
- const packageManager = generateOptions.packageManager || "pnpm";
1099
- if (packageManager === "pnpm") {
1100
- generateOptions.pnpmVersion = await getLatestPnpmVersion();
1101
- }
1102
- const nodeVersion = generateOptions.nodeVersion ?? "latest";
1103
- if (nodeVersion === "latest") {
1104
- generateOptions.nodeVersion = await getLatestNodeVersion();
1105
- }
1106
- const versions = {};
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) {
1118
- versionPromises.push(
1119
- getLatestNpmVersion("vite", "6.3.4").then((v) => {
1120
- versions.vite = v;
1121
- })
1122
- );
1123
- }
1124
- const linter = generateOptions.linter ?? "oxlint";
1125
- if (linter === "eslint") {
1126
- versionPromises.push(
1127
- getLatestNpmVersion("eslint", "9.17.0").then((v) => {
1128
- versions.eslint = v;
1129
- })
1130
- );
1131
- } else if (linter === "oxlint") {
1132
- versionPromises.push(
1133
- getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
1134
- versions.oxlint = v;
1135
- })
1136
- );
1137
- } else if (linter === "biome") {
1138
- versionPromises.push(
1139
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1140
- versions.biome = v;
1141
- })
1142
- );
1143
- }
1144
- const formatter = generateOptions.formatter ?? "oxfmt";
1145
- if (formatter === "prettier") {
1146
- versionPromises.push(
1147
- getLatestNpmVersion("prettier", "3.4.2").then((v) => {
1148
- versions.prettier = v;
1149
- })
1150
- );
1151
- } else if (formatter === "oxfmt") {
1152
- versionPromises.push(
1153
- getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
1154
- versions.oxfmt = v;
1155
- })
1156
- );
1157
- } else if (formatter === "biome" && linter !== "biome") {
1158
- versionPromises.push(
1159
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1160
- versions.biome = v;
1161
- })
1162
- );
1163
- }
1164
- await Promise.all(versionPromises);
1165
- generateOptions.versions = versions;
1166
- const basePath = join(cwd(), generateOptions.name);
1167
- const s = p.spinner();
1168
- s.start("Creating project...");
1169
- try {
1170
- const files = generate(generateOptions);
1171
- const filePaths = Object.keys(files).sort();
1172
- for (const filePath of filePaths) {
1173
- const fullFilePath = join(basePath, filePath);
1174
- await mkdir(dirname(fullFilePath), { recursive: true });
1175
- const file = files[filePath];
1176
- if (file.type === "text") {
1177
- await writeFile(fullFilePath, file.content);
1178
- } else {
1179
- const response = await fetch(file.url);
1180
- await writeFile(fullFilePath, response.body);
1181
- }
1182
- }
1183
- s.stop(color.green.inverse(" \u2713 Project created! "));
1184
- const isLibrary2 = generateOptions.projectType === "library";
1185
- const nextSteps = isLibrary2 ? [
1186
- `cd ${generateOptions.name}`,
1187
- `${packageManager} install`,
1188
- `${packageManager} run build`
1189
- ].join("\n") : [
1190
- `cd ${generateOptions.name}`,
1191
- `${packageManager} install`,
1192
- `${packageManager} run dev`
1193
- ].join("\n");
1194
- p.note(nextSteps, "Next steps");
1195
- const savedEditor = getPreferredEditor();
1196
- let selectedEditor;
1197
- if (savedEditor && savedEditor !== "skip") {
1198
- const useDefault = await p.confirm({
1199
- message: `Open in editor? ${color.dim(`(${editorNames[savedEditor]})`)}`,
1200
- initialValue: true
1201
- });
1202
- if (p.isCancel(useDefault)) {
1203
- selectedEditor = void 0;
1204
- } else if (useDefault) {
1205
- selectedEditor = savedEditor;
1206
- } else {
1207
- selectedEditor = "skip";
1208
- }
1209
- } else {
1210
- const openEditor = await p.select({
1211
- message: "Open project in editor?",
1212
- options: [
1213
- { value: "skip", label: "Skip" },
1214
- { value: "cursor", label: "Cursor" },
1215
- { value: "code", label: "VS Code" },
1216
- { value: "webstorm", label: "WebStorm" }
1217
- ],
1218
- initialValue: "skip"
1219
- });
1220
- if (!p.isCancel(openEditor)) {
1221
- selectedEditor = openEditor;
1222
- const saveChoice = await p.confirm({
1223
- message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
1224
- initialValue: true
1225
- });
1226
- if (!p.isCancel(saveChoice) && saveChoice) {
1227
- setPreferredEditor(selectedEditor);
1228
- if (selectedEditor === "cursor" || selectedEditor === "code") {
1229
- const reuseChoice = await p.confirm({
1230
- message: "Reuse current window when opening projects?",
1231
- initialValue: false
1232
- });
1233
- if (!p.isCancel(reuseChoice)) {
1234
- setReuseWindow(reuseChoice);
1235
- }
1236
- }
1237
- }
1238
- }
1239
- }
1240
- if (selectedEditor && selectedEditor !== "skip") {
1241
- try {
1242
- await openInEditor(
1243
- selectedEditor,
1244
- basePath,
1245
- getReuseWindow()
1246
- );
1247
- p.log.success(`Opening in ${editorNames[selectedEditor]}...`);
1248
- } catch {
1249
- p.log.warn(
1250
- `Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
1251
- );
1252
- }
1253
- }
1254
- p.outro(color.green("Happy coding! \u2728"));
1255
- } catch (error) {
1256
- s.stop("Failed to create project");
1257
- p.log.error(String(error));
1258
- process.exit(1);
2009
+ await handleMonorepoCreation(generateOptions);
2010
+ } else {
2011
+ await handleStandaloneProjectCreation(generateOptions);
1259
2012
  }
1260
2013
  });
1261
2014
  await program.parseAsync();