create-krispya 0.5.1 → 0.5.2

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