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/README.md +106 -8
- package/dist/chunks/index.cjs +581 -175
- package/dist/chunks/index.mjs +571 -176
- package/dist/cli.cjs +1078 -317
- package/dist/cli.mjs +1082 -321
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +46 -1
- package/dist/index.d.mts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
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,
|
|
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
|
|
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
|
|
429
|
-
message: "
|
|
430
|
-
|
|
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(
|
|
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
|
|
692
|
-
const devDeps =
|
|
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
|
|
705
|
-
if (
|
|
706
|
-
return
|
|
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
|
|
724
|
-
if (
|
|
725
|
-
packages.push({
|
|
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
|
-
|
|
1004
|
+
initialValue: `@${scope}/`,
|
|
743
1005
|
validate: (value) => {
|
|
744
|
-
|
|
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
|
|
751
|
-
const
|
|
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((
|
|
835
|
-
value:
|
|
836
|
-
label:
|
|
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
|
|
846
|
-
const
|
|
847
|
-
|
|
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
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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();
|