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