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