create-krispya 0.13.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.B2px1YOh.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 = [];
@@ -113,8 +114,9 @@ function formatMonorepoConfigSummary(options) {
113
114
  lines.push(
114
115
  formatRow("Engine", `${getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
115
116
  );
116
- lines.push(formatRow("Package manager", options.packageManager));
117
- if (options.packageManager === "pnpm") {
117
+ const packageManagerName = getPackageManagerName(options.packageManager);
118
+ lines.push(formatRow("Package manager", packageManagerName));
119
+ if (packageManagerName === "pnpm") {
118
120
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
119
121
  lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
120
122
  }
@@ -124,22 +126,6 @@ function formatMonorepoConfigSummary(options) {
124
126
  return lines.join("\n");
125
127
  }
126
128
 
127
- const config = new Conf({
128
- projectName: "create-krispya"
129
- });
130
- function getAiPlatforms() {
131
- return config.get("aiPlatforms");
132
- }
133
- function getConfigStrategy() {
134
- return config.get("configStrategy") ?? "stealth";
135
- }
136
- function clearConfig() {
137
- config.clear();
138
- }
139
- function getConfigPath() {
140
- return config.path;
141
- }
142
-
143
129
  const R3F_INTEGRATION_OPTIONS = [
144
130
  { value: "drei", label: "Drei" },
145
131
  { value: "handle", label: "Handle" },
@@ -253,7 +239,7 @@ async function promptForCustomization(template, name, projectType, features, inh
253
239
  libraryBundler = bundler;
254
240
  }
255
241
  let engine = inheritedSettings?.engine ?? presets?.engine ?? { name: "node", version: "latest" };
256
- let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager ?? "pnpm";
242
+ let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager?.name ?? "pnpm";
257
243
  let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
258
244
  if (!inheritedSettings?.engine?.version) {
259
245
  const nodeVersionInput = await p.text({
@@ -261,7 +247,7 @@ async function promptForCustomization(template, name, projectType, features, inh
261
247
  placeholder: presets?.engine?.version ?? "latest",
262
248
  defaultValue: presets?.engine?.version ?? "latest",
263
249
  validate: (value) => {
264
- if (!value.length) return "Required";
250
+ if (!value?.length) return "Required";
265
251
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
266
252
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
267
253
  }
@@ -281,7 +267,7 @@ async function promptForCustomization(template, name, projectType, features, inh
281
267
  { value: "npm", label: "npm" },
282
268
  { value: "yarn", label: "yarn" }
283
269
  ],
284
- initialValue: presets?.packageManager ?? "pnpm"
270
+ initialValue: presets?.packageManager?.name ?? "pnpm"
285
271
  });
286
272
  if (p.isCancel(packageManager)) {
287
273
  p.cancel("Operation cancelled.");
@@ -390,7 +376,7 @@ async function promptForCustomization(template, name, projectType, features, inh
390
376
  projectType,
391
377
  libraryBundler: projectType === "library" ? libraryBundler : void 0,
392
378
  engine,
393
- packageManager: { name: finalPackageManager },
379
+ packageManager: presets?.packageManager?.name === finalPackageManager ? presets.packageManager : { name: finalPackageManager },
394
380
  pnpmManageVersions,
395
381
  linter,
396
382
  formatter,
@@ -437,7 +423,7 @@ async function promptForMonorepoCustomization(name, presets) {
437
423
  placeholder: presets?.engine?.version ?? "latest",
438
424
  defaultValue: presets?.engine?.version ?? "latest",
439
425
  validate: (value) => {
440
- if (!value.length) return "Required";
426
+ if (!value?.length) return "Required";
441
427
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
442
428
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
443
429
  }
@@ -518,7 +504,7 @@ async function promptForMonorepo(workspaceName, presets) {
518
504
  formatMonorepoConfigSummary({
519
505
  name: defaultOptions.name,
520
506
  engine: defaultOptions.engine ?? { name: "node", version: "latest" },
521
- packageManager: getPackageManagerName(defaultOptions.packageManager),
507
+ packageManager: defaultOptions.packageManager ?? { name: "pnpm" },
522
508
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
523
509
  linter: defaultOptions.linter ?? "oxlint",
524
510
  formatter: defaultOptions.formatter ?? "prettier",
@@ -539,7 +525,7 @@ async function promptForOptions(name, presets) {
539
525
  placeholder: generateRandomName(),
540
526
  defaultValue: generateRandomName(),
541
527
  validate: (value) => {
542
- if (!value.length) return "Project name is required";
528
+ if (!value?.length) return "Project name is required";
543
529
  }
544
530
  });
545
531
  if (p.isCancel(nameResult)) {
@@ -571,7 +557,7 @@ function presetsToInheritedSettings(presets) {
571
557
  return {
572
558
  linter: presets.linter,
573
559
  formatter: presets.formatter,
574
- packageManager: presets.packageManager ? { name: presets.packageManager } : void 0,
560
+ packageManager: presets.packageManager,
575
561
  engine: presets.engine,
576
562
  pnpmManageVersions: presets.pnpmManageVersions
577
563
  };
@@ -656,54 +642,9 @@ async function promptForAiAgentPlatforms(isNonInteractive) {
656
642
  return selected;
657
643
  }
658
644
 
659
- async function checkAnyExists(paths) {
660
- for (const path of paths) {
661
- try {
662
- await access(path, constants.F_OK);
663
- return true;
664
- } catch {
665
- }
666
- }
667
- return false;
668
- }
669
- async function validateWorkspace(monorepoRoot) {
670
- const errors = [];
671
- const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
672
- try {
673
- await access(tsConfigPath, constants.F_OK);
674
- } catch {
675
- errors.push("Missing .config/typescript package");
676
- }
677
- const linterPaths = [
678
- join(monorepoRoot, ".config/oxlint/package.json"),
679
- join(monorepoRoot, ".config/eslint/package.json"),
680
- join(monorepoRoot, "eslint.config.js"),
681
- join(monorepoRoot, "biome.json")
682
- ];
683
- const hasLinter = await checkAnyExists(linterPaths);
684
- if (!hasLinter) {
685
- errors.push(
686
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
687
- );
688
- }
689
- const formatterPaths = [
690
- join(monorepoRoot, ".config/oxfmt/package.json"),
691
- join(monorepoRoot, ".config/prettier/package.json"),
692
- join(monorepoRoot, ".prettierrc.json"),
693
- join(monorepoRoot, "biome.json")
694
- ];
695
- const hasFormatter = await checkAnyExists(formatterPaths);
696
- if (!hasFormatter) {
697
- errors.push(
698
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
699
- );
700
- }
701
- return { valid: errors.length === 0, errors };
702
- }
703
-
704
645
  async function fileExists$1(path) {
705
646
  try {
706
- await access$1(path, constants$1.F_OK);
647
+ await access(path, constants.F_OK);
707
648
  return true;
708
649
  } catch {
709
650
  return false;
@@ -717,9 +658,9 @@ async function detectMonorepoRoot() {
717
658
  let currentDir = cwd();
718
659
  const root = resolve("/");
719
660
  while (currentDir !== root) {
720
- const workspaceFile = join$1(currentDir, "pnpm-workspace.yaml");
661
+ const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
721
662
  try {
722
- await access$1(workspaceFile, constants$1.F_OK);
663
+ await access(workspaceFile, constants.F_OK);
723
664
  const content = await readFile(workspaceFile, "utf-8");
724
665
  if (content.includes("packages:")) {
725
666
  return currentDir;
@@ -734,16 +675,16 @@ async function detectPackageRoot() {
734
675
  let currentDir = cwd();
735
676
  const root = resolve("/");
736
677
  while (currentDir !== root) {
737
- if (await fileExists$1(join$1(currentDir, "package.json"))) {
678
+ if (await fileExists$1(join(currentDir, "package.json"))) {
738
679
  return currentDir;
739
680
  }
740
681
  currentDir = dirname(currentDir);
741
682
  }
742
- return await fileExists$1(join$1(root, "package.json")) ? root : null;
683
+ return await fileExists$1(join(root, "package.json")) ? root : null;
743
684
  }
744
685
  async function parseWorkspaceDirectories(monorepoRoot) {
745
686
  try {
746
- const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
687
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
747
688
  const content = await readFile(workspaceFile, "utf-8");
748
689
  return parseWorkspaceYamlContent(content);
749
690
  } catch {
@@ -753,14 +694,14 @@ async function parseWorkspaceDirectories(monorepoRoot) {
753
694
  async function detectWorkspaceSettings(monorepoRoot) {
754
695
  try {
755
696
  const tooling = await detectTooling(monorepoRoot);
756
- const pkgPath = join$1(monorepoRoot, "package.json");
697
+ const pkgPath = join(monorepoRoot, "package.json");
757
698
  const content = await readFile(pkgPath, "utf-8");
758
699
  const pkgJson = JSON.parse(content);
759
- const packageManager = parsePackageManager(pkgJson.packageManager);
700
+ const packageManager = parsePackageManagerSpec(pkgJson.packageManager);
760
701
  const engine = parseEngine(pkgJson.engines);
761
702
  let pnpmManageVersions;
762
703
  try {
763
- const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
704
+ const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
764
705
  const workspaceContent = await readFile(workspaceFile, "utf-8");
765
706
  pnpmManageVersions = workspaceContent.includes("manage-package-manager-versions: true");
766
707
  } catch {
@@ -778,17 +719,17 @@ async function detectWorkspaceSettings(monorepoRoot) {
778
719
  }
779
720
  async function detectExistingConfigs(monorepoRoot) {
780
721
  const configs = {};
781
- const eslintPath = join$1(monorepoRoot, "eslint.config.js");
722
+ const eslintPath = join(monorepoRoot, "eslint.config.js");
782
723
  if (await fileExists$1(eslintPath)) {
783
724
  configs.linter = "eslint";
784
725
  configs.eslintConfigPath = eslintPath;
785
726
  }
786
- const prettierPath = join$1(monorepoRoot, ".prettierrc.json");
727
+ const prettierPath = join(monorepoRoot, ".prettierrc.json");
787
728
  if (await fileExists$1(prettierPath)) {
788
729
  configs.formatter = "prettier";
789
730
  configs.prettierConfigPath = prettierPath;
790
731
  }
791
- const biomePath = join$1(monorepoRoot, "biome.json");
732
+ const biomePath = join(monorepoRoot, "biome.json");
792
733
  if (await fileExists$1(biomePath)) {
793
734
  configs.biomeConfigPath = biomePath;
794
735
  if (!configs.linter) configs.linter = "biome";
@@ -798,7 +739,7 @@ async function detectExistingConfigs(monorepoRoot) {
798
739
  }
799
740
  async function getMonorepoScope(monorepoRoot) {
800
741
  try {
801
- const pkgPath = join$1(monorepoRoot, "package.json");
742
+ const pkgPath = join(monorepoRoot, "package.json");
802
743
  const content = await readFile(pkgPath, "utf-8");
803
744
  const pkgJson = JSON.parse(content);
804
745
  if (pkgJson.name) {
@@ -809,14 +750,14 @@ async function getMonorepoScope(monorepoRoot) {
809
750
  return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
810
751
  }
811
752
  async function getWorkspacePackages(monorepoRoot) {
812
- const packagesDir = join$1(monorepoRoot, "packages");
753
+ const packagesDir = join(monorepoRoot, "packages");
813
754
  try {
814
755
  const entries = await readdir(packagesDir, { withFileTypes: true });
815
756
  const names = [];
816
757
  for (const entry of entries) {
817
758
  if (!entry.isDirectory()) continue;
818
759
  try {
819
- 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");
820
761
  const pkg = JSON.parse(content);
821
762
  if (pkg.name) names.push(pkg.name);
822
763
  } catch {
@@ -828,7 +769,7 @@ async function getWorkspacePackages(monorepoRoot) {
828
769
  }
829
770
  }
830
771
  async function ensureConfigInWorkspace(monorepoRoot) {
831
- const workspacePath = join$1(monorepoRoot, "pnpm-workspace.yaml");
772
+ const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
832
773
  let content;
833
774
  try {
834
775
  content = await readFile(workspacePath, "utf-8");
@@ -878,7 +819,7 @@ async function handleCheckCommand() {
878
819
 
879
820
  async function migrateEslintConfig(monorepoRoot, files) {
880
821
  const configBasePath = ".config/eslint";
881
- const existingConfigPath = join$1(monorepoRoot, "eslint.config.js");
822
+ const existingConfigPath = join(monorepoRoot, "eslint.config.js");
882
823
  let existingContent;
883
824
  try {
884
825
  existingContent = await readFile(existingConfigPath, "utf-8");
@@ -957,7 +898,7 @@ export default [
957
898
  }
958
899
  async function migratePrettierConfig(monorepoRoot, files) {
959
900
  const configBasePath = ".config/prettier";
960
- const existingConfigPath = join$1(monorepoRoot, ".prettierrc.json");
901
+ const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
961
902
  let existingContent;
962
903
  try {
963
904
  existingContent = await readFile(existingConfigPath, "utf-8");
@@ -1093,15 +1034,15 @@ async function handleFixCommand(options) {
1093
1034
  spinner.start("Fixing workspace...");
1094
1035
  try {
1095
1036
  const files = {};
1096
- const tsConfigExists = await fileExists$1(join$1(monorepoRoot, ".config/typescript/package.json"));
1037
+ const tsConfigExists = await fileExists$1(join(monorepoRoot, ".config/typescript/package.json"));
1097
1038
  if (!tsConfigExists) {
1098
1039
  renderTypescriptConfigPackage(files);
1099
1040
  }
1100
1041
  if (linter === "oxlint") {
1101
- const oxlintExists = await fileExists$1(join$1(monorepoRoot, ".config/oxlint/package.json"));
1042
+ const oxlintExists = await fileExists$1(join(monorepoRoot, ".config/oxlint/package.json"));
1102
1043
  if (!oxlintExists) renderOxlintConfigPackage(files);
1103
1044
  } else if (linter === "eslint") {
1104
- const eslintPkgExists = await fileExists$1(join$1(monorepoRoot, ".config/eslint/package.json"));
1045
+ const eslintPkgExists = await fileExists$1(join(monorepoRoot, ".config/eslint/package.json"));
1105
1046
  if (!eslintPkgExists) {
1106
1047
  if (existingConfigs.eslintConfigPath) {
1107
1048
  await migrateEslintConfig(monorepoRoot, files);
@@ -1111,10 +1052,10 @@ async function handleFixCommand(options) {
1111
1052
  }
1112
1053
  }
1113
1054
  if (formatter === "oxfmt") {
1114
- const oxfmtExists = await fileExists$1(join$1(monorepoRoot, ".config/oxfmt/package.json"));
1055
+ const oxfmtExists = await fileExists$1(join(monorepoRoot, ".config/oxfmt/package.json"));
1115
1056
  if (!oxfmtExists) renderOxfmtConfigPackage(files);
1116
1057
  } else if (formatter === "prettier") {
1117
- const prettierPkgExists = await fileExists$1(join$1(monorepoRoot, ".config/prettier/package.json"));
1058
+ const prettierPkgExists = await fileExists$1(join(monorepoRoot, ".config/prettier/package.json"));
1118
1059
  if (!prettierPkgExists) {
1119
1060
  if (existingConfigs.prettierConfigPath) {
1120
1061
  await migratePrettierConfig(monorepoRoot, files);
@@ -1152,7 +1093,7 @@ async function handleFixCommand(options) {
1152
1093
  };
1153
1094
  }
1154
1095
  for (const [filePath, file] of Object.entries(files)) {
1155
- const fullPath = join$1(monorepoRoot, filePath);
1096
+ const fullPath = join(monorepoRoot, filePath);
1156
1097
  await mkdir(dirname(fullPath), { recursive: true });
1157
1098
  await writeFile(fullPath, file.content);
1158
1099
  }
@@ -1175,8 +1116,8 @@ async function handleFixCommand(options) {
1175
1116
  const pkgName = pkgFile.replace("/package.json", "");
1176
1117
  console.log(color.dim(` Generated ${pkgName}`));
1177
1118
  }
1178
- const vscodeSettingsExists = await fileExists$1(join$1(monorepoRoot, ".vscode/settings.json"));
1179
- 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"));
1180
1121
  const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
1181
1122
  if (!vscodeExists) {
1182
1123
  let addVscode = false;
@@ -1193,7 +1134,7 @@ async function handleFixCommand(options) {
1193
1134
  const vscodeFiles = {};
1194
1135
  renderVscodeFiles(vscodeFiles, linter, formatter);
1195
1136
  for (const [filePath, file] of Object.entries(vscodeFiles)) {
1196
- const fullPath = join$1(monorepoRoot, filePath);
1137
+ const fullPath = join(monorepoRoot, filePath);
1197
1138
  await mkdir(dirname(fullPath), { recursive: true });
1198
1139
  await writeFile(fullPath, file.content);
1199
1140
  }
@@ -1201,7 +1142,7 @@ async function handleFixCommand(options) {
1201
1142
  console.log(color.dim(" Generated .vscode/extensions.json"));
1202
1143
  }
1203
1144
  }
1204
- const aiRulesExist = await fileExists$1(join$1(monorepoRoot, ".ai/workspace.md"));
1145
+ const aiRulesExist = await fileExists$1(join(monorepoRoot, ".ai/workspace.md"));
1205
1146
  if (!aiRulesExist) {
1206
1147
  const platforms = await promptForAiAgentPlatforms(isNonInteractive);
1207
1148
  if (platforms.length > 0) {
@@ -1217,7 +1158,7 @@ async function handleFixCommand(options) {
1217
1158
  platforms
1218
1159
  });
1219
1160
  for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1220
- const fullPath = join$1(monorepoRoot, filePath);
1161
+ const fullPath = join(monorepoRoot, filePath);
1221
1162
  await mkdir(dirname(fullPath), { recursive: true });
1222
1163
  await writeFile(fullPath, file.content);
1223
1164
  console.log(color.dim(` Generated ${filePath}`));
@@ -1261,18 +1202,22 @@ function renderExpectedViteConfig(template) {
1261
1202
  async function detectCurrentConfig(root, isMonorepo = true) {
1262
1203
  let name = root.split(/[/\\]/).pop() ?? "workspace";
1263
1204
  let packageManager = "pnpm";
1205
+ let packageManagerSpec = { name: "pnpm" };
1206
+ let engine;
1264
1207
  let hasTypecheck = false;
1265
1208
  let viteTemplate;
1266
1209
  try {
1267
- const pkgPath = join(root, "package.json");
1210
+ const pkgPath = join$1(root, "package.json");
1268
1211
  const content = await readFile$1(pkgPath, "utf-8");
1269
1212
  const pkgJson = JSON.parse(content);
1270
1213
  if (pkgJson.name) {
1271
1214
  name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
1272
1215
  }
1273
1216
  if (pkgJson.packageManager) {
1274
- packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
1217
+ packageManagerSpec = parsePackageManagerSpec(pkgJson.packageManager) ?? packageManagerSpec;
1218
+ packageManager = packageManagerSpec.name;
1275
1219
  }
1220
+ engine = parseEngine(pkgJson.engines);
1276
1221
  hasTypecheck = pkgJson.scripts?.typecheck != null;
1277
1222
  viteTemplate = detectViteTemplate(pkgJson);
1278
1223
  } catch {
@@ -1284,6 +1229,8 @@ async function detectCurrentConfig(root, isMonorepo = true) {
1284
1229
  linter: tooling.linter ?? "oxlint",
1285
1230
  formatter: tooling.formatter ?? "prettier",
1286
1231
  packageManager,
1232
+ packageManagerSpec,
1233
+ engine,
1287
1234
  isMonorepo,
1288
1235
  configStrategy,
1289
1236
  hasTypecheck,
@@ -1292,10 +1239,10 @@ async function detectCurrentConfig(root, isMonorepo = true) {
1292
1239
  }
1293
1240
  async function detectSinglePackageConfigStrategy(root) {
1294
1241
  const hasStealthConfig = await Promise.all([
1295
- fileExists(join(root, ".config/tsconfig.app.json")),
1296
- fileExists(join(root, ".config/tsconfig.node.json")),
1297
- fileExists(join(root, ".config/prettier.json")),
1298
- 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"))
1299
1246
  ]).then((matches) => matches.some(Boolean));
1300
1247
  return hasStealthConfig ? "stealth" : "root";
1301
1248
  }
@@ -1390,7 +1337,7 @@ async function planExpectedFiles(config) {
1390
1337
  }
1391
1338
  async function fileExists(path) {
1392
1339
  try {
1393
- await access(path, constants$2.F_OK);
1340
+ await access$1(path, constants$1.F_OK);
1394
1341
  return true;
1395
1342
  } catch {
1396
1343
  return false;
@@ -1529,7 +1476,7 @@ async function compareWithDisk(expected, root) {
1529
1476
  const changes = [];
1530
1477
  for (const [filePath, file] of Object.entries(files)) {
1531
1478
  if (file.type !== "text") continue;
1532
- const fullPath = join(root, filePath);
1479
+ const fullPath = join$1(root, filePath);
1533
1480
  const newContent = file.content;
1534
1481
  if (await fileExists(fullPath)) {
1535
1482
  const currentContent = await readFile$1(fullPath, "utf-8");
@@ -1604,13 +1551,13 @@ function addMissingDevDependency(pkg, devDependencies, name) {
1604
1551
  }
1605
1552
  async function detectTypeScriptPackage(root, pkg) {
1606
1553
  if (hasPackage(pkg, "typescript")) return true;
1607
- 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"));
1608
1555
  }
1609
1556
  function detectLibraryPackage(pkg) {
1610
1557
  return pkg.exports != null || pkg.main?.includes("dist") === true || pkg.module?.includes("dist") === true || Array.isArray(pkg.files) && pkg.files.includes("dist");
1611
1558
  }
1612
1559
  function getPackageManagerForScripts(config, pkg) {
1613
- const packageManager = pkg.packageManager?.split("@")[0] ?? config.packageManager;
1560
+ const packageManager = parsePackageManagerSpec(pkg.packageManager)?.name ?? config.packageManager;
1614
1561
  return isPackageManagerName(packageManager) ? packageManager : "pnpm";
1615
1562
  }
1616
1563
  function getSinglePackageToolScripts(config) {
@@ -1640,6 +1587,35 @@ function scriptsEqual(left, right) {
1640
1587
  if (leftEntries.length !== Object.keys(right).length) return false;
1641
1588
  return leftEntries.every(([key, value]) => right[key] === value);
1642
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
+ }
1643
1619
  async function getExpectedPackageScripts(root, config, pkg) {
1644
1620
  if (config.isMonorepo) {
1645
1621
  return packageJsonScripts.monorepoRoot(config.linter, config.formatter);
@@ -1678,7 +1654,7 @@ async function getExpectedPackageDevDependencies(root, config, pkg) {
1678
1654
  return sortPackageMap(nextDevDependencies);
1679
1655
  }
1680
1656
  async function getPackageJsonScriptUpdates(root, config) {
1681
- const packageJsonPath = join(root, "package.json");
1657
+ const packageJsonPath = join$1(root, "package.json");
1682
1658
  let currentContent;
1683
1659
  try {
1684
1660
  currentContent = await readFile$1(packageJsonPath, "utf-8");
@@ -1691,7 +1667,9 @@ async function getPackageJsonScriptUpdates(root, config) {
1691
1667
  const nextScripts = mergePackageJsonScripts(currentScripts, expectedScripts);
1692
1668
  const currentDevDependencies = pkg.devDependencies ?? {};
1693
1669
  const nextDevDependencies = await getExpectedPackageDevDependencies(root, config, pkg);
1694
- 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)) {
1695
1673
  return [
1696
1674
  {
1697
1675
  path: "package.json",
@@ -1701,10 +1679,14 @@ async function getPackageJsonScriptUpdates(root, config) {
1701
1679
  }
1702
1680
  ];
1703
1681
  }
1704
- const nextPackageJson = {
1705
- ...pkg,
1706
- scripts: nextScripts
1707
- };
1682
+ const nextPackageJson = applyPackageManagerFields(
1683
+ {
1684
+ ...pkg,
1685
+ scripts: nextScripts
1686
+ },
1687
+ targetPackageManagerSpec,
1688
+ targetNodeVersion
1689
+ );
1708
1690
  if (Object.keys(nextDevDependencies).length > 0 || pkg.devDependencies != null) {
1709
1691
  nextPackageJson.devDependencies = nextDevDependencies;
1710
1692
  }
@@ -1719,6 +1701,41 @@ async function getPackageJsonScriptUpdates(root, config) {
1719
1701
  }
1720
1702
  ];
1721
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
+ }
1722
1739
  function planSinglePackageOxlintConfig(config) {
1723
1740
  if (config.linter !== "oxlint" || config.isMonorepo) return void 0;
1724
1741
  const isStealth = (config.configStrategy ?? "stealth") === "stealth";
@@ -1739,7 +1756,7 @@ function planSinglePackageOxlintConfig(config) {
1739
1756
  async function getOxlintConfigReplacementUpdates(root, config) {
1740
1757
  const expected = planSinglePackageOxlintConfig(config);
1741
1758
  if (expected == null) return [];
1742
- const fullPath = join(root, expected.path);
1759
+ const fullPath = join$1(root, expected.path);
1743
1760
  let currentContent;
1744
1761
  try {
1745
1762
  currentContent = await readFile$1(fullPath, "utf-8");
@@ -1764,9 +1781,105 @@ async function getOxlintConfigReplacementUpdates(root, config) {
1764
1781
  }
1765
1782
  ];
1766
1783
  }
1767
- async function getWorkspaceConfigUpdates(root) {
1768
- 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");
1769
1879
  const changes = [];
1880
+ const packageManagerSpec = config?.targetPackageManagerSpec ?? config?.packageManagerSpec ?? { name: "pnpm" };
1881
+ const profile = getPackageManagerProfile(packageManagerSpec);
1882
+ const defaultPackages = [".config/*", "apps/*", "packages/*"];
1770
1883
  let currentContent = "";
1771
1884
  let exists = false;
1772
1885
  try {
@@ -1775,15 +1888,11 @@ async function getWorkspaceConfigUpdates(root) {
1775
1888
  } catch {
1776
1889
  }
1777
1890
  if (!exists) {
1778
- const newContent = `manage-package-manager-versions: true
1779
-
1780
- packages:
1781
- - '.config/*'
1782
- - 'apps/*'
1783
- - 'packages/*'
1784
-
1785
- onlyBuiltDependencies:
1786
- - esbuild
1891
+ const newContent = `${renderPnpmWorkspaceConfig({
1892
+ profile,
1893
+ manageVersions: true,
1894
+ packages: defaultPackages
1895
+ })}
1787
1896
  `;
1788
1897
  changes.push({
1789
1898
  path: "pnpm-workspace.yaml",
@@ -1792,32 +1901,14 @@ onlyBuiltDependencies:
1792
1901
  });
1793
1902
  return changes;
1794
1903
  }
1795
- let updatedContent = currentContent;
1796
- let needsUpdate = false;
1797
- if (!currentContent.includes("manage-package-manager-versions")) {
1798
- updatedContent = `manage-package-manager-versions: true
1799
-
1800
- ${updatedContent}`;
1801
- needsUpdate = true;
1802
- }
1803
- if (!currentContent.includes("onlyBuiltDependencies")) {
1804
- updatedContent = `${updatedContent.trimEnd()}
1805
-
1806
- onlyBuiltDependencies:
1807
- - esbuild
1904
+ const buildDependencies = withDefaultBuildDependencies(extractBuildDependencies(currentContent));
1905
+ const updatedContent = `${patchPnpmWorkspaceManagedKeys(
1906
+ currentContent,
1907
+ profile,
1908
+ buildDependencies
1909
+ )}
1808
1910
  `;
1809
- needsUpdate = true;
1810
- }
1811
- if (!currentContent.includes(".config/*")) {
1812
- const lines = updatedContent.split("\n");
1813
- const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1814
- if (packagesIndex !== -1) {
1815
- lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1816
- updatedContent = lines.join("\n");
1817
- needsUpdate = true;
1818
- }
1819
- }
1820
- if (needsUpdate) {
1911
+ if (updatedContent !== currentContent) {
1821
1912
  changes.push({
1822
1913
  path: "pnpm-workspace.yaml",
1823
1914
  status: "modified",
@@ -1837,7 +1928,7 @@ onlyBuiltDependencies:
1837
1928
  async function applyUpdates(changes, root) {
1838
1929
  for (const change of changes) {
1839
1930
  if (change.status === "unchanged") continue;
1840
- const fullPath = join(root, change.path);
1931
+ const fullPath = join$1(root, change.path);
1841
1932
  await mkdir$1(dirname$1(fullPath), { recursive: true });
1842
1933
  await writeFile$1(fullPath, change.newContent);
1843
1934
  }
@@ -1915,6 +2006,96 @@ function orderUpdateCategories(categories) {
1915
2006
  (left, right) => getCategoryOrder(left.category) - getCategoryOrder(right.category)
1916
2007
  );
1917
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
+ }
1918
2099
  async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1919
2100
  const expected = await planExpectedFiles(config);
1920
2101
  const categories = await compareWithDisk(expected, projectRoot);
@@ -1938,7 +2119,7 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1938
2119
  });
1939
2120
  }
1940
2121
  if (isMonorepo) {
1941
- const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2122
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot, config);
1942
2123
  if (workspaceConfigChanges.length > 0) {
1943
2124
  allCategories.push({
1944
2125
  category: "workspace-config",
@@ -1950,6 +2131,110 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1950
2131
  }
1951
2132
  return orderUpdateCategories(allCategories);
1952
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
+ }
1953
2238
  async function processUpdateCategory(category, projectRoot, options) {
1954
2239
  const newChanges = category.changes.filter((change) => change.status === "added");
1955
2240
  const modifiedChanges = category.changes.filter((change) => change.status === "modified");
@@ -1989,11 +2274,8 @@ async function processUpdateCategory(category, projectRoot, options) {
1989
2274
  if (addedCount > 0) parts.push(`added ${addedCount}`);
1990
2275
  if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
1991
2276
  console.log(color.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
1992
- console.log();
1993
2277
  return "updated";
1994
2278
  }
1995
- console.log(color.dim(` Skipped ${category.label}`));
1996
- console.log();
1997
2279
  return "skipped";
1998
2280
  }
1999
2281
  async function handleUpdateCommand(options, handleFixCommand) {
@@ -2059,6 +2341,8 @@ async function handleUpdateCommand(options, handleFixCommand) {
2059
2341
  console.log(color.dim(` Skipped ${skippedCount}`));
2060
2342
  }
2061
2343
  }
2344
+ const finalConfig = await applyPackageManagerMigration(projectRoot, isMonorepo, options, config);
2345
+ await promptForPackageUpdate(projectRoot, finalConfig, options);
2062
2346
  process.exit(0);
2063
2347
  }
2064
2348
 
@@ -2076,12 +2360,13 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2076
2360
  message: "Package name?",
2077
2361
  initialValue: `@${scope}/`,
2078
2362
  validate: (value) => {
2079
- const validationError = validatePackageName(value);
2363
+ const packageName = value ?? "";
2364
+ const validationError = validatePackageName(packageName);
2080
2365
  if (validationError) return validationError;
2081
- const dirName = value.includes("/") ? value.split("/").pop() : value;
2366
+ const dirName = getPackageDirectoryName(packageName);
2082
2367
  if (!dirName) return "Package name is required";
2083
2368
  if (!hasCustomDirectories) {
2084
- const targetPath = join$1(monorepoRoot, defaultDir, dirName);
2369
+ const targetPath = join(monorepoRoot, defaultDir, dirName);
2085
2370
  try {
2086
2371
  const { statSync } = require$2("fs");
2087
2372
  statSync(targetPath);
@@ -2095,7 +2380,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2095
2380
  return false;
2096
2381
  }
2097
2382
  const scopedName = packageNameInput;
2098
- const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
2383
+ const shortName = getPackageDirectoryName(scopedName);
2099
2384
  const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
2100
2385
  let targetDir = defaultDir;
2101
2386
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -2111,7 +2396,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2111
2396
  return false;
2112
2397
  }
2113
2398
  targetDir = dirChoice;
2114
- const targetPath = join$1(monorepoRoot, targetDir, shortName);
2399
+ const targetPath = join(monorepoRoot, targetDir, shortName);
2115
2400
  try {
2116
2401
  const { statSync } = require$2("fs");
2117
2402
  statSync(targetPath);
@@ -2120,7 +2405,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2120
2405
  } catch {
2121
2406
  }
2122
2407
  }
2123
- const relativePkgPath = join$1(targetDir, shortName);
2408
+ const relativePkgPath = join(targetDir, shortName);
2124
2409
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2125
2410
  packageOptions.workspaceRoot = workspaceRoot;
2126
2411
  packageOptions.name = scopedName;
@@ -2135,7 +2420,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2135
2420
  packageOptions.workspaceDependencies = selectedDeps;
2136
2421
  }
2137
2422
  }
2138
- const outputPath = join$1(monorepoRoot, relativePkgPath);
2423
+ const outputPath = join(monorepoRoot, relativePkgPath);
2139
2424
  const spinner = p.spinner();
2140
2425
  spinner.start("Creating package...");
2141
2426
  try {
@@ -2176,10 +2461,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2176
2461
  const template = options.template ?? "vanilla";
2177
2462
  const baseTemplate = getBaseTemplate(template);
2178
2463
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2179
- const fullPackagePath = join$1(monorepoRoot, targetDir, name);
2464
+ const packageDirName = getPackageDirectoryName(name);
2465
+ const fullPackagePath = join(monorepoRoot, targetDir, packageDirName);
2180
2466
  try {
2181
- await access$1(fullPackagePath, constants$1.F_OK);
2182
- 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`);
2183
2469
  process.exit(1);
2184
2470
  } catch {
2185
2471
  }
@@ -2192,7 +2478,7 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2192
2478
  };
2193
2479
  const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
2194
2480
  const isLibrary = projectType === "library";
2195
- const relativePkgPath = join$1(targetDir, name);
2481
+ const relativePkgPath = join(targetDir, packageDirName);
2196
2482
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2197
2483
  const projectOptions = {
2198
2484
  name: scopedName,
@@ -2220,11 +2506,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2220
2506
  triplex: options.triplex ? {} : void 0
2221
2507
  }
2222
2508
  };
2223
- console.log(color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2509
+ console.log(color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${packageDirName}...`);
2224
2510
  try {
2225
2511
  const { files } = await planProject(resolveProjectPlanInput(projectOptions));
2226
2512
  await writeGeneratedFiles(fullPackagePath, files);
2227
- console.log(color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2513
+ console.log(color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${packageDirName}`);
2228
2514
  process.exit(0);
2229
2515
  } catch (error) {
2230
2516
  console.error(color.red("Error:") + " Failed to create package");
@@ -2294,7 +2580,7 @@ function hasConfigOptions(options) {
2294
2580
  async function writeGeneratedFiles(basePath, files) {
2295
2581
  const filePaths = Object.keys(files).sort();
2296
2582
  for (const filePath of filePaths) {
2297
- const fullFilePath = join$1(basePath, filePath);
2583
+ const fullFilePath = join(basePath, filePath);
2298
2584
  await mkdir(dirname(fullFilePath), { recursive: true });
2299
2585
  const file = files[filePath];
2300
2586
  if (file.type === "text") {
@@ -2307,7 +2593,7 @@ async function writeGeneratedFiles(basePath, files) {
2307
2593
  }
2308
2594
  async function handleMonorepoCreation(projectOptions, isNonInteractive) {
2309
2595
  const packageManager = getPackageManagerName(projectOptions.packageManager);
2310
- const projectPath = join$1(cwd(), projectOptions.name);
2596
+ const projectPath = join(cwd(), projectOptions.name);
2311
2597
  const spinner = p.spinner();
2312
2598
  spinner.start("Creating monorepo workspace...");
2313
2599
  try {
@@ -2369,7 +2655,8 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2369
2655
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
2370
2656
  projectOptions.name ??= defaultFallbackName;
2371
2657
  const packageManager = getPackageManagerName(projectOptions.packageManager);
2372
- const projectPath = join$1(cwd(), projectOptions.name);
2658
+ const projectDirName = getPackageDirectoryName(projectOptions.name);
2659
+ const projectPath = join(cwd(), projectDirName);
2373
2660
  const spinner = p.spinner();
2374
2661
  spinner.start("Creating project...");
2375
2662
  try {
@@ -2378,15 +2665,11 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2378
2665
  await writeGeneratedFiles(projectPath, files);
2379
2666
  spinner.stop(color.green.inverse(" \u2713 Project created! "));
2380
2667
  if (isNonInteractive) process.exit(0);
2381
- const nextSteps = projectOptions.projectType === "library" ? [
2382
- `cd ${projectOptions.name}`,
2383
- `${packageManager} install`,
2384
- `${packageManager} run build`
2385
- ].join("\n") : [
2386
- `cd ${projectOptions.name}`,
2387
- `${packageManager} install`,
2388
- `${packageManager} run dev`
2389
- ].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
+ );
2390
2673
  p.note(nextSteps, "Next steps");
2391
2674
  p.outro(color.green("Happy coding! \u2728"));
2392
2675
  } catch (error) {
@@ -2402,7 +2685,7 @@ async function main() {
2402
2685
  ).option(
2403
2686
  "--template <type>",
2404
2687
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2405
- ).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(
2406
2689
  "--pnpm-manage-versions",
2407
2690
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2408
2691
  ).option(
@@ -2428,6 +2711,12 @@ async function main() {
2428
2711
  console.error(color.red("Error:") + ' --ide must be "vscode" or "none"');
2429
2712
  process.exit(1);
2430
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
+ }
2431
2720
  if (name?.startsWith("-")) {
2432
2721
  switch (name) {
2433
2722
  case "--version":
@@ -2513,7 +2802,7 @@ async function main() {
2513
2802
  viverse: options.viverse ? {} : void 0,
2514
2803
  triplex: options.triplex ? {} : void 0
2515
2804
  },
2516
- packageManager: options.packageManager ? { name: options.packageManager } : void 0,
2805
+ packageManager: packageManagerSpec,
2517
2806
  pnpmManageVersions: options.pnpmManageVersions,
2518
2807
  engine: { name: "node", version: options.nodeVersion ?? "latest" }
2519
2808
  };
@@ -2524,7 +2813,7 @@ async function main() {
2524
2813
  bundler: options.bundler,
2525
2814
  linter: options.linter,
2526
2815
  formatter: options.formatter,
2527
- packageManager: options.packageManager,
2816
+ packageManager: packageManagerSpec,
2528
2817
  ide: options.ide,
2529
2818
  engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2530
2819
  pnpmManageVersions: options.pnpmManageVersions,