create-krispya 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -2,17 +2,18 @@
2
2
  import * as p from '@clack/prompts';
3
3
  import color from 'chalk';
4
4
  import { Command } from 'commander';
5
- import { access as access$1, readFile, writeFile, readdir, mkdir, unlink } from 'node:fs/promises';
5
+ import { access, readFile, writeFile, readdir, mkdir, unlink } from 'node:fs/promises';
6
6
  import { createRequire } from 'node:module';
7
- import { resolve, join as join$1, dirname } from 'node:path';
7
+ import { resolve, join, dirname } from 'node:path';
8
8
  import { cwd } from 'node:process';
9
9
  import { fetch } from 'undici';
10
- import { t as getEngineName, a as getBaseTemplate, s as shouldEnableReactCompiler, b as getLanguageFromTemplate, x as getPackageManagerName, g as generateRandomName, A as ALL_AI_PLATFORMS, y as AI_PLATFORM_LABELS, z as AI_PLATFORM_HINTS, d as detectTooling, B as parsePackageManager, C as parseEngine, p as parseWorkspaceYamlContent, D as renderTypescriptConfigPackage, E as renderOxlintConfigPackage, F as renderEslintConfigPackage, G as renderOxfmtConfigPackage, H as renderPrettierConfigPackage, I as resolveMonorepoRootPackageVersions, J as getResolvedPackageVersion, K as renderVscodeFiles, L as renderAiFiles, M as renderVscodeFiles$1, N as renderEditorConfig, O as renderGitignore, P as toPrettierIgnoreContent, Q as mergePackageJsonScripts, R as renderViteConfig, S as packageJsonScripts, T as resolveDefaultPackageJsonScripts, U as formatResolvedPackageVersion, V as renderOxlintConfig, l as planProject, r as resolveProjectPlanInput, v as validatePackageName, q as resolveWorkspacePlanInput, n as planWorkspace } from './shared/create-krispya.DZWMfM2v.mjs';
11
- import Conf from 'conf';
12
- import { access, constants, readFile as readFile$1, mkdir as mkdir$1, writeFile as writeFile$1 } from 'fs/promises';
13
- import { constants as constants$2 } from 'fs';
14
- import { join, dirname as dirname$1 } from 'path';
15
- import { constants as constants$1 } from 'node:fs';
10
+ import { g as getEngineName, a as getPackageManagerName, b as getBaseTemplate, s as shouldEnableReactCompiler, c as getLanguageFromTemplate, d as generateRandomName, e as getConfigStrategy, f as getAiPlatforms, A as ALL_AI_PLATFORMS, h as AI_PLATFORM_LABELS, i as AI_PLATFORM_HINTS, j as detectTooling, p as parsePackageManagerSpec, k as parseEngine, l as parseWorkspaceYamlContent, v as validateWorkspace, r as renderTypescriptConfigPackage, m as renderOxlintConfigPackage, n as renderEslintConfigPackage, o as renderOxfmtConfigPackage, q as renderPrettierConfigPackage, t as resolveMonorepoRootPackageVersions, u as getResolvedPackageVersion, w as renderVscodeFiles, x as renderAiFiles, y as renderVscodeFiles$1, z as renderEditorConfig, B as renderGitignore, C as toPrettierIgnoreContent, D as mergePackageJsonScripts, E as getPackageManagerProfile, F as renderPnpmWorkspaceConfig, G as renderViteConfig, H as packageJsonScripts, I as resolveDefaultPackageJsonScripts, J as formatResolvedPackageVersion, K as getSemverMajorString, L as formatPackageManager, M as renderOxlintConfig, N as resolvePackageManager, O as getSemverMajor, P as compareNumericSemver, Q as getPackageDirectoryName, R as planProject, S as resolveProjectPlanInput, T as validatePackageName, U as clearConfig, V as getConfigPath, W as resolveWorkspacePlanInput, X as planWorkspace } from './shared/create-krispya.BYCdQkPo.mjs';
11
+ import { readFile as readFile$1, mkdir as mkdir$1, writeFile as writeFile$1, access as access$1 } from 'fs/promises';
12
+ import { constants as constants$1 } from 'fs';
13
+ import { join as join$1, dirname as dirname$1 } from 'path';
14
+ import { constants } from 'node:fs';
15
+ import { spawn } from 'node:child_process';
16
+ import 'conf';
16
17
 
17
18
  function formatConfigSummary(options, inherited) {
18
19
  const lines = [];
@@ -30,8 +31,9 @@ function formatConfigSummary(options, inherited) {
30
31
  const projectType = options.projectType ?? "app";
31
32
  const baseTemplate = options.template ? getBaseTemplate(options.template) : "vanilla";
32
33
  if (baseTemplate === "react") {
33
- lines.push(formatRow("Framework", "React"));
34
- lines.push(formatRow("\u21B3 React compiler", shouldEnableReactCompiler(options) ? "yes" : "no"));
34
+ lines.push(
35
+ formatRow("Framework", shouldEnableReactCompiler(options) ? "React + compiler" : "React")
36
+ );
35
37
  } else if (baseTemplate === "r3f") {
36
38
  lines.push(formatRow("Framework", "React Three Fiber"));
37
39
  }
@@ -112,8 +114,9 @@ function formatMonorepoConfigSummary(options) {
112
114
  lines.push(
113
115
  formatRow("Engine", `${getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
114
116
  );
115
- lines.push(formatRow("Package manager", options.packageManager));
116
- if (options.packageManager === "pnpm") {
117
+ const packageManagerName = getPackageManagerName(options.packageManager);
118
+ lines.push(formatRow("Package manager", packageManagerName));
119
+ if (packageManagerName === "pnpm") {
117
120
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
118
121
  lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
119
122
  }
@@ -123,22 +126,6 @@ function formatMonorepoConfigSummary(options) {
123
126
  return lines.join("\n");
124
127
  }
125
128
 
126
- const config = new Conf({
127
- projectName: "create-krispya"
128
- });
129
- function getAiPlatforms() {
130
- return config.get("aiPlatforms");
131
- }
132
- function getConfigStrategy() {
133
- return config.get("configStrategy") ?? "stealth";
134
- }
135
- function clearConfig() {
136
- config.clear();
137
- }
138
- function getConfigPath() {
139
- return config.path;
140
- }
141
-
142
129
  const R3F_INTEGRATION_OPTIONS = [
143
130
  { value: "drei", label: "Drei" },
144
131
  { value: "handle", label: "Handle" },
@@ -252,7 +239,7 @@ async function promptForCustomization(template, name, projectType, features, inh
252
239
  libraryBundler = bundler;
253
240
  }
254
241
  let engine = inheritedSettings?.engine ?? presets?.engine ?? { name: "node", version: "latest" };
255
- let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager ?? "pnpm";
242
+ let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager?.name ?? "pnpm";
256
243
  let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
257
244
  if (!inheritedSettings?.engine?.version) {
258
245
  const nodeVersionInput = await p.text({
@@ -260,7 +247,7 @@ async function promptForCustomization(template, name, projectType, features, inh
260
247
  placeholder: presets?.engine?.version ?? "latest",
261
248
  defaultValue: presets?.engine?.version ?? "latest",
262
249
  validate: (value) => {
263
- if (!value.length) return "Required";
250
+ if (!value?.length) return "Required";
264
251
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
265
252
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
266
253
  }
@@ -280,7 +267,7 @@ async function promptForCustomization(template, name, projectType, features, inh
280
267
  { value: "npm", label: "npm" },
281
268
  { value: "yarn", label: "yarn" }
282
269
  ],
283
- initialValue: presets?.packageManager ?? "pnpm"
270
+ initialValue: presets?.packageManager?.name ?? "pnpm"
284
271
  });
285
272
  if (p.isCancel(packageManager)) {
286
273
  p.cancel("Operation cancelled.");
@@ -389,7 +376,7 @@ async function promptForCustomization(template, name, projectType, features, inh
389
376
  projectType,
390
377
  libraryBundler: projectType === "library" ? libraryBundler : void 0,
391
378
  engine,
392
- packageManager: { name: finalPackageManager },
379
+ packageManager: presets?.packageManager?.name === finalPackageManager ? presets.packageManager : { name: finalPackageManager },
393
380
  pnpmManageVersions,
394
381
  linter,
395
382
  formatter,
@@ -436,7 +423,7 @@ async function promptForMonorepoCustomization(name, presets) {
436
423
  placeholder: presets?.engine?.version ?? "latest",
437
424
  defaultValue: presets?.engine?.version ?? "latest",
438
425
  validate: (value) => {
439
- if (!value.length) return "Required";
426
+ if (!value?.length) return "Required";
440
427
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
441
428
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
442
429
  }
@@ -517,7 +504,7 @@ async function promptForMonorepo(workspaceName, presets) {
517
504
  formatMonorepoConfigSummary({
518
505
  name: defaultOptions.name,
519
506
  engine: defaultOptions.engine ?? { name: "node", version: "latest" },
520
- packageManager: getPackageManagerName(defaultOptions.packageManager),
507
+ packageManager: defaultOptions.packageManager ?? { name: "pnpm" },
521
508
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
522
509
  linter: defaultOptions.linter ?? "oxlint",
523
510
  formatter: defaultOptions.formatter ?? "prettier",
@@ -538,7 +525,7 @@ async function promptForOptions(name, presets) {
538
525
  placeholder: generateRandomName(),
539
526
  defaultValue: generateRandomName(),
540
527
  validate: (value) => {
541
- if (!value.length) return "Project name is required";
528
+ if (!value?.length) return "Project name is required";
542
529
  }
543
530
  });
544
531
  if (p.isCancel(nameResult)) {
@@ -570,7 +557,7 @@ function presetsToInheritedSettings(presets) {
570
557
  return {
571
558
  linter: presets.linter,
572
559
  formatter: presets.formatter,
573
- packageManager: presets.packageManager ? { name: presets.packageManager } : void 0,
560
+ packageManager: presets.packageManager,
574
561
  engine: presets.engine,
575
562
  pnpmManageVersions: presets.pnpmManageVersions
576
563
  };
@@ -655,54 +642,9 @@ async function promptForAiAgentPlatforms(isNonInteractive) {
655
642
  return selected;
656
643
  }
657
644
 
658
- async function checkAnyExists(paths) {
659
- for (const path of paths) {
660
- try {
661
- await access(path, constants.F_OK);
662
- return true;
663
- } catch {
664
- }
665
- }
666
- return false;
667
- }
668
- async function validateWorkspace(monorepoRoot) {
669
- const errors = [];
670
- const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
671
- try {
672
- await access(tsConfigPath, constants.F_OK);
673
- } catch {
674
- errors.push("Missing .config/typescript package");
675
- }
676
- const linterPaths = [
677
- join(monorepoRoot, ".config/oxlint/package.json"),
678
- join(monorepoRoot, ".config/eslint/package.json"),
679
- join(monorepoRoot, "eslint.config.js"),
680
- join(monorepoRoot, "biome.json")
681
- ];
682
- const hasLinter = await checkAnyExists(linterPaths);
683
- if (!hasLinter) {
684
- errors.push(
685
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
686
- );
687
- }
688
- const formatterPaths = [
689
- join(monorepoRoot, ".config/oxfmt/package.json"),
690
- join(monorepoRoot, ".config/prettier/package.json"),
691
- join(monorepoRoot, ".prettierrc.json"),
692
- join(monorepoRoot, "biome.json")
693
- ];
694
- const hasFormatter = await checkAnyExists(formatterPaths);
695
- if (!hasFormatter) {
696
- errors.push(
697
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
698
- );
699
- }
700
- return { valid: errors.length === 0, errors };
701
- }
702
-
703
645
  async function fileExists$1(path) {
704
646
  try {
705
- await access$1(path, constants$1.F_OK);
647
+ await access(path, constants.F_OK);
706
648
  return true;
707
649
  } catch {
708
650
  return false;
@@ -716,9 +658,9 @@ async function detectMonorepoRoot() {
716
658
  let currentDir = cwd();
717
659
  const root = resolve("/");
718
660
  while (currentDir !== root) {
719
- const workspaceFile = join$1(currentDir, "pnpm-workspace.yaml");
661
+ const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
720
662
  try {
721
- await access$1(workspaceFile, constants$1.F_OK);
663
+ await access(workspaceFile, constants.F_OK);
722
664
  const content = await readFile(workspaceFile, "utf-8");
723
665
  if (content.includes("packages:")) {
724
666
  return currentDir;
@@ -733,16 +675,16 @@ async function detectPackageRoot() {
733
675
  let currentDir = cwd();
734
676
  const root = resolve("/");
735
677
  while (currentDir !== root) {
736
- if (await fileExists$1(join$1(currentDir, "package.json"))) {
678
+ if (await fileExists$1(join(currentDir, "package.json"))) {
737
679
  return currentDir;
738
680
  }
739
681
  currentDir = dirname(currentDir);
740
682
  }
741
- return await fileExists$1(join$1(root, "package.json")) ? root : null;
683
+ return await fileExists$1(join(root, "package.json")) ? root : null;
742
684
  }
743
685
  async function parseWorkspaceDirectories(monorepoRoot) {
744
686
  try {
745
- const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
687
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
746
688
  const content = await readFile(workspaceFile, "utf-8");
747
689
  return parseWorkspaceYamlContent(content);
748
690
  } catch {
@@ -752,14 +694,14 @@ async function parseWorkspaceDirectories(monorepoRoot) {
752
694
  async function detectWorkspaceSettings(monorepoRoot) {
753
695
  try {
754
696
  const tooling = await detectTooling(monorepoRoot);
755
- const pkgPath = join$1(monorepoRoot, "package.json");
697
+ const pkgPath = join(monorepoRoot, "package.json");
756
698
  const content = await readFile(pkgPath, "utf-8");
757
699
  const pkgJson = JSON.parse(content);
758
- const packageManager = parsePackageManager(pkgJson.packageManager);
700
+ const packageManager = parsePackageManagerSpec(pkgJson.packageManager);
759
701
  const engine = parseEngine(pkgJson.engines);
760
702
  let pnpmManageVersions;
761
703
  try {
762
- const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
704
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
763
705
  const workspaceContent = await readFile(workspaceFile, "utf-8");
764
706
  pnpmManageVersions = workspaceContent.includes("manage-package-manager-versions: true");
765
707
  } catch {
@@ -777,17 +719,17 @@ async function detectWorkspaceSettings(monorepoRoot) {
777
719
  }
778
720
  async function detectExistingConfigs(monorepoRoot) {
779
721
  const configs = {};
780
- const eslintPath = join$1(monorepoRoot, "eslint.config.js");
722
+ const eslintPath = join(monorepoRoot, "eslint.config.js");
781
723
  if (await fileExists$1(eslintPath)) {
782
724
  configs.linter = "eslint";
783
725
  configs.eslintConfigPath = eslintPath;
784
726
  }
785
- const prettierPath = join$1(monorepoRoot, ".prettierrc.json");
727
+ const prettierPath = join(monorepoRoot, ".prettierrc.json");
786
728
  if (await fileExists$1(prettierPath)) {
787
729
  configs.formatter = "prettier";
788
730
  configs.prettierConfigPath = prettierPath;
789
731
  }
790
- const biomePath = join$1(monorepoRoot, "biome.json");
732
+ const biomePath = join(monorepoRoot, "biome.json");
791
733
  if (await fileExists$1(biomePath)) {
792
734
  configs.biomeConfigPath = biomePath;
793
735
  if (!configs.linter) configs.linter = "biome";
@@ -797,7 +739,7 @@ async function detectExistingConfigs(monorepoRoot) {
797
739
  }
798
740
  async function getMonorepoScope(monorepoRoot) {
799
741
  try {
800
- const pkgPath = join$1(monorepoRoot, "package.json");
742
+ const pkgPath = join(monorepoRoot, "package.json");
801
743
  const content = await readFile(pkgPath, "utf-8");
802
744
  const pkgJson = JSON.parse(content);
803
745
  if (pkgJson.name) {
@@ -808,14 +750,14 @@ async function getMonorepoScope(monorepoRoot) {
808
750
  return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
809
751
  }
810
752
  async function getWorkspacePackages(monorepoRoot) {
811
- const packagesDir = join$1(monorepoRoot, "packages");
753
+ const packagesDir = join(monorepoRoot, "packages");
812
754
  try {
813
755
  const entries = await readdir(packagesDir, { withFileTypes: true });
814
756
  const names = [];
815
757
  for (const entry of entries) {
816
758
  if (!entry.isDirectory()) continue;
817
759
  try {
818
- const content = await readFile(join$1(packagesDir, entry.name, "package.json"), "utf-8");
760
+ const content = await readFile(join(packagesDir, entry.name, "package.json"), "utf-8");
819
761
  const pkg = JSON.parse(content);
820
762
  if (pkg.name) names.push(pkg.name);
821
763
  } catch {
@@ -827,7 +769,7 @@ async function getWorkspacePackages(monorepoRoot) {
827
769
  }
828
770
  }
829
771
  async function ensureConfigInWorkspace(monorepoRoot) {
830
- const workspacePath = join$1(monorepoRoot, "pnpm-workspace.yaml");
772
+ const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
831
773
  let content;
832
774
  try {
833
775
  content = await readFile(workspacePath, "utf-8");
@@ -877,7 +819,7 @@ async function handleCheckCommand() {
877
819
 
878
820
  async function migrateEslintConfig(monorepoRoot, files) {
879
821
  const configBasePath = ".config/eslint";
880
- const existingConfigPath = join$1(monorepoRoot, "eslint.config.js");
822
+ const existingConfigPath = join(monorepoRoot, "eslint.config.js");
881
823
  let existingContent;
882
824
  try {
883
825
  existingContent = await readFile(existingConfigPath, "utf-8");
@@ -956,7 +898,7 @@ export default [
956
898
  }
957
899
  async function migratePrettierConfig(monorepoRoot, files) {
958
900
  const configBasePath = ".config/prettier";
959
- const existingConfigPath = join$1(monorepoRoot, ".prettierrc.json");
901
+ const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
960
902
  let existingContent;
961
903
  try {
962
904
  existingContent = await readFile(existingConfigPath, "utf-8");
@@ -1092,15 +1034,15 @@ async function handleFixCommand(options) {
1092
1034
  spinner.start("Fixing workspace...");
1093
1035
  try {
1094
1036
  const files = {};
1095
- const tsConfigExists = await fileExists$1(join$1(monorepoRoot, ".config/typescript/package.json"));
1037
+ const tsConfigExists = await fileExists$1(join(monorepoRoot, ".config/typescript/package.json"));
1096
1038
  if (!tsConfigExists) {
1097
1039
  renderTypescriptConfigPackage(files);
1098
1040
  }
1099
1041
  if (linter === "oxlint") {
1100
- const oxlintExists = await fileExists$1(join$1(monorepoRoot, ".config/oxlint/package.json"));
1042
+ const oxlintExists = await fileExists$1(join(monorepoRoot, ".config/oxlint/package.json"));
1101
1043
  if (!oxlintExists) renderOxlintConfigPackage(files);
1102
1044
  } else if (linter === "eslint") {
1103
- const eslintPkgExists = await fileExists$1(join$1(monorepoRoot, ".config/eslint/package.json"));
1045
+ const eslintPkgExists = await fileExists$1(join(monorepoRoot, ".config/eslint/package.json"));
1104
1046
  if (!eslintPkgExists) {
1105
1047
  if (existingConfigs.eslintConfigPath) {
1106
1048
  await migrateEslintConfig(monorepoRoot, files);
@@ -1110,10 +1052,10 @@ async function handleFixCommand(options) {
1110
1052
  }
1111
1053
  }
1112
1054
  if (formatter === "oxfmt") {
1113
- const oxfmtExists = await fileExists$1(join$1(monorepoRoot, ".config/oxfmt/package.json"));
1055
+ const oxfmtExists = await fileExists$1(join(monorepoRoot, ".config/oxfmt/package.json"));
1114
1056
  if (!oxfmtExists) renderOxfmtConfigPackage(files);
1115
1057
  } else if (formatter === "prettier") {
1116
- const prettierPkgExists = await fileExists$1(join$1(monorepoRoot, ".config/prettier/package.json"));
1058
+ const prettierPkgExists = await fileExists$1(join(monorepoRoot, ".config/prettier/package.json"));
1117
1059
  if (!prettierPkgExists) {
1118
1060
  if (existingConfigs.prettierConfigPath) {
1119
1061
  await migratePrettierConfig(monorepoRoot, files);
@@ -1151,7 +1093,7 @@ async function handleFixCommand(options) {
1151
1093
  };
1152
1094
  }
1153
1095
  for (const [filePath, file] of Object.entries(files)) {
1154
- const fullPath = join$1(monorepoRoot, filePath);
1096
+ const fullPath = join(monorepoRoot, filePath);
1155
1097
  await mkdir(dirname(fullPath), { recursive: true });
1156
1098
  await writeFile(fullPath, file.content);
1157
1099
  }
@@ -1174,8 +1116,8 @@ async function handleFixCommand(options) {
1174
1116
  const pkgName = pkgFile.replace("/package.json", "");
1175
1117
  console.log(color.dim(` Generated ${pkgName}`));
1176
1118
  }
1177
- const vscodeSettingsExists = await fileExists$1(join$1(monorepoRoot, ".vscode/settings.json"));
1178
- const vscodeExtensionsExists = await fileExists$1(join$1(monorepoRoot, ".vscode/extensions.json"));
1119
+ const vscodeSettingsExists = await fileExists$1(join(monorepoRoot, ".vscode/settings.json"));
1120
+ const vscodeExtensionsExists = await fileExists$1(join(monorepoRoot, ".vscode/extensions.json"));
1179
1121
  const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
1180
1122
  if (!vscodeExists) {
1181
1123
  let addVscode = false;
@@ -1192,7 +1134,7 @@ async function handleFixCommand(options) {
1192
1134
  const vscodeFiles = {};
1193
1135
  renderVscodeFiles(vscodeFiles, linter, formatter);
1194
1136
  for (const [filePath, file] of Object.entries(vscodeFiles)) {
1195
- const fullPath = join$1(monorepoRoot, filePath);
1137
+ const fullPath = join(monorepoRoot, filePath);
1196
1138
  await mkdir(dirname(fullPath), { recursive: true });
1197
1139
  await writeFile(fullPath, file.content);
1198
1140
  }
@@ -1200,7 +1142,7 @@ async function handleFixCommand(options) {
1200
1142
  console.log(color.dim(" Generated .vscode/extensions.json"));
1201
1143
  }
1202
1144
  }
1203
- const aiRulesExist = await fileExists$1(join$1(monorepoRoot, ".ai/workspace.md"));
1145
+ const aiRulesExist = await fileExists$1(join(monorepoRoot, ".ai/workspace.md"));
1204
1146
  if (!aiRulesExist) {
1205
1147
  const platforms = await promptForAiAgentPlatforms(isNonInteractive);
1206
1148
  if (platforms.length > 0) {
@@ -1216,7 +1158,7 @@ async function handleFixCommand(options) {
1216
1158
  platforms
1217
1159
  });
1218
1160
  for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1219
- const fullPath = join$1(monorepoRoot, filePath);
1161
+ const fullPath = join(monorepoRoot, filePath);
1220
1162
  await mkdir(dirname(fullPath), { recursive: true });
1221
1163
  await writeFile(fullPath, file.content);
1222
1164
  console.log(color.dim(` Generated ${filePath}`));
@@ -1260,18 +1202,22 @@ function renderExpectedViteConfig(template) {
1260
1202
  async function detectCurrentConfig(root, isMonorepo = true) {
1261
1203
  let name = root.split(/[/\\]/).pop() ?? "workspace";
1262
1204
  let packageManager = "pnpm";
1205
+ let packageManagerSpec = { name: "pnpm" };
1206
+ let engine;
1263
1207
  let hasTypecheck = false;
1264
1208
  let viteTemplate;
1265
1209
  try {
1266
- const pkgPath = join(root, "package.json");
1210
+ const pkgPath = join$1(root, "package.json");
1267
1211
  const content = await readFile$1(pkgPath, "utf-8");
1268
1212
  const pkgJson = JSON.parse(content);
1269
1213
  if (pkgJson.name) {
1270
1214
  name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
1271
1215
  }
1272
1216
  if (pkgJson.packageManager) {
1273
- packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
1217
+ packageManagerSpec = parsePackageManagerSpec(pkgJson.packageManager) ?? packageManagerSpec;
1218
+ packageManager = packageManagerSpec.name;
1274
1219
  }
1220
+ engine = parseEngine(pkgJson.engines);
1275
1221
  hasTypecheck = pkgJson.scripts?.typecheck != null;
1276
1222
  viteTemplate = detectViteTemplate(pkgJson);
1277
1223
  } catch {
@@ -1283,6 +1229,8 @@ async function detectCurrentConfig(root, isMonorepo = true) {
1283
1229
  linter: tooling.linter ?? "oxlint",
1284
1230
  formatter: tooling.formatter ?? "prettier",
1285
1231
  packageManager,
1232
+ packageManagerSpec,
1233
+ engine,
1286
1234
  isMonorepo,
1287
1235
  configStrategy,
1288
1236
  hasTypecheck,
@@ -1291,10 +1239,10 @@ async function detectCurrentConfig(root, isMonorepo = true) {
1291
1239
  }
1292
1240
  async function detectSinglePackageConfigStrategy(root) {
1293
1241
  const hasStealthConfig = await Promise.all([
1294
- fileExists(join(root, ".config/tsconfig.app.json")),
1295
- fileExists(join(root, ".config/tsconfig.node.json")),
1296
- fileExists(join(root, ".config/prettier.json")),
1297
- fileExists(join(root, ".config/oxlint.json"))
1242
+ fileExists(join$1(root, ".config/tsconfig.app.json")),
1243
+ fileExists(join$1(root, ".config/tsconfig.node.json")),
1244
+ fileExists(join$1(root, ".config/prettier.json")),
1245
+ fileExists(join$1(root, ".config/oxlint.json"))
1298
1246
  ]).then((matches) => matches.some(Boolean));
1299
1247
  return hasStealthConfig ? "stealth" : "root";
1300
1248
  }
@@ -1389,7 +1337,7 @@ async function planExpectedFiles(config) {
1389
1337
  }
1390
1338
  async function fileExists(path) {
1391
1339
  try {
1392
- await access(path, constants$2.F_OK);
1340
+ await access$1(path, constants$1.F_OK);
1393
1341
  return true;
1394
1342
  } catch {
1395
1343
  return false;
@@ -1528,7 +1476,7 @@ async function compareWithDisk(expected, root) {
1528
1476
  const changes = [];
1529
1477
  for (const [filePath, file] of Object.entries(files)) {
1530
1478
  if (file.type !== "text") continue;
1531
- const fullPath = join(root, filePath);
1479
+ const fullPath = join$1(root, filePath);
1532
1480
  const newContent = file.content;
1533
1481
  if (await fileExists(fullPath)) {
1534
1482
  const currentContent = await readFile$1(fullPath, "utf-8");
@@ -1603,13 +1551,13 @@ function addMissingDevDependency(pkg, devDependencies, name) {
1603
1551
  }
1604
1552
  async function detectTypeScriptPackage(root, pkg) {
1605
1553
  if (hasPackage(pkg, "typescript")) return true;
1606
- return await fileExists(join(root, "tsconfig.json")) || await fileExists(join(root, "tsconfig.app.json")) || await fileExists(join(root, ".config/tsconfig.app.json"));
1554
+ return await fileExists(join$1(root, "tsconfig.json")) || await fileExists(join$1(root, "tsconfig.app.json")) || await fileExists(join$1(root, ".config/tsconfig.app.json"));
1607
1555
  }
1608
1556
  function detectLibraryPackage(pkg) {
1609
1557
  return pkg.exports != null || pkg.main?.includes("dist") === true || pkg.module?.includes("dist") === true || Array.isArray(pkg.files) && pkg.files.includes("dist");
1610
1558
  }
1611
1559
  function getPackageManagerForScripts(config, pkg) {
1612
- const packageManager = pkg.packageManager?.split("@")[0] ?? config.packageManager;
1560
+ const packageManager = parsePackageManagerSpec(pkg.packageManager)?.name ?? config.packageManager;
1613
1561
  return isPackageManagerName(packageManager) ? packageManager : "pnpm";
1614
1562
  }
1615
1563
  function getSinglePackageToolScripts(config) {
@@ -1639,6 +1587,35 @@ function scriptsEqual(left, right) {
1639
1587
  if (leftEntries.length !== Object.keys(right).length) return false;
1640
1588
  return leftEntries.every(([key, value]) => right[key] === value);
1641
1589
  }
1590
+ function packageManagerFieldsEqual(pkg, packageManagerSpec, targetNodeVersion) {
1591
+ if (packageManagerSpec == null || packageManagerSpec.version == null) {
1592
+ return targetNodeVersion == null || pkg.engines?.node === `>=${targetNodeVersion}`;
1593
+ }
1594
+ const majorVersion = getSemverMajorString(packageManagerSpec.version);
1595
+ return pkg.packageManager === formatPackageManager(packageManagerSpec) && pkg.engines?.[packageManagerSpec.name] === `>=${majorVersion}.0.0` && (targetNodeVersion == null || pkg.engines?.node === `>=${targetNodeVersion}`);
1596
+ }
1597
+ function applyPackageManagerFields(pkg, packageManagerSpec, targetNodeVersion) {
1598
+ if (packageManagerSpec == null || packageManagerSpec.version == null) {
1599
+ if (targetNodeVersion == null) return pkg;
1600
+ return {
1601
+ ...pkg,
1602
+ engines: {
1603
+ ...pkg.engines,
1604
+ node: `>=${targetNodeVersion}`
1605
+ }
1606
+ };
1607
+ }
1608
+ const majorVersion = getSemverMajorString(packageManagerSpec.version);
1609
+ return {
1610
+ ...pkg,
1611
+ packageManager: formatPackageManager(packageManagerSpec),
1612
+ engines: {
1613
+ ...pkg.engines,
1614
+ [packageManagerSpec.name]: `>=${majorVersion}.0.0`,
1615
+ ...targetNodeVersion == null ? {} : { node: `>=${targetNodeVersion}` }
1616
+ }
1617
+ };
1618
+ }
1642
1619
  async function getExpectedPackageScripts(root, config, pkg) {
1643
1620
  if (config.isMonorepo) {
1644
1621
  return packageJsonScripts.monorepoRoot(config.linter, config.formatter);
@@ -1667,6 +1644,9 @@ async function getExpectedPackageDevDependencies(root, config, pkg) {
1667
1644
  addMissingDevDependency(pkg, nextDevDependencies, "@babel/core");
1668
1645
  addMissingDevDependency(pkg, nextDevDependencies, "@rolldown/plugin-babel");
1669
1646
  addMissingDevDependency(pkg, nextDevDependencies, "babel-plugin-react-compiler");
1647
+ if (config.linter === "oxlint") {
1648
+ addMissingDevDependency(pkg, nextDevDependencies, "eslint-plugin-react-hooks");
1649
+ }
1670
1650
  if (await detectTypeScriptPackage(root, pkg)) {
1671
1651
  addMissingDevDependency(pkg, nextDevDependencies, "@types/babel__core");
1672
1652
  }
@@ -1674,7 +1654,7 @@ async function getExpectedPackageDevDependencies(root, config, pkg) {
1674
1654
  return sortPackageMap(nextDevDependencies);
1675
1655
  }
1676
1656
  async function getPackageJsonScriptUpdates(root, config) {
1677
- const packageJsonPath = join(root, "package.json");
1657
+ const packageJsonPath = join$1(root, "package.json");
1678
1658
  let currentContent;
1679
1659
  try {
1680
1660
  currentContent = await readFile$1(packageJsonPath, "utf-8");
@@ -1687,7 +1667,9 @@ async function getPackageJsonScriptUpdates(root, config) {
1687
1667
  const nextScripts = mergePackageJsonScripts(currentScripts, expectedScripts);
1688
1668
  const currentDevDependencies = pkg.devDependencies ?? {};
1689
1669
  const nextDevDependencies = await getExpectedPackageDevDependencies(root, config, pkg);
1690
- if (scriptsEqual(currentScripts, nextScripts) && scriptsEqual(currentDevDependencies, nextDevDependencies)) {
1670
+ const targetPackageManagerSpec = config.targetPackageManagerSpec;
1671
+ const targetNodeVersion = config.targetNodeVersion;
1672
+ if (scriptsEqual(currentScripts, nextScripts) && scriptsEqual(currentDevDependencies, nextDevDependencies) && packageManagerFieldsEqual(pkg, targetPackageManagerSpec, targetNodeVersion)) {
1691
1673
  return [
1692
1674
  {
1693
1675
  path: "package.json",
@@ -1697,10 +1679,14 @@ async function getPackageJsonScriptUpdates(root, config) {
1697
1679
  }
1698
1680
  ];
1699
1681
  }
1700
- const nextPackageJson = {
1701
- ...pkg,
1702
- scripts: nextScripts
1703
- };
1682
+ const nextPackageJson = applyPackageManagerFields(
1683
+ {
1684
+ ...pkg,
1685
+ scripts: nextScripts
1686
+ },
1687
+ targetPackageManagerSpec,
1688
+ targetNodeVersion
1689
+ );
1704
1690
  if (Object.keys(nextDevDependencies).length > 0 || pkg.devDependencies != null) {
1705
1691
  nextPackageJson.devDependencies = nextDevDependencies;
1706
1692
  }
@@ -1715,12 +1701,49 @@ async function getPackageJsonScriptUpdates(root, config) {
1715
1701
  }
1716
1702
  ];
1717
1703
  }
1704
+ async function getPackageManagerConfigUpdates(root, config) {
1705
+ if (config.targetPackageManagerSpec == null && config.targetNodeVersion == null) return [];
1706
+ const packageJsonPath = join$1(root, "package.json");
1707
+ let currentContent;
1708
+ try {
1709
+ currentContent = await readFile$1(packageJsonPath, "utf-8");
1710
+ } catch {
1711
+ return [];
1712
+ }
1713
+ const pkg = JSON.parse(currentContent);
1714
+ if (packageManagerFieldsEqual(pkg, config.targetPackageManagerSpec, config.targetNodeVersion)) {
1715
+ return [
1716
+ {
1717
+ path: "package.json",
1718
+ status: "unchanged",
1719
+ currentContent,
1720
+ newContent: currentContent
1721
+ }
1722
+ ];
1723
+ }
1724
+ const nextPackageJson = applyPackageManagerFields(
1725
+ pkg,
1726
+ config.targetPackageManagerSpec,
1727
+ config.targetNodeVersion
1728
+ );
1729
+ return [
1730
+ {
1731
+ path: "package.json",
1732
+ status: "modified",
1733
+ currentContent,
1734
+ newContent: `${JSON.stringify(nextPackageJson, null, 2)}
1735
+ `
1736
+ }
1737
+ ];
1738
+ }
1718
1739
  function planSinglePackageOxlintConfig(config) {
1719
1740
  if (config.linter !== "oxlint" || config.isMonorepo) return void 0;
1720
1741
  const isStealth = (config.configStrategy ?? "stealth") === "stealth";
1721
1742
  const path = isStealth ? ".config/oxlint.json" : "oxlint.json";
1722
1743
  const oxlintConfig = renderOxlintConfig({
1723
1744
  schemaPath: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
1745
+ react: config.viteTemplate === "react" || config.viteTemplate === "r3f",
1746
+ reactCompiler: config.viteTemplate === "react",
1724
1747
  typescript: true
1725
1748
  });
1726
1749
  return {
@@ -1733,7 +1756,7 @@ function planSinglePackageOxlintConfig(config) {
1733
1756
  async function getOxlintConfigReplacementUpdates(root, config) {
1734
1757
  const expected = planSinglePackageOxlintConfig(config);
1735
1758
  if (expected == null) return [];
1736
- const fullPath = join(root, expected.path);
1759
+ const fullPath = join$1(root, expected.path);
1737
1760
  let currentContent;
1738
1761
  try {
1739
1762
  currentContent = await readFile$1(fullPath, "utf-8");
@@ -1758,9 +1781,105 @@ async function getOxlintConfigReplacementUpdates(root, config) {
1758
1781
  }
1759
1782
  ];
1760
1783
  }
1761
- async function getWorkspaceConfigUpdates(root) {
1762
- const workspacePath = join(root, "pnpm-workspace.yaml");
1784
+ function extractBuildDependencies(content) {
1785
+ const buildDependencies = {};
1786
+ let section;
1787
+ for (const line of content.split("\n")) {
1788
+ const trimmed = line.trim();
1789
+ if (trimmed === "onlyBuiltDependencies:") {
1790
+ section = "onlyBuiltDependencies";
1791
+ continue;
1792
+ }
1793
+ if (trimmed === "allowBuilds:") {
1794
+ section = "allowBuilds";
1795
+ continue;
1796
+ }
1797
+ if (section != null && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
1798
+ section = void 0;
1799
+ }
1800
+ if (section === "onlyBuiltDependencies" && trimmed.startsWith("-")) {
1801
+ const dependency = trimmed.slice(1).trim().replace(/^["']|["']$/g, "");
1802
+ if (dependency.length > 0) {
1803
+ buildDependencies[dependency] = true;
1804
+ }
1805
+ }
1806
+ if (section === "allowBuilds") {
1807
+ const match = trimmed.match(/^([^:]+):\s*(true|false)$/);
1808
+ if (match == null) continue;
1809
+ const dependency = match[1].trim().replace(/^["']|["']$/g, "");
1810
+ if (dependency.length > 0) {
1811
+ buildDependencies[dependency] = match[2] === "true";
1812
+ }
1813
+ }
1814
+ }
1815
+ return buildDependencies;
1816
+ }
1817
+ function withDefaultBuildDependencies(buildDependencies) {
1818
+ return Object.keys(buildDependencies).length > 0 ? buildDependencies : { esbuild: true };
1819
+ }
1820
+ function isPnpmWorkspaceManagedScalarKey(trimmed) {
1821
+ return trimmed.startsWith("manage-package-manager-versions:") || trimmed.startsWith("pmOnFail:");
1822
+ }
1823
+ function isPnpmWorkspaceManagedBlockKey(trimmed) {
1824
+ return trimmed === "onlyBuiltDependencies:" || trimmed === "allowBuilds:";
1825
+ }
1826
+ function isIndentedWorkspaceLine(line) {
1827
+ return line.startsWith(" ") || line.startsWith(" ");
1828
+ }
1829
+ function removePnpmWorkspaceManagedKeys(content) {
1830
+ const sourceLines = content.split("\n");
1831
+ const lines = [];
1832
+ let insertionIndex = 0;
1833
+ let foundManagedKey = false;
1834
+ for (let index = 0; index < sourceLines.length; index++) {
1835
+ const line = sourceLines[index];
1836
+ const trimmed = line.trim();
1837
+ if (isPnpmWorkspaceManagedScalarKey(trimmed)) {
1838
+ if (!foundManagedKey) {
1839
+ insertionIndex = lines.length;
1840
+ foundManagedKey = true;
1841
+ }
1842
+ continue;
1843
+ }
1844
+ if (isPnpmWorkspaceManagedBlockKey(trimmed)) {
1845
+ if (!foundManagedKey) {
1846
+ insertionIndex = lines.length;
1847
+ foundManagedKey = true;
1848
+ }
1849
+ while (index + 1 < sourceLines.length) {
1850
+ const nextLine = sourceLines[index + 1];
1851
+ const nextTrimmed = nextLine.trim();
1852
+ if (nextTrimmed !== "" && !isIndentedWorkspaceLine(nextLine)) break;
1853
+ index++;
1854
+ }
1855
+ continue;
1856
+ }
1857
+ lines.push(line);
1858
+ }
1859
+ return {
1860
+ content: lines.join("\n").trim(),
1861
+ insertionIndex
1862
+ };
1863
+ }
1864
+ function patchPnpmWorkspaceManagedKeys(content, profile, buildDependencies) {
1865
+ const managedContent = renderPnpmWorkspaceConfig({
1866
+ profile,
1867
+ manageVersions: true,
1868
+ buildDependencies
1869
+ });
1870
+ const existing = removePnpmWorkspaceManagedKeys(content);
1871
+ const preservedLines = existing.content.length > 0 ? existing.content.split("\n") : [];
1872
+ const insertionIndex = Math.min(existing.insertionIndex, preservedLines.length);
1873
+ const managedLines = managedContent.split("\n");
1874
+ preservedLines.splice(insertionIndex, 0, ...managedLines);
1875
+ return preservedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1876
+ }
1877
+ async function getWorkspaceConfigUpdates(root, config) {
1878
+ const workspacePath = join$1(root, "pnpm-workspace.yaml");
1763
1879
  const changes = [];
1880
+ const packageManagerSpec = config?.targetPackageManagerSpec ?? config?.packageManagerSpec ?? { name: "pnpm" };
1881
+ const profile = getPackageManagerProfile(packageManagerSpec);
1882
+ const defaultPackages = [".config/*", "apps/*", "packages/*"];
1764
1883
  let currentContent = "";
1765
1884
  let exists = false;
1766
1885
  try {
@@ -1769,15 +1888,11 @@ async function getWorkspaceConfigUpdates(root) {
1769
1888
  } catch {
1770
1889
  }
1771
1890
  if (!exists) {
1772
- const newContent = `manage-package-manager-versions: true
1773
-
1774
- packages:
1775
- - '.config/*'
1776
- - 'apps/*'
1777
- - 'packages/*'
1778
-
1779
- onlyBuiltDependencies:
1780
- - esbuild
1891
+ const newContent = `${renderPnpmWorkspaceConfig({
1892
+ profile,
1893
+ manageVersions: true,
1894
+ packages: defaultPackages
1895
+ })}
1781
1896
  `;
1782
1897
  changes.push({
1783
1898
  path: "pnpm-workspace.yaml",
@@ -1786,32 +1901,14 @@ onlyBuiltDependencies:
1786
1901
  });
1787
1902
  return changes;
1788
1903
  }
1789
- let updatedContent = currentContent;
1790
- let needsUpdate = false;
1791
- if (!currentContent.includes("manage-package-manager-versions")) {
1792
- updatedContent = `manage-package-manager-versions: true
1793
-
1794
- ${updatedContent}`;
1795
- needsUpdate = true;
1796
- }
1797
- if (!currentContent.includes("onlyBuiltDependencies")) {
1798
- updatedContent = `${updatedContent.trimEnd()}
1799
-
1800
- onlyBuiltDependencies:
1801
- - esbuild
1904
+ const buildDependencies = withDefaultBuildDependencies(extractBuildDependencies(currentContent));
1905
+ const updatedContent = `${patchPnpmWorkspaceManagedKeys(
1906
+ currentContent,
1907
+ profile,
1908
+ buildDependencies
1909
+ )}
1802
1910
  `;
1803
- needsUpdate = true;
1804
- }
1805
- if (!currentContent.includes(".config/*")) {
1806
- const lines = updatedContent.split("\n");
1807
- const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1808
- if (packagesIndex !== -1) {
1809
- lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1810
- updatedContent = lines.join("\n");
1811
- needsUpdate = true;
1812
- }
1813
- }
1814
- if (needsUpdate) {
1911
+ if (updatedContent !== currentContent) {
1815
1912
  changes.push({
1816
1913
  path: "pnpm-workspace.yaml",
1817
1914
  status: "modified",
@@ -1831,7 +1928,7 @@ onlyBuiltDependencies:
1831
1928
  async function applyUpdates(changes, root) {
1832
1929
  for (const change of changes) {
1833
1930
  if (change.status === "unchanged") continue;
1834
- const fullPath = join(root, change.path);
1931
+ const fullPath = join$1(root, change.path);
1835
1932
  await mkdir$1(dirname$1(fullPath), { recursive: true });
1836
1933
  await writeFile$1(fullPath, change.newContent);
1837
1934
  }
@@ -1909,6 +2006,96 @@ function orderUpdateCategories(categories) {
1909
2006
  (left, right) => getCategoryOrder(left.category) - getCategoryOrder(right.category)
1910
2007
  );
1911
2008
  }
2009
+ function isPnpmMajorMigration(config) {
2010
+ const currentPackageManager = config.packageManagerSpec;
2011
+ const targetPackageManager = config.targetPackageManagerSpec;
2012
+ if (currentPackageManager?.name !== "pnpm" || targetPackageManager?.name !== "pnpm") {
2013
+ return false;
2014
+ }
2015
+ const currentMajor = getSemverMajor(currentPackageManager.version);
2016
+ const targetMajor = getSemverMajor(targetPackageManager.version);
2017
+ return currentMajor != null && targetMajor != null && currentMajor !== targetMajor;
2018
+ }
2019
+ function getPackageUpdateCommand(config) {
2020
+ const packageManagerName = config.packageManager.split("@")[0] ?? config.packageManager;
2021
+ if (packageManagerName === "pnpm") {
2022
+ if (isPnpmMajorMigration(config)) {
2023
+ return {
2024
+ command: "pnpm",
2025
+ args: ["install"],
2026
+ displayCommand: "pnpm install",
2027
+ promptMessage: "Install dependencies?",
2028
+ successMessage: "Dependencies installed",
2029
+ failureLabel: "Dependency install"
2030
+ };
2031
+ }
2032
+ return {
2033
+ command: "pnpm",
2034
+ args: ["update"],
2035
+ displayCommand: "pnpm update",
2036
+ promptMessage: "Update packages?",
2037
+ successMessage: "Packages updated",
2038
+ failureLabel: "Package update"
2039
+ };
2040
+ }
2041
+ if (packageManagerName === "npm") {
2042
+ return {
2043
+ command: "npm",
2044
+ args: ["update"],
2045
+ displayCommand: "npm update",
2046
+ promptMessage: "Update packages?",
2047
+ successMessage: "Packages updated",
2048
+ failureLabel: "Package update"
2049
+ };
2050
+ }
2051
+ return void 0;
2052
+ }
2053
+ async function runPackageUpdate(projectRoot, updateCommand) {
2054
+ await new Promise((resolve, reject) => {
2055
+ const child = spawn(updateCommand.command, updateCommand.args, {
2056
+ cwd: projectRoot,
2057
+ shell: process.platform === "win32",
2058
+ stdio: "inherit"
2059
+ });
2060
+ child.on("error", reject);
2061
+ child.on("exit", (code) => {
2062
+ if (code === 0) {
2063
+ resolve();
2064
+ return;
2065
+ }
2066
+ reject(new Error(`${updateCommand.displayCommand} exited with code ${code ?? "unknown"}`));
2067
+ });
2068
+ });
2069
+ }
2070
+ async function promptForPackageUpdate(projectRoot, config, options) {
2071
+ const updateCommand = getPackageUpdateCommand(config);
2072
+ if (!updateCommand) {
2073
+ console.log(color.dim(` Package updates are not supported for ${config.packageManager}`));
2074
+ return;
2075
+ }
2076
+ const shouldUpdatePackages = options.yes || await p.confirm({
2077
+ message: updateCommand.promptMessage,
2078
+ initialValue: true
2079
+ });
2080
+ if (p.isCancel(shouldUpdatePackages)) {
2081
+ p.cancel("Operation cancelled.");
2082
+ process.exit(0);
2083
+ }
2084
+ if (!shouldUpdatePackages) {
2085
+ console.log(color.dim(" Skipped package updates"));
2086
+ return;
2087
+ }
2088
+ console.log();
2089
+ console.log(color.cyan(`Running ${updateCommand.displayCommand}...`));
2090
+ try {
2091
+ await runPackageUpdate(projectRoot, updateCommand);
2092
+ console.log(color.green("\u2713") + ` ${updateCommand.successMessage}`);
2093
+ } catch (error) {
2094
+ const message = error instanceof Error ? error.message : String(error);
2095
+ console.log(color.red("\u2717") + ` ${updateCommand.failureLabel} failed: ${message}`);
2096
+ process.exit(1);
2097
+ }
2098
+ }
1912
2099
  async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1913
2100
  const expected = await planExpectedFiles(config);
1914
2101
  const categories = await compareWithDisk(expected, projectRoot);
@@ -1932,7 +2119,7 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1932
2119
  });
1933
2120
  }
1934
2121
  if (isMonorepo) {
1935
- const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2122
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot, config);
1936
2123
  if (workspaceConfigChanges.length > 0) {
1937
2124
  allCategories.push({
1938
2125
  category: "workspace-config",
@@ -1944,6 +2131,110 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1944
2131
  }
1945
2132
  return orderUpdateCategories(allCategories);
1946
2133
  }
2134
+ async function resolveTargetPackageManagerSpec(options, config) {
2135
+ if (options.packageManager == null) return void 0;
2136
+ const packageManager = parsePackageManagerSpec(options.packageManager);
2137
+ if (packageManager == null) {
2138
+ console.log(color.red("\u2717") + ` Unsupported package manager: ${options.packageManager}`);
2139
+ process.exit(1);
2140
+ }
2141
+ return resolvePackageManager({
2142
+ name: config.name,
2143
+ packageManager
2144
+ });
2145
+ }
2146
+ function getPackageManagerMajorUpdateTarget(currentPackageManager, latestPackageManager) {
2147
+ if (currentPackageManager?.version == null) return void 0;
2148
+ const currentMajor = getSemverMajor(currentPackageManager.version);
2149
+ const latestMajor = getSemverMajor(latestPackageManager.version);
2150
+ if (currentMajor == null || latestMajor == null || latestMajor <= currentMajor) {
2151
+ return void 0;
2152
+ }
2153
+ return latestPackageManager;
2154
+ }
2155
+ function getRequiredNodeUpdateTarget(currentNodeVersion, requiredNodeVersion) {
2156
+ if (requiredNodeVersion == null) return void 0;
2157
+ if (currentNodeVersion == null) return requiredNodeVersion;
2158
+ return compareNumericSemver(currentNodeVersion, requiredNodeVersion) < 0 ? requiredNodeVersion : void 0;
2159
+ }
2160
+ function formatPackageManagerMajor(packageManager) {
2161
+ const major = getSemverMajor(packageManager.version);
2162
+ return major == null ? packageManager.name : `${packageManager.name}@${major}`;
2163
+ }
2164
+ async function promptForNodeRequirementUpdate(options, config, targetPackageManagerSpec) {
2165
+ if (targetPackageManagerSpec == null) return void 0;
2166
+ const profile = getPackageManagerProfile(targetPackageManagerSpec);
2167
+ const requiredNodeVersion = profile.requirements.node;
2168
+ const currentNodeVersion = config.engine?.version;
2169
+ const targetNodeVersion = getRequiredNodeUpdateTarget(currentNodeVersion, requiredNodeVersion);
2170
+ if (targetNodeVersion == null) return void 0;
2171
+ const currentNodeLabel = currentNodeVersion == null ? "not set" : `>=${currentNodeVersion}`;
2172
+ p.log.warn(
2173
+ `${formatPackageManager(targetPackageManagerSpec)} requires Node >=${targetNodeVersion}; current engines.node is ${currentNodeLabel}.`
2174
+ );
2175
+ const shouldUpdate = options.yes || await p.confirm({
2176
+ message: `Update engines.node to >=${targetNodeVersion} too?`,
2177
+ initialValue: true
2178
+ });
2179
+ if (p.isCancel(shouldUpdate)) {
2180
+ p.cancel("Operation cancelled.");
2181
+ process.exit(0);
2182
+ }
2183
+ return shouldUpdate ? targetNodeVersion : void 0;
2184
+ }
2185
+ async function promptForPackageManagerMajorUpdate(options, config) {
2186
+ if (options.packageManager != null || config.packageManagerSpec == null) return void 0;
2187
+ const currentPackageManager = config.packageManagerSpec;
2188
+ if (currentPackageManager.version == null) return void 0;
2189
+ const latestPackageManager = await resolvePackageManager({
2190
+ name: config.name,
2191
+ packageManager: { name: currentPackageManager.name }
2192
+ });
2193
+ const updateTarget = getPackageManagerMajorUpdateTarget(
2194
+ currentPackageManager,
2195
+ latestPackageManager
2196
+ );
2197
+ if (updateTarget == null) return void 0;
2198
+ const shouldUpdate = options.yes || await p.confirm({
2199
+ message: `Update ${currentPackageManager.name} from ${formatPackageManagerMajor(
2200
+ currentPackageManager
2201
+ )} to ${formatPackageManagerMajor(latestPackageManager)}?`,
2202
+ initialValue: true
2203
+ });
2204
+ if (p.isCancel(shouldUpdate)) {
2205
+ p.cancel("Operation cancelled.");
2206
+ process.exit(0);
2207
+ }
2208
+ return shouldUpdate ? updateTarget : void 0;
2209
+ }
2210
+ async function applyPackageManagerMigration(projectRoot, isMonorepo, options, detectedConfig) {
2211
+ const targetPackageManagerSpec = await resolveTargetPackageManagerSpec(options, detectedConfig) ?? await promptForPackageManagerMajorUpdate(options, detectedConfig);
2212
+ if (targetPackageManagerSpec == null) return detectedConfig;
2213
+ const targetNodeVersion = await promptForNodeRequirementUpdate(
2214
+ options,
2215
+ detectedConfig,
2216
+ targetPackageManagerSpec
2217
+ );
2218
+ const migrationConfig = {
2219
+ ...detectedConfig,
2220
+ packageManager: targetPackageManagerSpec.name,
2221
+ targetPackageManagerSpec,
2222
+ targetNodeVersion
2223
+ };
2224
+ const changes = [
2225
+ ...await getPackageManagerConfigUpdates(projectRoot, migrationConfig),
2226
+ ...isMonorepo || targetPackageManagerSpec.name === "pnpm" ? await getWorkspaceConfigUpdates(projectRoot, migrationConfig) : []
2227
+ ].filter((change) => change.status === "added" || change.status === "modified");
2228
+ if (changes.length === 0) return migrationConfig;
2229
+ console.log();
2230
+ console.log(color.cyan("Package Manager:"));
2231
+ for (const change of changes) {
2232
+ console.log(formatFileChange(change));
2233
+ }
2234
+ await applyUpdates(changes, projectRoot);
2235
+ console.log(color.green("\u2713") + ` Package Manager: updated ${changes.length}`);
2236
+ return migrationConfig;
2237
+ }
1947
2238
  async function processUpdateCategory(category, projectRoot, options) {
1948
2239
  const newChanges = category.changes.filter((change) => change.status === "added");
1949
2240
  const modifiedChanges = category.changes.filter((change) => change.status === "modified");
@@ -1983,11 +2274,8 @@ async function processUpdateCategory(category, projectRoot, options) {
1983
2274
  if (addedCount > 0) parts.push(`added ${addedCount}`);
1984
2275
  if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
1985
2276
  console.log(color.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
1986
- console.log();
1987
2277
  return "updated";
1988
2278
  }
1989
- console.log(color.dim(` Skipped ${category.label}`));
1990
- console.log();
1991
2279
  return "skipped";
1992
2280
  }
1993
2281
  async function handleUpdateCommand(options, handleFixCommand) {
@@ -2053,6 +2341,8 @@ async function handleUpdateCommand(options, handleFixCommand) {
2053
2341
  console.log(color.dim(` Skipped ${skippedCount}`));
2054
2342
  }
2055
2343
  }
2344
+ const finalConfig = await applyPackageManagerMigration(projectRoot, isMonorepo, options, config);
2345
+ await promptForPackageUpdate(projectRoot, finalConfig, options);
2056
2346
  process.exit(0);
2057
2347
  }
2058
2348
 
@@ -2070,12 +2360,13 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2070
2360
  message: "Package name?",
2071
2361
  initialValue: `@${scope}/`,
2072
2362
  validate: (value) => {
2073
- const validationError = validatePackageName(value);
2363
+ const packageName = value ?? "";
2364
+ const validationError = validatePackageName(packageName);
2074
2365
  if (validationError) return validationError;
2075
- const dirName = value.includes("/") ? value.split("/").pop() : value;
2366
+ const dirName = getPackageDirectoryName(packageName);
2076
2367
  if (!dirName) return "Package name is required";
2077
2368
  if (!hasCustomDirectories) {
2078
- const targetPath = join$1(monorepoRoot, defaultDir, dirName);
2369
+ const targetPath = join(monorepoRoot, defaultDir, dirName);
2079
2370
  try {
2080
2371
  const { statSync } = require$2("fs");
2081
2372
  statSync(targetPath);
@@ -2089,7 +2380,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2089
2380
  return false;
2090
2381
  }
2091
2382
  const scopedName = packageNameInput;
2092
- const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
2383
+ const shortName = getPackageDirectoryName(scopedName);
2093
2384
  const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
2094
2385
  let targetDir = defaultDir;
2095
2386
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -2105,7 +2396,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2105
2396
  return false;
2106
2397
  }
2107
2398
  targetDir = dirChoice;
2108
- const targetPath = join$1(monorepoRoot, targetDir, shortName);
2399
+ const targetPath = join(monorepoRoot, targetDir, shortName);
2109
2400
  try {
2110
2401
  const { statSync } = require$2("fs");
2111
2402
  statSync(targetPath);
@@ -2114,7 +2405,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2114
2405
  } catch {
2115
2406
  }
2116
2407
  }
2117
- const relativePkgPath = join$1(targetDir, shortName);
2408
+ const relativePkgPath = join(targetDir, shortName);
2118
2409
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2119
2410
  packageOptions.workspaceRoot = workspaceRoot;
2120
2411
  packageOptions.name = scopedName;
@@ -2129,7 +2420,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2129
2420
  packageOptions.workspaceDependencies = selectedDeps;
2130
2421
  }
2131
2422
  }
2132
- const outputPath = join$1(monorepoRoot, relativePkgPath);
2423
+ const outputPath = join(monorepoRoot, relativePkgPath);
2133
2424
  const spinner = p.spinner();
2134
2425
  spinner.start("Creating package...");
2135
2426
  try {
@@ -2170,10 +2461,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2170
2461
  const template = options.template ?? "vanilla";
2171
2462
  const baseTemplate = getBaseTemplate(template);
2172
2463
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2173
- const fullPackagePath = join$1(monorepoRoot, targetDir, name);
2464
+ const packageDirName = getPackageDirectoryName(name);
2465
+ const fullPackagePath = join(monorepoRoot, targetDir, packageDirName);
2174
2466
  try {
2175
- await access$1(fullPackagePath, constants$1.F_OK);
2176
- console.error(color.red("Error:") + ` Directory ${targetDir}/${name} already exists`);
2467
+ await access(fullPackagePath, constants.F_OK);
2468
+ console.error(color.red("Error:") + ` Directory ${targetDir}/${packageDirName} already exists`);
2177
2469
  process.exit(1);
2178
2470
  } catch {
2179
2471
  }
@@ -2186,7 +2478,7 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2186
2478
  };
2187
2479
  const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
2188
2480
  const isLibrary = projectType === "library";
2189
- const relativePkgPath = join$1(targetDir, name);
2481
+ const relativePkgPath = join(targetDir, packageDirName);
2190
2482
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2191
2483
  const projectOptions = {
2192
2484
  name: scopedName,
@@ -2214,11 +2506,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2214
2506
  triplex: options.triplex ? {} : void 0
2215
2507
  }
2216
2508
  };
2217
- console.log(color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2509
+ console.log(color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${packageDirName}...`);
2218
2510
  try {
2219
2511
  const { files } = await planProject(resolveProjectPlanInput(projectOptions));
2220
2512
  await writeGeneratedFiles(fullPackagePath, files);
2221
- console.log(color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2513
+ console.log(color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${packageDirName}`);
2222
2514
  process.exit(0);
2223
2515
  } catch (error) {
2224
2516
  console.error(color.red("Error:") + " Failed to create package");
@@ -2288,7 +2580,7 @@ function hasConfigOptions(options) {
2288
2580
  async function writeGeneratedFiles(basePath, files) {
2289
2581
  const filePaths = Object.keys(files).sort();
2290
2582
  for (const filePath of filePaths) {
2291
- const fullFilePath = join$1(basePath, filePath);
2583
+ const fullFilePath = join(basePath, filePath);
2292
2584
  await mkdir(dirname(fullFilePath), { recursive: true });
2293
2585
  const file = files[filePath];
2294
2586
  if (file.type === "text") {
@@ -2301,7 +2593,7 @@ async function writeGeneratedFiles(basePath, files) {
2301
2593
  }
2302
2594
  async function handleMonorepoCreation(projectOptions, isNonInteractive) {
2303
2595
  const packageManager = getPackageManagerName(projectOptions.packageManager);
2304
- const projectPath = join$1(cwd(), projectOptions.name);
2596
+ const projectPath = join(cwd(), projectOptions.name);
2305
2597
  const spinner = p.spinner();
2306
2598
  spinner.start("Creating monorepo workspace...");
2307
2599
  try {
@@ -2363,7 +2655,8 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2363
2655
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
2364
2656
  projectOptions.name ??= defaultFallbackName;
2365
2657
  const packageManager = getPackageManagerName(projectOptions.packageManager);
2366
- const projectPath = join$1(cwd(), projectOptions.name);
2658
+ const projectDirName = getPackageDirectoryName(projectOptions.name);
2659
+ const projectPath = join(cwd(), projectDirName);
2367
2660
  const spinner = p.spinner();
2368
2661
  spinner.start("Creating project...");
2369
2662
  try {
@@ -2372,15 +2665,11 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2372
2665
  await writeGeneratedFiles(projectPath, files);
2373
2666
  spinner.stop(color.green.inverse(" \u2713 Project created! "));
2374
2667
  if (isNonInteractive) process.exit(0);
2375
- const nextSteps = projectOptions.projectType === "library" ? [
2376
- `cd ${projectOptions.name}`,
2377
- `${packageManager} install`,
2378
- `${packageManager} run build`
2379
- ].join("\n") : [
2380
- `cd ${projectOptions.name}`,
2381
- `${packageManager} install`,
2382
- `${packageManager} run dev`
2383
- ].join("\n");
2668
+ const nextSteps = projectOptions.projectType === "library" ? [`cd ${projectDirName}`, `${packageManager} install`, `${packageManager} run build`].join(
2669
+ "\n"
2670
+ ) : [`cd ${projectDirName}`, `${packageManager} install`, `${packageManager} run dev`].join(
2671
+ "\n"
2672
+ );
2384
2673
  p.note(nextSteps, "Next steps");
2385
2674
  p.outro(color.green("Happy coding! \u2728"));
2386
2675
  } catch (error) {
@@ -2396,7 +2685,7 @@ async function main() {
2396
2685
  ).option(
2397
2686
  "--template <type>",
2398
2687
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2399
- ).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: prettier)").option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option("--package-manager <manager>", "specify package manager (e.g. npm, yarn, pnpm)").option("--ide <ide>", "IDE files: vscode or none (default: vscode)").option(
2688
+ ).option("--linter <type>", "linter: eslint, oxlint, or biome (default: oxlint)").option("--formatter <type>", "formatter: prettier, oxfmt, or biome (default: prettier)").option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option("--package-manager <manager>", "specify package manager (e.g. npm, yarn, pnpm@11)").option("--ide <ide>", "IDE files: vscode or none (default: vscode)").option(
2400
2689
  "--pnpm-manage-versions",
2401
2690
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2402
2691
  ).option(
@@ -2422,6 +2711,12 @@ async function main() {
2422
2711
  console.error(color.red("Error:") + ' --ide must be "vscode" or "none"');
2423
2712
  process.exit(1);
2424
2713
  }
2714
+ const packageManagerSpec = parsePackageManagerSpec(options.packageManager);
2715
+ if (options.packageManager && packageManagerSpec == null) {
2716
+ console.error(color.red("Error:") + " --package-manager must be npm, yarn, or pnpm");
2717
+ console.log(color.dim(" Version specs are allowed, e.g. pnpm@10 or pnpm@11"));
2718
+ process.exit(1);
2719
+ }
2425
2720
  if (name?.startsWith("-")) {
2426
2721
  switch (name) {
2427
2722
  case "--version":
@@ -2507,7 +2802,7 @@ async function main() {
2507
2802
  viverse: options.viverse ? {} : void 0,
2508
2803
  triplex: options.triplex ? {} : void 0
2509
2804
  },
2510
- packageManager: options.packageManager ? { name: options.packageManager } : void 0,
2805
+ packageManager: packageManagerSpec,
2511
2806
  pnpmManageVersions: options.pnpmManageVersions,
2512
2807
  engine: { name: "node", version: options.nodeVersion ?? "latest" }
2513
2808
  };
@@ -2518,7 +2813,7 @@ async function main() {
2518
2813
  bundler: options.bundler,
2519
2814
  linter: options.linter,
2520
2815
  formatter: options.formatter,
2521
- packageManager: options.packageManager,
2816
+ packageManager: packageManagerSpec,
2522
2817
  ide: options.ide,
2523
2818
  engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2524
2819
  pnpmManageVersions: options.pnpmManageVersions,