create-krispya 0.5.0 → 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.
@@ -146,10 +146,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
146
146
  const tsConfig = {
147
147
  $schema: "https://json.schemastore.org/tsconfig",
148
148
  files: [],
149
- references: [
150
- { path: "./.config/tsconfig.app.json" },
151
- { path: "./.config/tsconfig.node.json" }
152
- ]
149
+ references: [{ path: "./.config/tsconfig.app.json" }, { path: "./.config/tsconfig.node.json" }]
153
150
  };
154
151
  files["tsconfig.json"] = {
155
152
  type: "text",
@@ -210,7 +207,8 @@ function generatePackageJson(params) {
210
207
  devDependencies,
211
208
  peerDependencies,
212
209
  scripts,
213
- options
210
+ options,
211
+ workspaceDependencies
214
212
  } = params;
215
213
  const files = {};
216
214
  const packageManager = options.packageManager ?? "pnpm";
@@ -236,8 +234,14 @@ function generatePackageJson(params) {
236
234
  packageJson.files = ["dist"];
237
235
  }
238
236
  const sortKeys = (obj) => Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
237
+ const allDependencies = { ...dependencies };
238
+ if (workspaceDependencies && workspaceDependencies.length > 0) {
239
+ for (const pkgName of workspaceDependencies) {
240
+ allDependencies[pkgName] = "workspace:*";
241
+ }
242
+ }
239
243
  packageJson.scripts = scripts;
240
- packageJson.dependencies = sortKeys(dependencies);
244
+ packageJson.dependencies = sortKeys(allDependencies);
241
245
  if (Object.keys(devDependencies).length > 0) {
242
246
  packageJson.devDependencies = sortKeys(devDependencies);
243
247
  }
@@ -252,6 +256,16 @@ function generatePackageJson(params) {
252
256
  const majorVersion = pnpmVersion.split(".")[0];
253
257
  engines.pnpm = `>=${majorVersion}.0.0`;
254
258
  packageJson.packageManager = `pnpm@${pnpmVersion}`;
259
+ } else if (packageManager === "yarn") {
260
+ const yarnVersion = options.yarnVersion ?? "4.6.0";
261
+ const majorVersion = yarnVersion.split(".")[0];
262
+ engines.yarn = `>=${majorVersion}.0.0`;
263
+ packageJson.packageManager = `yarn@${yarnVersion}`;
264
+ } else if (packageManager === "npm") {
265
+ const npmVersion = options.npmVersion ?? "11.0.0";
266
+ const majorVersion = npmVersion.split(".")[0];
267
+ engines.npm = `>=${majorVersion}.0.0`;
268
+ packageJson.packageManager = `npm@${npmVersion}`;
255
269
  }
256
270
  if (options.nodeVersion) {
257
271
  const majorVersion = options.nodeVersion.split(".")[0];
@@ -281,14 +295,7 @@ function generatePackageJson(params) {
281
295
  }
282
296
 
283
297
  function generateReadme(params) {
284
- const {
285
- name,
286
- baseTemplate,
287
- isLibrary,
288
- libraryBundler,
289
- packageManager,
290
- codeSnippets
291
- } = params;
298
+ const { name, baseTemplate, isLibrary, libraryBundler, packageManager, codeSnippets } = params;
292
299
  const isVanilla = baseTemplate === "vanilla";
293
300
  const isReact = baseTemplate === "react";
294
301
  const isR3f = baseTemplate === "r3f";
@@ -652,168 +659,128 @@ function generateViteConfig(params) {
652
659
  return { type: "text", content: viteConfigContent };
653
660
  }
654
661
 
655
- function generateMonorepo(params) {
656
- const {
662
+ function generateAiFiles(files, params) {
663
+ const { name, packageManager, linter, formatter, aiFiles } = params;
664
+ const content = getAiInstructionsContent({
657
665
  name,
658
- linter,
659
- formatter,
660
666
  packageManager,
661
- pnpmVersion,
662
- pnpmManageVersions,
663
- nodeVersion
664
- } = params;
665
- const files = {};
666
- const isPnpm = packageManager === "pnpm";
667
- const devDependencies = {};
668
- if (linter === "oxlint") {
669
- devDependencies["oxlint"] = "^1.36.0";
670
- } else if (linter === "eslint") {
671
- devDependencies["eslint"] = "^9.17.0";
672
- } else if (linter === "biome") {
673
- devDependencies["@biomejs/biome"] = "^1.9.4";
674
- }
675
- if (formatter === "oxfmt") {
676
- devDependencies["oxfmt"] = "^0.21.0";
677
- } else if (formatter === "prettier") {
678
- devDependencies["prettier"] = "^3.4.2";
679
- }
680
- const rootPackageJson = {
681
- name: "root",
682
- version: "0.0.0",
683
- private: true,
684
- type: "module",
685
- scripts: {
686
- dev: "pnpm --filter './apps/*' run dev",
687
- build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
688
- test: "pnpm -r run test",
689
- lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
690
- format: formatter === "oxfmt" ? "oxfmt ." : formatter === "biome" ? "biome format . --write" : "prettier --write ."
691
- },
692
- devDependencies
693
- };
694
- const engines = {};
695
- if (isPnpm && pnpmVersion) {
696
- const majorVersion = pnpmVersion.split(".")[0];
697
- engines.pnpm = `>=${majorVersion}.0.0`;
698
- rootPackageJson.packageManager = `pnpm@${pnpmVersion}`;
699
- }
700
- if (nodeVersion) {
701
- const majorVersion = nodeVersion.split(".")[0];
702
- engines.node = `>=${majorVersion}.0.0`;
703
- }
704
- if (Object.keys(engines).length > 0) {
705
- rootPackageJson.engines = engines;
706
- }
707
- files["package.json"] = {
708
- type: "text",
709
- content: JSON.stringify(rootPackageJson, null, 2)
710
- };
711
- if (isPnpm) {
712
- const workspaceLines = [];
713
- if (pnpmManageVersions) {
714
- workspaceLines.push("manage-package-manager-versions: true", "");
667
+ linter,
668
+ formatter
669
+ });
670
+ for (const fileChoice of aiFiles) {
671
+ switch (fileChoice) {
672
+ case "cursor-rules":
673
+ files[".cursor/rules"] = { type: "text", content };
674
+ break;
675
+ case "agents-md":
676
+ files["AGENTS.md"] = { type: "text", content };
677
+ break;
678
+ case "claude-md":
679
+ files["CLAUDE.md"] = { type: "text", content };
680
+ break;
681
+ case "copilot-md":
682
+ files[".github/copilot-instructions.md"] = { type: "text", content };
683
+ break;
715
684
  }
716
- workspaceLines.push(
717
- "packages:",
718
- ' - ".config/*"',
719
- ' - "apps/*"',
720
- ' - "packages/*"',
721
- ""
722
- );
723
- workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
724
- files["pnpm-workspace.yaml"] = {
725
- type: "text",
726
- content: workspaceLines.join("\n")
727
- };
728
685
  }
729
- generateTypescriptConfigPackage(files);
730
- if (linter === "oxlint") {
731
- generateOxlintConfigPackage(files);
732
- } else if (linter === "eslint") {
733
- files["eslint.config.js"] = {
734
- type: "text",
735
- content: `export default [
736
- // Add your ESLint rules here
737
- ];
738
- `
739
- };
740
- } else if (linter === "biome") {
741
- const biomeConfig = {
742
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
743
- vcs: {
744
- enabled: true,
745
- clientKind: "git",
746
- useIgnoreFile: true
747
- },
748
- linter: {
749
- enabled: true,
750
- rules: {
751
- recommended: true
752
- }
753
- },
754
- formatter: {
755
- enabled: formatter === "biome"
756
- }
757
- };
758
- files["biome.json"] = {
759
- type: "text",
760
- content: JSON.stringify(biomeConfig, null, 2)
761
- };
686
+ }
687
+ function getConfigPackagesDescription(linter, formatter) {
688
+ const packages = ["`@config/typescript`"];
689
+ if (linter !== "biome") {
690
+ packages.push(`\`@config/${linter}\``);
762
691
  }
763
- if (formatter === "oxfmt") {
764
- generateOxfmtConfigPackage(files);
765
- } else if (formatter === "prettier") {
766
- const prettierConfig = {
767
- semi: true,
768
- singleQuote: false,
769
- trailingComma: "es5",
770
- printWidth: 100
771
- };
772
- files[".prettierrc.json"] = {
773
- type: "text",
774
- content: JSON.stringify(prettierConfig, null, 2)
775
- };
692
+ if (formatter !== "biome" && formatter !== linter) {
693
+ packages.push(`\`@config/${formatter}\``);
776
694
  }
777
- files[".gitignore"] = {
778
- type: "text",
779
- content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
780
- };
781
- files[".gitattributes"] = {
782
- type: "text",
783
- content: `* text=auto eol=lf
784
- *.{cmd,[cC][mM][dD]} text eol=crlf
785
- *.{bat,[bB][aA][tT]} text eol=crlf
786
- `
787
- };
788
- generateVscodeFiles(files, linter, formatter);
789
- files["README.md"] = {
790
- type: "text",
791
- content: `# ${name}
695
+ let description = `- \`.config/\`: shared config packages (${packages.join(", ")})`;
696
+ if (linter === "biome" || formatter === "biome") {
697
+ description += "\n- `biome.json`: Biome configuration (root level)";
698
+ }
699
+ return description;
700
+ }
701
+ function getAiInstructionsContent(params) {
702
+ const { name, packageManager, linter, formatter } = params;
703
+ const configDescription = getConfigPackagesDescription(linter, formatter);
704
+ return `# ${name}
792
705
 
793
- This monorepo workspace was generated with create-krispya.
706
+ This is a pnpm monorepo workspace generated with \`create-krispya\`.
794
707
 
795
- ## Structure
708
+ ## Most important rule (package creation)
796
709
 
797
- - \`apps/\` - Applications
798
- - \`packages/\` - Shared packages and libraries
799
- - \`.config/\` - Shared configuration packages
710
+ If you need a new app/package for any reason, **ALWAYS** create it with \`create-krispya\` (do not hand-create folders/package.json).
800
711
 
801
- ## Development Commands
712
+ ### Non-interactive (preferred for agents)
802
713
 
803
- - \`${packageManager} install\` to install all dependencies
804
- - \`${packageManager} run dev\` to run all applications in development mode
805
- - \`${packageManager} run build\` to build all packages and applications
806
- - \`${packageManager} run test\` to run tests across the workspace
807
- - \`${packageManager} run lint\` to lint all code
808
- - \`${packageManager} run format\` to format all code
714
+ \`\`\`bash
715
+ ${packageManager} create krispya <name> --workspace [options]
716
+ \`\`\`
809
717
 
810
- ## Adding Packages
718
+ - The package directory will be \`apps/<name>\` (apps) or \`packages/<name>\` (libraries), unless \`--dir\` is provided.
719
+ - Package names default to \`@${name}/<name>\` but you can pass any name (scoped or unscoped).
811
720
 
812
- To add a new package to this workspace, run create-krispya from this directory and it will detect the monorepo.
813
- `
814
- };
815
- return { files };
721
+ ### Workspace maintenance (non-interactive)
722
+
723
+ \`\`\`bash
724
+ ${packageManager} create krispya --check # validate workspace
725
+ ${packageManager} create krispya --fix --linter ${linter} --formatter ${formatter} # fix missing .config packages
726
+ \`\`\`
727
+
728
+ For non-interactive \`--fix\`, you MUST provide both \`--linter\` and \`--formatter\` flags.
729
+
730
+ ## Package creation options (CLI truth)
731
+
732
+ | Option | Values | Notes |
733
+ |--------|--------|-------|
734
+ | \`--type\` | app, library | default: app |
735
+ | \`--template\` | vanilla, vanilla-js, react, react-js, r3f, r3f-js | default: vanilla |
736
+ | \`--dir\` | any directory | requires \`--workspace\`; default: \`apps/\` or \`packages/\` |
737
+ | \`--bundler\` | unbuild, tsdown | libraries only; default: unbuild |
738
+
739
+ ### R3F flags (r3f templates only)
740
+
741
+ \`--drei\` \`--handle\` \`--leva\` \`--postprocessing\` \`--rapier\` \`--xr\` \`--uikit\` \`--offscreen\` \`--zustand\` \`--koota\` \`--triplex\` \`--viverse\`
742
+
743
+ ### Examples
744
+
745
+ \`\`\`bash
746
+ # React library (@${name}/ui) in packages/ui
747
+ ${packageManager} create krispya ui --workspace --type library --template react
748
+
749
+ # R3F app with physics + controls (@${name}/game) in apps/game
750
+ ${packageManager} create krispya game --workspace --type app --template r3f --drei --rapier --leva
751
+
752
+ # App in a custom directory
753
+ ${packageManager} create krispya demo --workspace --type app --template react --dir examples
754
+ \`\`\`
755
+
756
+ ## After creating a package
757
+
758
+ \`\`\`bash
759
+ ${packageManager} install
760
+ \`\`\`
761
+
762
+ - Use \`"workspace:*"\` for internal deps (e.g. \`"@${name}/ui": "workspace:*"\`).
763
+
764
+ ## Workspace commands
765
+
766
+ \`\`\`bash
767
+ ${packageManager} install # Install all dependencies
768
+ ${packageManager} run dev # Run all apps in dev mode
769
+ ${packageManager} run build # Build packages then apps
770
+ ${packageManager} run test # Run all tests
771
+ ${packageManager} run lint # Lint with ${linter}
772
+ ${packageManager} run format # Format with ${formatter}
773
+ \`\`\`
774
+
775
+ ## Structure + conventions
776
+
777
+ - \`apps/\`: applications (\`--type app\`)
778
+ - \`packages/\`: libraries (\`--type library\`)
779
+ ${configDescription}
780
+ - TS configs extend \`@config/typescript/*\` (base/app/node/react)
781
+ `;
816
782
  }
783
+
817
784
  function generateTypescriptConfigPackage(files) {
818
785
  const basePath = ".config/typescript";
819
786
  files[`${basePath}/package.json`] = {
@@ -892,123 +859,504 @@ In your package's \`tsconfig.json\`:
892
859
  };
893
860
  files[`${basePath}/node.json`] = {
894
861
  type: "text",
895
- content: JSON.stringify(
896
- {
897
- $schema: "https://json.schemastore.org/tsconfig",
898
- extends: "./base.json",
899
- compilerOptions: {
900
- lib: ["ESNext"]
901
- }
902
- },
903
- null,
904
- 2
905
- )
862
+ content: JSON.stringify(
863
+ {
864
+ $schema: "https://json.schemastore.org/tsconfig",
865
+ extends: "./base.json",
866
+ compilerOptions: {
867
+ lib: ["ESNext"]
868
+ }
869
+ },
870
+ null,
871
+ 2
872
+ )
873
+ };
874
+ files[`${basePath}/react.json`] = {
875
+ type: "text",
876
+ content: JSON.stringify(
877
+ {
878
+ $schema: "https://json.schemastore.org/tsconfig",
879
+ extends: "./app.json",
880
+ compilerOptions: {
881
+ jsx: "react-jsx"
882
+ }
883
+ },
884
+ null,
885
+ 2
886
+ )
887
+ };
888
+ }
889
+ function generateOxlintConfigPackage(files) {
890
+ const basePath = ".config/oxlint";
891
+ const { rules } = defaultLinterConfig;
892
+ files[`${basePath}/package.json`] = {
893
+ type: "text",
894
+ content: JSON.stringify(
895
+ {
896
+ name: "@config/oxlint",
897
+ version: "0.1.0",
898
+ private: true,
899
+ files: ["base.json", "react.json"]
900
+ },
901
+ null,
902
+ 2
903
+ )
904
+ };
905
+ files[`${basePath}/README.md`] = {
906
+ type: "text",
907
+ content: `# \`@config/oxlint\`
908
+
909
+ Shared oxlint configurations for the monorepo.
910
+
911
+ ## Usage
912
+
913
+ Run oxlint with a config:
914
+
915
+ \`\`\`bash
916
+ oxlint -c node_modules/@config/oxlint/base.json
917
+ \`\`\`
918
+
919
+ ## Available Configs
920
+
921
+ - \`base.json\` - Base linting rules for TypeScript projects
922
+ - \`react.json\` - Extends base with React-specific rules
923
+ `
924
+ };
925
+ files[`${basePath}/base.json`] = {
926
+ type: "text",
927
+ content: JSON.stringify(
928
+ {
929
+ $schema: "./node_modules/oxlint/configuration_schema.json",
930
+ plugins: ["unicorn", "typescript", "oxc"],
931
+ rules: {
932
+ "no-unused-vars": [
933
+ rules.noUnusedVars.level,
934
+ {
935
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
936
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
937
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
938
+ }
939
+ ],
940
+ "no-useless-escape": "off",
941
+ "no-unused-expressions": [
942
+ rules.noUnusedExpressions.level,
943
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
944
+ ]
945
+ },
946
+ ignorePatterns: defaultLinterConfig.ignorePatterns
947
+ },
948
+ null,
949
+ 2
950
+ )
951
+ };
952
+ files[`${basePath}/react.json`] = {
953
+ type: "text",
954
+ content: JSON.stringify(
955
+ {
956
+ $schema: "./node_modules/oxlint/configuration_schema.json",
957
+ plugins: ["unicorn", "typescript", "oxc", "react"],
958
+ rules: {
959
+ "no-unused-vars": [
960
+ rules.noUnusedVars.level,
961
+ {
962
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
963
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
964
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
965
+ }
966
+ ],
967
+ "no-useless-escape": "off",
968
+ "no-unused-expressions": [
969
+ rules.noUnusedExpressions.level,
970
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
971
+ ]
972
+ },
973
+ ignorePatterns: defaultLinterConfig.ignorePatterns
974
+ },
975
+ null,
976
+ 2
977
+ )
978
+ };
979
+ }
980
+ function generateEslintConfigPackage(files) {
981
+ const basePath = ".config/eslint";
982
+ files[`${basePath}/package.json`] = {
983
+ type: "text",
984
+ content: JSON.stringify(
985
+ {
986
+ name: "@config/eslint",
987
+ version: "0.1.0",
988
+ private: true,
989
+ type: "module",
990
+ exports: {
991
+ "./base": "./base.js",
992
+ "./react": "./react.js"
993
+ },
994
+ files: ["base.js", "react.js"],
995
+ devDependencies: {
996
+ "@eslint/js": "^9.17.0",
997
+ "typescript-eslint": "^8.18.0"
998
+ }
999
+ },
1000
+ null,
1001
+ 2
1002
+ )
1003
+ };
1004
+ files[`${basePath}/README.md`] = {
1005
+ type: "text",
1006
+ content: `# \`@config/eslint\`
1007
+
1008
+ Shared ESLint configurations for the monorepo.
1009
+
1010
+ ## Usage
1011
+
1012
+ In your package's \`eslint.config.js\`:
1013
+
1014
+ \`\`\`js
1015
+ import base from "@config/eslint/base";
1016
+
1017
+ export default [...base];
1018
+ \`\`\`
1019
+
1020
+ Or for React projects:
1021
+
1022
+ \`\`\`js
1023
+ import react from "@config/eslint/react";
1024
+
1025
+ export default [...react];
1026
+ \`\`\`
1027
+
1028
+ ## Available Configs
1029
+
1030
+ - \`base\` - Base linting rules for TypeScript projects
1031
+ - \`react\` - Extends base with React-specific rules
1032
+ `
1033
+ };
1034
+ files[`${basePath}/base.js`] = {
1035
+ type: "text",
1036
+ content: `import js from "@eslint/js";
1037
+ import tseslint from "typescript-eslint";
1038
+
1039
+ export default tseslint.config(
1040
+ js.configs.recommended,
1041
+ ...tseslint.configs.recommended,
1042
+ {
1043
+ rules: {
1044
+ "@typescript-eslint/no-unused-vars": [
1045
+ "error",
1046
+ {
1047
+ argsIgnorePattern: "^_",
1048
+ varsIgnorePattern: "^_",
1049
+ caughtErrorsIgnorePattern: "^_",
1050
+ },
1051
+ ],
1052
+ },
1053
+ },
1054
+ {
1055
+ ignores: ["dist/**", "node_modules/**"],
1056
+ }
1057
+ );
1058
+ `
1059
+ };
1060
+ files[`${basePath}/react.js`] = {
1061
+ type: "text",
1062
+ content: `import js from "@eslint/js";
1063
+ import tseslint from "typescript-eslint";
1064
+
1065
+ export default tseslint.config(
1066
+ js.configs.recommended,
1067
+ ...tseslint.configs.recommended,
1068
+ {
1069
+ rules: {
1070
+ "@typescript-eslint/no-unused-vars": [
1071
+ "error",
1072
+ {
1073
+ argsIgnorePattern: "^_",
1074
+ varsIgnorePattern: "^_",
1075
+ caughtErrorsIgnorePattern: "^_",
1076
+ },
1077
+ ],
1078
+ },
1079
+ },
1080
+ {
1081
+ ignores: ["dist/**", "node_modules/**"],
1082
+ }
1083
+ );
1084
+ `
1085
+ };
1086
+ }
1087
+ function generatePrettierConfigPackage(files) {
1088
+ const basePath = ".config/prettier";
1089
+ files[`${basePath}/package.json`] = {
1090
+ type: "text",
1091
+ content: JSON.stringify(
1092
+ {
1093
+ name: "@config/prettier",
1094
+ version: "0.1.0",
1095
+ private: true,
1096
+ type: "module",
1097
+ exports: {
1098
+ ".": "./base.json"
1099
+ },
1100
+ files: ["base.json"]
1101
+ },
1102
+ null,
1103
+ 2
1104
+ )
1105
+ };
1106
+ files[`${basePath}/README.md`] = {
1107
+ type: "text",
1108
+ content: `# \`@config/prettier\`
1109
+
1110
+ Shared Prettier configuration for the monorepo.
1111
+
1112
+ ## Usage
1113
+
1114
+ In your package's \`package.json\`:
1115
+
1116
+ \`\`\`json
1117
+ {
1118
+ "prettier": "@config/prettier"
1119
+ }
1120
+ \`\`\`
1121
+
1122
+ Or in \`.prettierrc.json\`:
1123
+
1124
+ \`\`\`json
1125
+ "@config/prettier"
1126
+ \`\`\`
1127
+
1128
+ ## Available Configs
1129
+
1130
+ - Default export - Base formatter settings
1131
+ `
1132
+ };
1133
+ files[`${basePath}/base.json`] = {
1134
+ type: "text",
1135
+ content: JSON.stringify(
1136
+ {
1137
+ printWidth: defaultFormatterConfig.printWidth,
1138
+ tabWidth: defaultFormatterConfig.tabWidth,
1139
+ useTabs: defaultFormatterConfig.useTabs,
1140
+ semi: defaultFormatterConfig.semi,
1141
+ singleQuote: defaultFormatterConfig.singleQuote,
1142
+ trailingComma: defaultFormatterConfig.trailingComma,
1143
+ bracketSpacing: defaultFormatterConfig.bracketSpacing,
1144
+ arrowParens: defaultFormatterConfig.arrowParens
1145
+ },
1146
+ null,
1147
+ 2
1148
+ )
1149
+ };
1150
+ }
1151
+ function generateOxfmtConfigPackage(files) {
1152
+ const basePath = ".config/oxfmt";
1153
+ files[`${basePath}/package.json`] = {
1154
+ type: "text",
1155
+ content: JSON.stringify(
1156
+ {
1157
+ name: "@config/oxfmt",
1158
+ version: "0.1.0",
1159
+ private: true,
1160
+ files: ["base.json"]
1161
+ },
1162
+ null,
1163
+ 2
1164
+ )
1165
+ };
1166
+ files[`${basePath}/README.md`] = {
1167
+ type: "text",
1168
+ content: `# \`@config/oxfmt\`
1169
+
1170
+ Shared oxfmt (formatter) configuration for the monorepo.
1171
+
1172
+ ## Usage
1173
+
1174
+ Run oxfmt with the config:
1175
+
1176
+ \`\`\`bash
1177
+ oxfmt -c node_modules/@config/oxfmt/base.json --write .
1178
+ \`\`\`
1179
+
1180
+ ## Available Configs
1181
+
1182
+ - \`base.json\` - Base formatter settings (Prettier-compatible)
1183
+ `
1184
+ };
1185
+ files[`${basePath}/base.json`] = {
1186
+ type: "text",
1187
+ content: JSON.stringify(
1188
+ {
1189
+ printWidth: defaultFormatterConfig.printWidth,
1190
+ tabWidth: defaultFormatterConfig.tabWidth,
1191
+ useTabs: defaultFormatterConfig.useTabs,
1192
+ semi: defaultFormatterConfig.semi,
1193
+ singleQuote: defaultFormatterConfig.singleQuote,
1194
+ trailingComma: defaultFormatterConfig.trailingComma,
1195
+ bracketSpacing: defaultFormatterConfig.bracketSpacing,
1196
+ arrowParens: defaultFormatterConfig.arrowParens
1197
+ },
1198
+ null,
1199
+ 2
1200
+ )
1201
+ };
1202
+ }
1203
+
1204
+ function generateMonorepo(params) {
1205
+ const {
1206
+ name,
1207
+ linter,
1208
+ formatter,
1209
+ packageManager,
1210
+ pnpmVersion,
1211
+ pnpmManageVersions,
1212
+ nodeVersion,
1213
+ aiFiles
1214
+ } = params;
1215
+ const files = {};
1216
+ const isPnpm = packageManager === "pnpm";
1217
+ const devDependencies = {};
1218
+ if (linter === "oxlint") {
1219
+ devDependencies["oxlint"] = "^1.36.0";
1220
+ } else if (linter === "eslint") {
1221
+ devDependencies["eslint"] = "^9.17.0";
1222
+ } else if (linter === "biome") {
1223
+ devDependencies["@biomejs/biome"] = "^1.9.4";
1224
+ }
1225
+ if (formatter === "oxfmt") {
1226
+ devDependencies["oxfmt"] = "^0.21.0";
1227
+ } else if (formatter === "prettier") {
1228
+ devDependencies["prettier"] = "^3.4.2";
1229
+ }
1230
+ const rootPackageJson = {
1231
+ name: "root",
1232
+ version: "0.0.0",
1233
+ private: true,
1234
+ type: "module",
1235
+ scripts: {
1236
+ dev: "pnpm --filter './apps/*' run dev",
1237
+ build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1238
+ test: "pnpm -r run test",
1239
+ lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
1240
+ format: formatter === "oxfmt" ? "oxfmt ." : formatter === "biome" ? "biome format . --write" : "prettier --write ."
1241
+ },
1242
+ devDependencies
1243
+ };
1244
+ const engines = {};
1245
+ if (isPnpm && pnpmVersion) {
1246
+ const majorVersion = pnpmVersion.split(".")[0];
1247
+ engines.pnpm = `>=${majorVersion}.0.0`;
1248
+ rootPackageJson.packageManager = `pnpm@${pnpmVersion}`;
1249
+ }
1250
+ if (nodeVersion) {
1251
+ const majorVersion = nodeVersion.split(".")[0];
1252
+ engines.node = `>=${majorVersion}.0.0`;
1253
+ }
1254
+ if (Object.keys(engines).length > 0) {
1255
+ rootPackageJson.engines = engines;
1256
+ }
1257
+ files["package.json"] = {
1258
+ type: "text",
1259
+ content: JSON.stringify(rootPackageJson, null, 2)
906
1260
  };
907
- files[`${basePath}/react.json`] = {
908
- type: "text",
909
- content: JSON.stringify(
910
- {
911
- $schema: "https://json.schemastore.org/tsconfig",
912
- extends: "./app.json",
913
- compilerOptions: {
914
- jsx: "react-jsx"
1261
+ if (isPnpm) {
1262
+ const workspaceLines = [];
1263
+ if (pnpmManageVersions) {
1264
+ workspaceLines.push("manage-package-manager-versions: true", "");
1265
+ }
1266
+ workspaceLines.push(
1267
+ "packages:",
1268
+ ' - ".config/*"',
1269
+ ' - "apps/*"',
1270
+ ' - "packages/*"',
1271
+ ""
1272
+ );
1273
+ workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
1274
+ files["pnpm-workspace.yaml"] = {
1275
+ type: "text",
1276
+ content: workspaceLines.join("\n")
1277
+ };
1278
+ }
1279
+ generateTypescriptConfigPackage(files);
1280
+ if (linter === "oxlint") {
1281
+ generateOxlintConfigPackage(files);
1282
+ } else if (linter === "eslint") {
1283
+ generateEslintConfigPackage(files);
1284
+ } else if (linter === "biome") {
1285
+ const biomeConfig = {
1286
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1287
+ vcs: {
1288
+ enabled: true,
1289
+ clientKind: "git",
1290
+ useIgnoreFile: true
1291
+ },
1292
+ linter: {
1293
+ enabled: true,
1294
+ rules: {
1295
+ recommended: true
915
1296
  }
916
1297
  },
917
- null,
918
- 2
919
- )
1298
+ formatter: {
1299
+ enabled: formatter === "biome"
1300
+ }
1301
+ };
1302
+ files["biome.json"] = {
1303
+ type: "text",
1304
+ content: JSON.stringify(biomeConfig, null, 2)
1305
+ };
1306
+ }
1307
+ if (formatter === "oxfmt") {
1308
+ generateOxfmtConfigPackage(files);
1309
+ } else if (formatter === "prettier") {
1310
+ generatePrettierConfigPackage(files);
1311
+ }
1312
+ files[".gitignore"] = {
1313
+ type: "text",
1314
+ content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
920
1315
  };
921
- }
922
- function generateOxlintConfigPackage(files) {
923
- const basePath = ".config/oxlint";
924
- const { rules } = defaultLinterConfig;
925
- files[`${basePath}/package.json`] = {
1316
+ files[".gitattributes"] = {
926
1317
  type: "text",
927
- content: JSON.stringify(
928
- {
929
- name: "@config/oxlint",
930
- version: "0.1.0",
931
- private: true,
932
- files: ["base.json", "react.json"]
933
- },
934
- null,
935
- 2
936
- )
1318
+ content: `* text=auto eol=lf
1319
+ *.{cmd,[cC][mM][dD]} text eol=crlf
1320
+ *.{bat,[bB][aA][tT]} text eol=crlf
1321
+ `
937
1322
  };
938
- files[`${basePath}/README.md`] = {
1323
+ generateVscodeFiles(files, linter, formatter);
1324
+ files["README.md"] = {
939
1325
  type: "text",
940
- content: `# \`@config/oxlint\`
1326
+ content: `# ${name}
941
1327
 
942
- Shared oxlint configurations for the monorepo.
1328
+ This monorepo workspace was generated with create-krispya.
943
1329
 
944
- ## Usage
1330
+ ## Structure
945
1331
 
946
- Run oxlint with a config:
1332
+ - \`apps/\` - Applications
1333
+ - \`packages/\` - Shared packages and libraries
1334
+ - \`.config/\` - Shared configuration packages
947
1335
 
948
- \`\`\`bash
949
- oxlint -c node_modules/@config/oxlint/base.json
950
- \`\`\`
1336
+ ## Development Commands
951
1337
 
952
- ## Available Configs
1338
+ - \`${packageManager} install\` to install all dependencies
1339
+ - \`${packageManager} run dev\` to run all applications in development mode
1340
+ - \`${packageManager} run build\` to build all packages and applications
1341
+ - \`${packageManager} run test\` to run tests across the workspace
1342
+ - \`${packageManager} run lint\` to lint all code
1343
+ - \`${packageManager} run format\` to format all code
953
1344
 
954
- - \`base.json\` - Base linting rules for TypeScript projects
955
- - \`react.json\` - Extends base with React-specific rules
1345
+ ## Adding Packages
1346
+
1347
+ To add a new package to this workspace, run create-krispya from this directory and it will detect the monorepo.
956
1348
  `
957
1349
  };
958
- files[`${basePath}/base.json`] = {
959
- type: "text",
960
- content: JSON.stringify(
961
- {
962
- $schema: "./node_modules/oxlint/configuration_schema.json",
963
- plugins: ["unicorn", "typescript", "oxc"],
964
- rules: {
965
- "no-unused-vars": [
966
- rules.noUnusedVars.level,
967
- {
968
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
969
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
970
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
971
- }
972
- ],
973
- "no-useless-escape": "off",
974
- "no-unused-expressions": [
975
- rules.noUnusedExpressions.level,
976
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
977
- ]
978
- },
979
- ignorePatterns: defaultLinterConfig.ignorePatterns
980
- },
981
- null,
982
- 2
983
- )
984
- };
985
- files[`${basePath}/react.json`] = {
986
- type: "text",
987
- content: JSON.stringify(
988
- {
989
- $schema: "./node_modules/oxlint/configuration_schema.json",
990
- plugins: ["unicorn", "typescript", "oxc", "react"],
991
- rules: {
992
- "no-unused-vars": [
993
- rules.noUnusedVars.level,
994
- {
995
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
996
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
997
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
998
- }
999
- ],
1000
- "no-useless-escape": "off",
1001
- "no-unused-expressions": [
1002
- rules.noUnusedExpressions.level,
1003
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1004
- ]
1005
- },
1006
- ignorePatterns: defaultLinterConfig.ignorePatterns
1007
- },
1008
- null,
1009
- 2
1010
- )
1011
- };
1350
+ if (aiFiles && aiFiles.length > 0) {
1351
+ generateAiFiles(files, {
1352
+ name,
1353
+ packageManager,
1354
+ linter,
1355
+ formatter,
1356
+ aiFiles
1357
+ });
1358
+ }
1359
+ return { files };
1012
1360
  }
1013
1361
  function generateVscodeFiles(files, linter, formatter) {
1014
1362
  const recommendations = [];
@@ -1034,8 +1382,12 @@ function generateVscodeFiles(files, linter, formatter) {
1034
1382
  recommendations.push("oxc.oxc-vscode");
1035
1383
  }
1036
1384
  settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
1037
- settings["[json]"] = { "editor.defaultFormatter": "vscode.json-language-features" };
1038
- settings["[jsonc]"] = { "editor.defaultFormatter": "vscode.json-language-features" };
1385
+ settings["[json]"] = {
1386
+ "editor.defaultFormatter": "vscode.json-language-features"
1387
+ };
1388
+ settings["[jsonc]"] = {
1389
+ "editor.defaultFormatter": "vscode.json-language-features"
1390
+ };
1039
1391
  } else if (formatter === "prettier") {
1040
1392
  recommendations.push("esbenp.prettier-vscode");
1041
1393
  settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
@@ -1054,62 +1406,16 @@ function generateVscodeFiles(files, linter, formatter) {
1054
1406
  content: JSON.stringify(settings, null, " ")
1055
1407
  };
1056
1408
  }
1057
- function generateOxfmtConfigPackage(files) {
1058
- const basePath = ".config/oxfmt";
1059
- files[`${basePath}/package.json`] = {
1060
- type: "text",
1061
- content: JSON.stringify(
1062
- {
1063
- name: "@config/oxfmt",
1064
- version: "0.1.0",
1065
- private: true,
1066
- files: ["base.json"]
1067
- },
1068
- null,
1069
- 2
1070
- )
1071
- };
1072
- files[`${basePath}/README.md`] = {
1073
- type: "text",
1074
- content: `# \`@config/oxfmt\`
1075
-
1076
- Shared oxfmt (formatter) configuration for the monorepo.
1077
-
1078
- ## Usage
1079
-
1080
- Run oxfmt with the config:
1081
-
1082
- \`\`\`bash
1083
- oxfmt -c node_modules/@config/oxfmt/base.json --write .
1084
- \`\`\`
1085
-
1086
- ## Available Configs
1087
-
1088
- - \`base.json\` - Base formatter settings (Prettier-compatible)
1089
- `
1090
- };
1091
- files[`${basePath}/base.json`] = {
1092
- type: "text",
1093
- content: JSON.stringify(
1094
- {
1095
- printWidth: defaultFormatterConfig.printWidth,
1096
- tabWidth: defaultFormatterConfig.tabWidth,
1097
- useTabs: defaultFormatterConfig.useTabs,
1098
- semi: defaultFormatterConfig.semi,
1099
- singleQuote: defaultFormatterConfig.singleQuote,
1100
- trailingComma: defaultFormatterConfig.trailingComma,
1101
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
1102
- arrowParens: defaultFormatterConfig.arrowParens
1103
- },
1104
- null,
1105
- 2
1106
- )
1107
- };
1108
- }
1109
1409
 
1110
1410
  const monorepo = {
1111
1411
  __proto__: null,
1112
- generateMonorepo: generateMonorepo
1412
+ generateEslintConfigPackage: generateEslintConfigPackage,
1413
+ generateMonorepo: generateMonorepo,
1414
+ generateOxfmtConfigPackage: generateOxfmtConfigPackage,
1415
+ generateOxlintConfigPackage: generateOxlintConfigPackage,
1416
+ generatePrettierConfigPackage: generatePrettierConfigPackage,
1417
+ generateTypescriptConfigPackage: generateTypescriptConfigPackage,
1418
+ generateVscodeFiles: generateVscodeFiles
1113
1419
  };
1114
1420
 
1115
1421
  function toBiomeLevel(level) {
@@ -1366,10 +1672,7 @@ jobs:
1366
1672
  uses: actions/deploy-pages@v4
1367
1673
  `
1368
1674
  });
1369
- generator.inject(
1370
- "readme-start",
1371
- `A github pages deployment action is configurd.`
1372
- );
1675
+ generator.inject("readme-start", `A github pages deployment action is configurd.`);
1373
1676
  if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
1374
1677
  const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
1375
1678
  generator.inject(
@@ -2041,7 +2344,9 @@ function merge(target, modification) {
2041
2344
 
2042
2345
  async function getLatestNpmVersion(packageName, fallback) {
2043
2346
  try {
2044
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
2347
+ const response = await fetch(
2348
+ `https://registry.npmjs.org/${packageName}/latest`
2349
+ );
2045
2350
  const data = await response.json();
2046
2351
  return data.version;
2047
2352
  } catch {
@@ -2051,6 +2356,12 @@ async function getLatestNpmVersion(packageName, fallback) {
2051
2356
  async function getLatestPnpmVersion() {
2052
2357
  return getLatestNpmVersion("pnpm", "10.11.0");
2053
2358
  }
2359
+ async function getLatestYarnVersion() {
2360
+ return getLatestNpmVersion("yarn", "4.6.0");
2361
+ }
2362
+ async function getLatestNpmCliVersion() {
2363
+ return getLatestNpmVersion("npm", "11.0.0");
2364
+ }
2054
2365
  async function getLatestNodeVersion() {
2055
2366
  try {
2056
2367
  const response = await fetch("https://nodejs.org/dist/index.json");
@@ -2064,6 +2375,70 @@ async function getLatestNodeVersion() {
2064
2375
  return "22.0.0";
2065
2376
  }
2066
2377
  }
2378
+ function validateNameSegment(segment, label) {
2379
+ if (!segment.length) {
2380
+ return `${label} is required`;
2381
+ }
2382
+ if (!/^[a-z0-9-]+$/.test(segment)) {
2383
+ return `${label} must be lowercase and contain only letters, numbers, and hyphens`;
2384
+ }
2385
+ if (segment.startsWith("-") || segment.endsWith("-")) {
2386
+ return `${label} cannot start or end with a hyphen`;
2387
+ }
2388
+ if (segment.includes("--")) {
2389
+ return `${label} cannot contain consecutive hyphens`;
2390
+ }
2391
+ return void 0;
2392
+ }
2393
+ function validatePackageName(name) {
2394
+ if (!name.length) {
2395
+ return "Package name is required";
2396
+ }
2397
+ if (name.includes("..") || name.includes("\\")) {
2398
+ return "Package name cannot contain path traversal sequences";
2399
+ }
2400
+ if (name.startsWith("@")) {
2401
+ const slashIndex = name.indexOf("/");
2402
+ if (slashIndex === -1) {
2403
+ return "Scoped package name must include a package name after the scope (e.g., @scope/name)";
2404
+ }
2405
+ if (name.indexOf("/", slashIndex + 1) !== -1) {
2406
+ return "Package name can only have one slash for scoped packages";
2407
+ }
2408
+ const scope = name.slice(1, slashIndex);
2409
+ const packageName = name.slice(slashIndex + 1);
2410
+ const scopeError = validateNameSegment(scope, "Scope");
2411
+ if (scopeError) return scopeError;
2412
+ const nameError = validateNameSegment(packageName, "Package name");
2413
+ if (nameError) return nameError;
2414
+ return void 0;
2415
+ }
2416
+ if (name.includes("/")) {
2417
+ return "Unscoped package name cannot contain slashes. Use @scope/name format for scoped packages";
2418
+ }
2419
+ return validateNameSegment(name, "Package name");
2420
+ }
2421
+ function parseWorkspaceYamlContent(content) {
2422
+ const directories = [];
2423
+ let inPackagesSection = false;
2424
+ for (const line of content.split("\n")) {
2425
+ const trimmed = line.trim();
2426
+ if (trimmed === "packages:") {
2427
+ inPackagesSection = true;
2428
+ continue;
2429
+ }
2430
+ if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
2431
+ break;
2432
+ }
2433
+ if (inPackagesSection && trimmed.startsWith("-")) {
2434
+ const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
2435
+ if (entry && !entry.startsWith(".")) {
2436
+ directories.push(entry);
2437
+ }
2438
+ }
2439
+ }
2440
+ return directories;
2441
+ }
2067
2442
  function generateRandomName() {
2068
2443
  const adjectives = [
2069
2444
  "red",
@@ -2352,7 +2727,8 @@ function generate(options) {
2352
2727
  devDependencies,
2353
2728
  peerDependencies,
2354
2729
  scripts,
2355
- options: clonedOptions
2730
+ options: clonedOptions,
2731
+ workspaceDependencies: clonedOptions.workspaceDependencies
2356
2732
  }).files
2357
2733
  );
2358
2734
  if (!isMonorepoPackage) {
@@ -2369,11 +2745,22 @@ function generate(options) {
2369
2745
  }
2370
2746
 
2371
2747
  exports.generate = generate;
2748
+ exports.generateAiFiles = generateAiFiles;
2749
+ exports.generateEslintConfigPackage = generateEslintConfigPackage;
2372
2750
  exports.generateMonorepo = generateMonorepo;
2751
+ exports.generateOxfmtConfigPackage = generateOxfmtConfigPackage;
2752
+ exports.generateOxlintConfigPackage = generateOxlintConfigPackage;
2753
+ exports.generatePrettierConfigPackage = generatePrettierConfigPackage;
2373
2754
  exports.generateRandomName = generateRandomName;
2755
+ exports.generateTypescriptConfigPackage = generateTypescriptConfigPackage;
2756
+ exports.generateVscodeFiles = generateVscodeFiles;
2374
2757
  exports.getBaseTemplate = getBaseTemplate;
2375
2758
  exports.getLanguageFromTemplate = getLanguageFromTemplate;
2376
2759
  exports.getLatestNodeVersion = getLatestNodeVersion;
2760
+ exports.getLatestNpmCliVersion = getLatestNpmCliVersion;
2377
2761
  exports.getLatestNpmVersion = getLatestNpmVersion;
2378
2762
  exports.getLatestPnpmVersion = getLatestPnpmVersion;
2763
+ exports.getLatestYarnVersion = getLatestYarnVersion;
2379
2764
  exports.monorepo = monorepo;
2765
+ exports.parseWorkspaceYamlContent = parseWorkspaceYamlContent;
2766
+ exports.validatePackageName = validatePackageName;