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.cjs CHANGED
@@ -4,17 +4,18 @@
4
4
  const p = require('@clack/prompts');
5
5
  const color = require('chalk');
6
6
  const commander = require('commander');
7
- const promises$1 = require('node:fs/promises');
7
+ const promises = require('node:fs/promises');
8
8
  const node_module = require('node:module');
9
9
  const node_path = require('node:path');
10
10
  const node_process = require('node:process');
11
11
  const undici = require('undici');
12
- const workspace = require('./shared/create-krispya.CiGwBdQ3.cjs');
13
- const Conf = require('conf');
14
- const promises = require('fs/promises');
12
+ const workspace = require('./shared/create-krispya.Wf4wp5cQ.cjs');
13
+ const promises$1 = require('fs/promises');
15
14
  const fs = require('fs');
16
15
  const path = require('path');
17
16
  const node_fs = require('node:fs');
17
+ const node_child_process = require('node:child_process');
18
+ require('conf');
18
19
 
19
20
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
21
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
@@ -33,7 +34,6 @@ function _interopNamespaceCompat(e) {
33
34
 
34
35
  const p__namespace = /*#__PURE__*/_interopNamespaceCompat(p);
35
36
  const color__default = /*#__PURE__*/_interopDefaultCompat(color);
36
- const Conf__default = /*#__PURE__*/_interopDefaultCompat(Conf);
37
37
 
38
38
  function formatConfigSummary(options, inherited) {
39
39
  const lines = [];
@@ -134,8 +134,9 @@ function formatMonorepoConfigSummary(options) {
134
134
  lines.push(
135
135
  formatRow("Engine", `${workspace.getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
136
136
  );
137
- lines.push(formatRow("Package manager", options.packageManager));
138
- if (options.packageManager === "pnpm") {
137
+ const packageManagerName = workspace.getPackageManagerName(options.packageManager);
138
+ lines.push(formatRow("Package manager", packageManagerName));
139
+ if (packageManagerName === "pnpm") {
139
140
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
140
141
  lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
141
142
  }
@@ -145,22 +146,6 @@ function formatMonorepoConfigSummary(options) {
145
146
  return lines.join("\n");
146
147
  }
147
148
 
148
- const config = new Conf__default({
149
- projectName: "create-krispya"
150
- });
151
- function getAiPlatforms() {
152
- return config.get("aiPlatforms");
153
- }
154
- function getConfigStrategy() {
155
- return config.get("configStrategy") ?? "stealth";
156
- }
157
- function clearConfig() {
158
- config.clear();
159
- }
160
- function getConfigPath() {
161
- return config.path;
162
- }
163
-
164
149
  const R3F_INTEGRATION_OPTIONS = [
165
150
  { value: "drei", label: "Drei" },
166
151
  { value: "handle", label: "Handle" },
@@ -224,7 +209,7 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler,
224
209
  formatter: inheritedSettings?.formatter ?? "prettier",
225
210
  // Libraries get vitest by default, apps don't
226
211
  testing: projectType === "library" ? "vitest" : "none",
227
- configStrategy: getConfigStrategy(),
212
+ configStrategy: workspace.getConfigStrategy(),
228
213
  ide: "vscode"
229
214
  };
230
215
  return {
@@ -274,7 +259,7 @@ async function promptForCustomization(template, name, projectType, features, inh
274
259
  libraryBundler = bundler;
275
260
  }
276
261
  let engine = inheritedSettings?.engine ?? presets?.engine ?? { name: "node", version: "latest" };
277
- let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager ?? "pnpm";
262
+ let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager?.name ?? "pnpm";
278
263
  let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
279
264
  if (!inheritedSettings?.engine?.version) {
280
265
  const nodeVersionInput = await p__namespace.text({
@@ -282,7 +267,7 @@ async function promptForCustomization(template, name, projectType, features, inh
282
267
  placeholder: presets?.engine?.version ?? "latest",
283
268
  defaultValue: presets?.engine?.version ?? "latest",
284
269
  validate: (value) => {
285
- if (!value.length) return "Required";
270
+ if (!value?.length) return "Required";
286
271
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
287
272
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
288
273
  }
@@ -302,7 +287,7 @@ async function promptForCustomization(template, name, projectType, features, inh
302
287
  { value: "npm", label: "npm" },
303
288
  { value: "yarn", label: "yarn" }
304
289
  ],
305
- initialValue: presets?.packageManager ?? "pnpm"
290
+ initialValue: presets?.packageManager?.name ?? "pnpm"
306
291
  });
307
292
  if (p__namespace.isCancel(packageManager)) {
308
293
  p__namespace.cancel("Operation cancelled.");
@@ -385,7 +370,7 @@ async function promptForCustomization(template, name, projectType, features, inh
385
370
  { value: "stealth", label: "stealth", hint: "configs in .config/" },
386
371
  { value: "root", label: "root", hint: "configs at project root" }
387
372
  ],
388
- initialValue: getConfigStrategy()
373
+ initialValue: workspace.getConfigStrategy()
389
374
  });
390
375
  if (p__namespace.isCancel(configStrategyChoice)) {
391
376
  p__namespace.cancel("Operation cancelled.");
@@ -411,7 +396,7 @@ async function promptForCustomization(template, name, projectType, features, inh
411
396
  projectType,
412
397
  libraryBundler: projectType === "library" ? libraryBundler : void 0,
413
398
  engine,
414
- packageManager: { name: finalPackageManager },
399
+ packageManager: presets?.packageManager?.name === finalPackageManager ? presets.packageManager : { name: finalPackageManager },
415
400
  pnpmManageVersions,
416
401
  linter,
417
402
  formatter,
@@ -458,7 +443,7 @@ async function promptForMonorepoCustomization(name, presets) {
458
443
  placeholder: presets?.engine?.version ?? "latest",
459
444
  defaultValue: presets?.engine?.version ?? "latest",
460
445
  validate: (value) => {
461
- if (!value.length) return "Required";
446
+ if (!value?.length) return "Required";
462
447
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
463
448
  return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
464
449
  }
@@ -539,7 +524,7 @@ async function promptForMonorepo(workspaceName, presets) {
539
524
  formatMonorepoConfigSummary({
540
525
  name: defaultOptions.name,
541
526
  engine: defaultOptions.engine ?? { name: "node", version: "latest" },
542
- packageManager: workspace.getPackageManagerName(defaultOptions.packageManager),
527
+ packageManager: defaultOptions.packageManager ?? { name: "pnpm" },
543
528
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
544
529
  linter: defaultOptions.linter ?? "oxlint",
545
530
  formatter: defaultOptions.formatter ?? "prettier",
@@ -560,7 +545,7 @@ async function promptForOptions(name, presets) {
560
545
  placeholder: workspace.generateRandomName(),
561
546
  defaultValue: workspace.generateRandomName(),
562
547
  validate: (value) => {
563
- if (!value.length) return "Project name is required";
548
+ if (!value?.length) return "Project name is required";
564
549
  }
565
550
  });
566
551
  if (p__namespace.isCancel(nameResult)) {
@@ -592,7 +577,7 @@ function presetsToInheritedSettings(presets) {
592
577
  return {
593
578
  linter: presets.linter,
594
579
  formatter: presets.formatter,
595
- packageManager: presets.packageManager ? { name: presets.packageManager } : void 0,
580
+ packageManager: presets.packageManager,
596
581
  engine: presets.engine,
597
582
  pnpmManageVersions: presets.pnpmManageVersions
598
583
  };
@@ -644,7 +629,7 @@ async function promptForPackageOptions(projectName, projectType, inheritedSettin
644
629
  }
645
630
 
646
631
  async function promptForAiAgentPlatforms(isNonInteractive) {
647
- const savedPlatforms = getAiPlatforms();
632
+ const savedPlatforms = workspace.getAiPlatforms();
648
633
  if (isNonInteractive) {
649
634
  return savedPlatforms ?? workspace.ALL_AI_PLATFORMS;
650
635
  }
@@ -677,54 +662,9 @@ async function promptForAiAgentPlatforms(isNonInteractive) {
677
662
  return selected;
678
663
  }
679
664
 
680
- async function checkAnyExists(paths) {
681
- for (const path of paths) {
682
- try {
683
- await promises.access(path, promises.constants.F_OK);
684
- return true;
685
- } catch {
686
- }
687
- }
688
- return false;
689
- }
690
- async function validateWorkspace(monorepoRoot) {
691
- const errors = [];
692
- const tsConfigPath = path.join(monorepoRoot, ".config/typescript/package.json");
693
- try {
694
- await promises.access(tsConfigPath, promises.constants.F_OK);
695
- } catch {
696
- errors.push("Missing .config/typescript package");
697
- }
698
- const linterPaths = [
699
- path.join(monorepoRoot, ".config/oxlint/package.json"),
700
- path.join(monorepoRoot, ".config/eslint/package.json"),
701
- path.join(monorepoRoot, "eslint.config.js"),
702
- path.join(monorepoRoot, "biome.json")
703
- ];
704
- const hasLinter = await checkAnyExists(linterPaths);
705
- if (!hasLinter) {
706
- errors.push(
707
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
708
- );
709
- }
710
- const formatterPaths = [
711
- path.join(monorepoRoot, ".config/oxfmt/package.json"),
712
- path.join(monorepoRoot, ".config/prettier/package.json"),
713
- path.join(monorepoRoot, ".prettierrc.json"),
714
- path.join(monorepoRoot, "biome.json")
715
- ];
716
- const hasFormatter = await checkAnyExists(formatterPaths);
717
- if (!hasFormatter) {
718
- errors.push(
719
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
720
- );
721
- }
722
- return { valid: errors.length === 0, errors };
723
- }
724
-
725
665
  async function fileExists$1(path) {
726
666
  try {
727
- await promises$1.access(path, node_fs.constants.F_OK);
667
+ await promises.access(path, node_fs.constants.F_OK);
728
668
  return true;
729
669
  } catch {
730
670
  return false;
@@ -740,8 +680,8 @@ async function detectMonorepoRoot() {
740
680
  while (currentDir !== root) {
741
681
  const workspaceFile = node_path.join(currentDir, "pnpm-workspace.yaml");
742
682
  try {
743
- await promises$1.access(workspaceFile, node_fs.constants.F_OK);
744
- const content = await promises$1.readFile(workspaceFile, "utf-8");
683
+ await promises.access(workspaceFile, node_fs.constants.F_OK);
684
+ const content = await promises.readFile(workspaceFile, "utf-8");
745
685
  if (content.includes("packages:")) {
746
686
  return currentDir;
747
687
  }
@@ -765,7 +705,7 @@ async function detectPackageRoot() {
765
705
  async function parseWorkspaceDirectories(monorepoRoot) {
766
706
  try {
767
707
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
768
- const content = await promises$1.readFile(workspaceFile, "utf-8");
708
+ const content = await promises.readFile(workspaceFile, "utf-8");
769
709
  return workspace.parseWorkspaceYamlContent(content);
770
710
  } catch {
771
711
  return [];
@@ -775,14 +715,14 @@ async function detectWorkspaceSettings(monorepoRoot) {
775
715
  try {
776
716
  const tooling = await workspace.detectTooling(monorepoRoot);
777
717
  const pkgPath = node_path.join(monorepoRoot, "package.json");
778
- const content = await promises$1.readFile(pkgPath, "utf-8");
718
+ const content = await promises.readFile(pkgPath, "utf-8");
779
719
  const pkgJson = JSON.parse(content);
780
- const packageManager = workspace.parsePackageManager(pkgJson.packageManager);
720
+ const packageManager = workspace.parsePackageManagerSpec(pkgJson.packageManager);
781
721
  const engine = workspace.parseEngine(pkgJson.engines);
782
722
  let pnpmManageVersions;
783
723
  try {
784
724
  const workspaceFile = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
785
- const workspaceContent = await promises$1.readFile(workspaceFile, "utf-8");
725
+ const workspaceContent = await promises.readFile(workspaceFile, "utf-8");
786
726
  pnpmManageVersions = workspaceContent.includes("manage-package-manager-versions: true");
787
727
  } catch {
788
728
  }
@@ -820,7 +760,7 @@ async function detectExistingConfigs(monorepoRoot) {
820
760
  async function getMonorepoScope(monorepoRoot) {
821
761
  try {
822
762
  const pkgPath = node_path.join(monorepoRoot, "package.json");
823
- const content = await promises$1.readFile(pkgPath, "utf-8");
763
+ const content = await promises.readFile(pkgPath, "utf-8");
824
764
  const pkgJson = JSON.parse(content);
825
765
  if (pkgJson.name) {
826
766
  return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
@@ -832,12 +772,12 @@ async function getMonorepoScope(monorepoRoot) {
832
772
  async function getWorkspacePackages(monorepoRoot) {
833
773
  const packagesDir = node_path.join(monorepoRoot, "packages");
834
774
  try {
835
- const entries = await promises$1.readdir(packagesDir, { withFileTypes: true });
775
+ const entries = await promises.readdir(packagesDir, { withFileTypes: true });
836
776
  const names = [];
837
777
  for (const entry of entries) {
838
778
  if (!entry.isDirectory()) continue;
839
779
  try {
840
- const content = await promises$1.readFile(node_path.join(packagesDir, entry.name, "package.json"), "utf-8");
780
+ const content = await promises.readFile(node_path.join(packagesDir, entry.name, "package.json"), "utf-8");
841
781
  const pkg = JSON.parse(content);
842
782
  if (pkg.name) names.push(pkg.name);
843
783
  } catch {
@@ -852,13 +792,13 @@ async function ensureConfigInWorkspace(monorepoRoot) {
852
792
  const workspacePath = node_path.join(monorepoRoot, "pnpm-workspace.yaml");
853
793
  let content;
854
794
  try {
855
- content = await promises$1.readFile(workspacePath, "utf-8");
795
+ content = await promises.readFile(workspacePath, "utf-8");
856
796
  } catch {
857
797
  content = `packages:
858
798
  - '.config/*'
859
799
  - 'packages/*'
860
800
  `;
861
- await promises$1.writeFile(workspacePath, content);
801
+ await promises.writeFile(workspacePath, content);
862
802
  return;
863
803
  }
864
804
  if (content.includes(".config/*")) {
@@ -874,7 +814,7 @@ ${content}`;
874
814
  lines.splice(packagesIndex + 1, 0, " - '.config/*'");
875
815
  content = lines.join("\n");
876
816
  }
877
- await promises$1.writeFile(workspacePath, content);
817
+ await promises.writeFile(workspacePath, content);
878
818
  }
879
819
 
880
820
  async function handleCheckCommand() {
@@ -883,7 +823,7 @@ async function handleCheckCommand() {
883
823
  console.log(color__default.red("\u2717") + " Not a monorepo workspace");
884
824
  process.exit(1);
885
825
  }
886
- const { valid, errors } = await validateWorkspace(monorepoRoot);
826
+ const { valid, errors } = await workspace.validateWorkspace(monorepoRoot);
887
827
  if (valid) {
888
828
  console.log(color__default.green("\u2713") + " Valid monorepo workspace");
889
829
  console.log(color__default.dim(` ${monorepoRoot}`));
@@ -902,7 +842,7 @@ async function migrateEslintConfig(monorepoRoot, files) {
902
842
  const existingConfigPath = node_path.join(monorepoRoot, "eslint.config.js");
903
843
  let existingContent;
904
844
  try {
905
- existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
845
+ existingContent = await promises.readFile(existingConfigPath, "utf-8");
906
846
  } catch {
907
847
  workspace.renderEslintConfigPackage(files);
908
848
  return;
@@ -981,7 +921,7 @@ async function migratePrettierConfig(monorepoRoot, files) {
981
921
  const existingConfigPath = node_path.join(monorepoRoot, ".prettierrc.json");
982
922
  let existingContent;
983
923
  try {
984
- existingContent = await promises$1.readFile(existingConfigPath, "utf-8");
924
+ existingContent = await promises.readFile(existingConfigPath, "utf-8");
985
925
  } catch {
986
926
  workspace.renderPrettierConfigPackage(files);
987
927
  return;
@@ -1040,7 +980,7 @@ async function handleFixCommand(options) {
1040
980
  console.log(color__default.dim(" Run this command from within a monorepo"));
1041
981
  process.exit(1);
1042
982
  }
1043
- const { valid, errors } = await validateWorkspace(monorepoRoot);
983
+ const { valid, errors } = await workspace.validateWorkspace(monorepoRoot);
1044
984
  if (valid) {
1045
985
  console.log(color__default.green("\u2713") + " Workspace is already valid");
1046
986
  console.log(color__default.dim(` ${monorepoRoot}`));
@@ -1174,19 +1114,19 @@ async function handleFixCommand(options) {
1174
1114
  }
1175
1115
  for (const [filePath, file] of Object.entries(files)) {
1176
1116
  const fullPath = node_path.join(monorepoRoot, filePath);
1177
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1178
- await promises$1.writeFile(fullPath, file.content);
1117
+ await promises.mkdir(node_path.dirname(fullPath), { recursive: true });
1118
+ await promises.writeFile(fullPath, file.content);
1179
1119
  }
1180
1120
  await ensureConfigInWorkspace(monorepoRoot);
1181
1121
  if (existingConfigs.eslintConfigPath && linter === "eslint") {
1182
1122
  try {
1183
- await promises$1.unlink(existingConfigs.eslintConfigPath);
1123
+ await promises.unlink(existingConfigs.eslintConfigPath);
1184
1124
  } catch {
1185
1125
  }
1186
1126
  }
1187
1127
  if (existingConfigs.prettierConfigPath && formatter === "prettier") {
1188
1128
  try {
1189
- await promises$1.unlink(existingConfigs.prettierConfigPath);
1129
+ await promises.unlink(existingConfigs.prettierConfigPath);
1190
1130
  } catch {
1191
1131
  }
1192
1132
  }
@@ -1215,8 +1155,8 @@ async function handleFixCommand(options) {
1215
1155
  workspace.renderVscodeFiles(vscodeFiles, linter, formatter);
1216
1156
  for (const [filePath, file] of Object.entries(vscodeFiles)) {
1217
1157
  const fullPath = node_path.join(monorepoRoot, filePath);
1218
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1219
- await promises$1.writeFile(fullPath, file.content);
1158
+ await promises.mkdir(node_path.dirname(fullPath), { recursive: true });
1159
+ await promises.writeFile(fullPath, file.content);
1220
1160
  }
1221
1161
  console.log(color__default.dim(" Generated .vscode/settings.json"));
1222
1162
  console.log(color__default.dim(" Generated .vscode/extensions.json"));
@@ -1239,8 +1179,8 @@ async function handleFixCommand(options) {
1239
1179
  });
1240
1180
  for (const [filePath, file] of Object.entries(aiFilesOutput)) {
1241
1181
  const fullPath = node_path.join(monorepoRoot, filePath);
1242
- await promises$1.mkdir(node_path.dirname(fullPath), { recursive: true });
1243
- await promises$1.writeFile(fullPath, file.content);
1182
+ await promises.mkdir(node_path.dirname(fullPath), { recursive: true });
1183
+ await promises.writeFile(fullPath, file.content);
1244
1184
  console.log(color__default.dim(` Generated ${filePath}`));
1245
1185
  }
1246
1186
  }
@@ -1282,18 +1222,22 @@ function renderExpectedViteConfig(template) {
1282
1222
  async function detectCurrentConfig(root, isMonorepo = true) {
1283
1223
  let name = root.split(/[/\\]/).pop() ?? "workspace";
1284
1224
  let packageManager = "pnpm";
1225
+ let packageManagerSpec = { name: "pnpm" };
1226
+ let engine;
1285
1227
  let hasTypecheck = false;
1286
1228
  let viteTemplate;
1287
1229
  try {
1288
1230
  const pkgPath = path.join(root, "package.json");
1289
- const content = await promises.readFile(pkgPath, "utf-8");
1231
+ const content = await promises$1.readFile(pkgPath, "utf-8");
1290
1232
  const pkgJson = JSON.parse(content);
1291
1233
  if (pkgJson.name) {
1292
1234
  name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
1293
1235
  }
1294
1236
  if (pkgJson.packageManager) {
1295
- packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
1237
+ packageManagerSpec = workspace.parsePackageManagerSpec(pkgJson.packageManager) ?? packageManagerSpec;
1238
+ packageManager = packageManagerSpec.name;
1296
1239
  }
1240
+ engine = workspace.parseEngine(pkgJson.engines);
1297
1241
  hasTypecheck = pkgJson.scripts?.typecheck != null;
1298
1242
  viteTemplate = detectViteTemplate(pkgJson);
1299
1243
  } catch {
@@ -1305,6 +1249,8 @@ async function detectCurrentConfig(root, isMonorepo = true) {
1305
1249
  linter: tooling.linter ?? "oxlint",
1306
1250
  formatter: tooling.formatter ?? "prettier",
1307
1251
  packageManager,
1252
+ packageManagerSpec,
1253
+ engine,
1308
1254
  isMonorepo,
1309
1255
  configStrategy,
1310
1256
  hasTypecheck,
@@ -1411,7 +1357,7 @@ async function planExpectedFiles(config) {
1411
1357
  }
1412
1358
  async function fileExists(path) {
1413
1359
  try {
1414
- await promises.access(path, fs.constants.F_OK);
1360
+ await promises$1.access(path, fs.constants.F_OK);
1415
1361
  return true;
1416
1362
  } catch {
1417
1363
  return false;
@@ -1553,7 +1499,7 @@ async function compareWithDisk(expected, root) {
1553
1499
  const fullPath = path.join(root, filePath);
1554
1500
  const newContent = file.content;
1555
1501
  if (await fileExists(fullPath)) {
1556
- const currentContent = await promises.readFile(fullPath, "utf-8");
1502
+ const currentContent = await promises$1.readFile(fullPath, "utf-8");
1557
1503
  if (fileContentsEqual(filePath, currentContent, newContent)) {
1558
1504
  changes.push({
1559
1505
  path: filePath,
@@ -1631,7 +1577,7 @@ function detectLibraryPackage(pkg) {
1631
1577
  return pkg.exports != null || pkg.main?.includes("dist") === true || pkg.module?.includes("dist") === true || Array.isArray(pkg.files) && pkg.files.includes("dist");
1632
1578
  }
1633
1579
  function getPackageManagerForScripts(config, pkg) {
1634
- const packageManager = pkg.packageManager?.split("@")[0] ?? config.packageManager;
1580
+ const packageManager = workspace.parsePackageManagerSpec(pkg.packageManager)?.name ?? config.packageManager;
1635
1581
  return isPackageManagerName(packageManager) ? packageManager : "pnpm";
1636
1582
  }
1637
1583
  function getSinglePackageToolScripts(config) {
@@ -1661,6 +1607,35 @@ function scriptsEqual(left, right) {
1661
1607
  if (leftEntries.length !== Object.keys(right).length) return false;
1662
1608
  return leftEntries.every(([key, value]) => right[key] === value);
1663
1609
  }
1610
+ function packageManagerFieldsEqual(pkg, packageManagerSpec, targetNodeVersion) {
1611
+ if (packageManagerSpec == null || packageManagerSpec.version == null) {
1612
+ return targetNodeVersion == null || pkg.engines?.node === `>=${targetNodeVersion}`;
1613
+ }
1614
+ const majorVersion = workspace.getSemverMajorString(packageManagerSpec.version);
1615
+ return pkg.packageManager === workspace.formatPackageManager(packageManagerSpec) && pkg.engines?.[packageManagerSpec.name] === `>=${majorVersion}.0.0` && (targetNodeVersion == null || pkg.engines?.node === `>=${targetNodeVersion}`);
1616
+ }
1617
+ function applyPackageManagerFields(pkg, packageManagerSpec, targetNodeVersion) {
1618
+ if (packageManagerSpec == null || packageManagerSpec.version == null) {
1619
+ if (targetNodeVersion == null) return pkg;
1620
+ return {
1621
+ ...pkg,
1622
+ engines: {
1623
+ ...pkg.engines,
1624
+ node: `>=${targetNodeVersion}`
1625
+ }
1626
+ };
1627
+ }
1628
+ const majorVersion = workspace.getSemverMajorString(packageManagerSpec.version);
1629
+ return {
1630
+ ...pkg,
1631
+ packageManager: workspace.formatPackageManager(packageManagerSpec),
1632
+ engines: {
1633
+ ...pkg.engines,
1634
+ [packageManagerSpec.name]: `>=${majorVersion}.0.0`,
1635
+ ...targetNodeVersion == null ? {} : { node: `>=${targetNodeVersion}` }
1636
+ }
1637
+ };
1638
+ }
1664
1639
  async function getExpectedPackageScripts(root, config, pkg) {
1665
1640
  if (config.isMonorepo) {
1666
1641
  return workspace.packageJsonScripts.monorepoRoot(config.linter, config.formatter);
@@ -1702,7 +1677,7 @@ async function getPackageJsonScriptUpdates(root, config) {
1702
1677
  const packageJsonPath = path.join(root, "package.json");
1703
1678
  let currentContent;
1704
1679
  try {
1705
- currentContent = await promises.readFile(packageJsonPath, "utf-8");
1680
+ currentContent = await promises$1.readFile(packageJsonPath, "utf-8");
1706
1681
  } catch {
1707
1682
  return [];
1708
1683
  }
@@ -1712,7 +1687,9 @@ async function getPackageJsonScriptUpdates(root, config) {
1712
1687
  const nextScripts = workspace.mergePackageJsonScripts(currentScripts, expectedScripts);
1713
1688
  const currentDevDependencies = pkg.devDependencies ?? {};
1714
1689
  const nextDevDependencies = await getExpectedPackageDevDependencies(root, config, pkg);
1715
- if (scriptsEqual(currentScripts, nextScripts) && scriptsEqual(currentDevDependencies, nextDevDependencies)) {
1690
+ const targetPackageManagerSpec = config.targetPackageManagerSpec;
1691
+ const targetNodeVersion = config.targetNodeVersion;
1692
+ if (scriptsEqual(currentScripts, nextScripts) && scriptsEqual(currentDevDependencies, nextDevDependencies) && packageManagerFieldsEqual(pkg, targetPackageManagerSpec, targetNodeVersion)) {
1716
1693
  return [
1717
1694
  {
1718
1695
  path: "package.json",
@@ -1722,10 +1699,14 @@ async function getPackageJsonScriptUpdates(root, config) {
1722
1699
  }
1723
1700
  ];
1724
1701
  }
1725
- const nextPackageJson = {
1726
- ...pkg,
1727
- scripts: nextScripts
1728
- };
1702
+ const nextPackageJson = applyPackageManagerFields(
1703
+ {
1704
+ ...pkg,
1705
+ scripts: nextScripts
1706
+ },
1707
+ targetPackageManagerSpec,
1708
+ targetNodeVersion
1709
+ );
1729
1710
  if (Object.keys(nextDevDependencies).length > 0 || pkg.devDependencies != null) {
1730
1711
  nextPackageJson.devDependencies = nextDevDependencies;
1731
1712
  }
@@ -1740,6 +1721,41 @@ async function getPackageJsonScriptUpdates(root, config) {
1740
1721
  }
1741
1722
  ];
1742
1723
  }
1724
+ async function getPackageManagerConfigUpdates(root, config) {
1725
+ if (config.targetPackageManagerSpec == null && config.targetNodeVersion == null) return [];
1726
+ const packageJsonPath = path.join(root, "package.json");
1727
+ let currentContent;
1728
+ try {
1729
+ currentContent = await promises$1.readFile(packageJsonPath, "utf-8");
1730
+ } catch {
1731
+ return [];
1732
+ }
1733
+ const pkg = JSON.parse(currentContent);
1734
+ if (packageManagerFieldsEqual(pkg, config.targetPackageManagerSpec, config.targetNodeVersion)) {
1735
+ return [
1736
+ {
1737
+ path: "package.json",
1738
+ status: "unchanged",
1739
+ currentContent,
1740
+ newContent: currentContent
1741
+ }
1742
+ ];
1743
+ }
1744
+ const nextPackageJson = applyPackageManagerFields(
1745
+ pkg,
1746
+ config.targetPackageManagerSpec,
1747
+ config.targetNodeVersion
1748
+ );
1749
+ return [
1750
+ {
1751
+ path: "package.json",
1752
+ status: "modified",
1753
+ currentContent,
1754
+ newContent: `${JSON.stringify(nextPackageJson, null, 2)}
1755
+ `
1756
+ }
1757
+ ];
1758
+ }
1743
1759
  function planSinglePackageOxlintConfig(config) {
1744
1760
  if (config.linter !== "oxlint" || config.isMonorepo) return void 0;
1745
1761
  const isStealth = (config.configStrategy ?? "stealth") === "stealth";
@@ -1763,7 +1779,7 @@ async function getOxlintConfigReplacementUpdates(root, config) {
1763
1779
  const fullPath = path.join(root, expected.path);
1764
1780
  let currentContent;
1765
1781
  try {
1766
- currentContent = await promises.readFile(fullPath, "utf-8");
1782
+ currentContent = await promises$1.readFile(fullPath, "utf-8");
1767
1783
  } catch {
1768
1784
  return [expected];
1769
1785
  }
@@ -1785,26 +1801,118 @@ async function getOxlintConfigReplacementUpdates(root, config) {
1785
1801
  }
1786
1802
  ];
1787
1803
  }
1788
- async function getWorkspaceConfigUpdates(root) {
1804
+ function extractBuildDependencies(content) {
1805
+ const buildDependencies = {};
1806
+ let section;
1807
+ for (const line of content.split("\n")) {
1808
+ const trimmed = line.trim();
1809
+ if (trimmed === "onlyBuiltDependencies:") {
1810
+ section = "onlyBuiltDependencies";
1811
+ continue;
1812
+ }
1813
+ if (trimmed === "allowBuilds:") {
1814
+ section = "allowBuilds";
1815
+ continue;
1816
+ }
1817
+ if (section != null && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
1818
+ section = void 0;
1819
+ }
1820
+ if (section === "onlyBuiltDependencies" && trimmed.startsWith("-")) {
1821
+ const dependency = trimmed.slice(1).trim().replace(/^["']|["']$/g, "");
1822
+ if (dependency.length > 0) {
1823
+ buildDependencies[dependency] = true;
1824
+ }
1825
+ }
1826
+ if (section === "allowBuilds") {
1827
+ const match = trimmed.match(/^([^:]+):\s*(true|false)$/);
1828
+ if (match == null) continue;
1829
+ const dependency = match[1].trim().replace(/^["']|["']$/g, "");
1830
+ if (dependency.length > 0) {
1831
+ buildDependencies[dependency] = match[2] === "true";
1832
+ }
1833
+ }
1834
+ }
1835
+ return buildDependencies;
1836
+ }
1837
+ function withDefaultBuildDependencies(buildDependencies) {
1838
+ return Object.keys(buildDependencies).length > 0 ? buildDependencies : { esbuild: true };
1839
+ }
1840
+ function isPnpmWorkspaceManagedScalarKey(trimmed) {
1841
+ return trimmed.startsWith("manage-package-manager-versions:") || trimmed.startsWith("pmOnFail:");
1842
+ }
1843
+ function isPnpmWorkspaceManagedBlockKey(trimmed) {
1844
+ return trimmed === "onlyBuiltDependencies:" || trimmed === "allowBuilds:";
1845
+ }
1846
+ function isIndentedWorkspaceLine(line) {
1847
+ return line.startsWith(" ") || line.startsWith(" ");
1848
+ }
1849
+ function removePnpmWorkspaceManagedKeys(content) {
1850
+ const sourceLines = content.split("\n");
1851
+ const lines = [];
1852
+ let insertionIndex = 0;
1853
+ let foundManagedKey = false;
1854
+ for (let index = 0; index < sourceLines.length; index++) {
1855
+ const line = sourceLines[index];
1856
+ const trimmed = line.trim();
1857
+ if (isPnpmWorkspaceManagedScalarKey(trimmed)) {
1858
+ if (!foundManagedKey) {
1859
+ insertionIndex = lines.length;
1860
+ foundManagedKey = true;
1861
+ }
1862
+ continue;
1863
+ }
1864
+ if (isPnpmWorkspaceManagedBlockKey(trimmed)) {
1865
+ if (!foundManagedKey) {
1866
+ insertionIndex = lines.length;
1867
+ foundManagedKey = true;
1868
+ }
1869
+ while (index + 1 < sourceLines.length) {
1870
+ const nextLine = sourceLines[index + 1];
1871
+ const nextTrimmed = nextLine.trim();
1872
+ if (nextTrimmed !== "" && !isIndentedWorkspaceLine(nextLine)) break;
1873
+ index++;
1874
+ }
1875
+ continue;
1876
+ }
1877
+ lines.push(line);
1878
+ }
1879
+ return {
1880
+ content: lines.join("\n").trim(),
1881
+ insertionIndex
1882
+ };
1883
+ }
1884
+ function patchPnpmWorkspaceManagedKeys(content, profile, buildDependencies) {
1885
+ const managedContent = workspace.renderPnpmWorkspaceConfig({
1886
+ profile,
1887
+ manageVersions: true,
1888
+ buildDependencies
1889
+ });
1890
+ const existing = removePnpmWorkspaceManagedKeys(content);
1891
+ const preservedLines = existing.content.length > 0 ? existing.content.split("\n") : [];
1892
+ const insertionIndex = Math.min(existing.insertionIndex, preservedLines.length);
1893
+ const managedLines = managedContent.split("\n");
1894
+ preservedLines.splice(insertionIndex, 0, ...managedLines);
1895
+ return preservedLines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1896
+ }
1897
+ async function getWorkspaceConfigUpdates(root, config) {
1789
1898
  const workspacePath = path.join(root, "pnpm-workspace.yaml");
1790
1899
  const changes = [];
1900
+ const packageManagerSpec = config?.targetPackageManagerSpec ?? config?.packageManagerSpec ?? { name: "pnpm" };
1901
+ const profile = workspace.getPackageManagerProfile(packageManagerSpec);
1902
+ const defaultPackages = [".config/*", "apps/*", "packages/*"];
1791
1903
  let currentContent = "";
1792
1904
  let exists = false;
1793
1905
  try {
1794
- currentContent = await promises.readFile(workspacePath, "utf-8");
1906
+ currentContent = await promises$1.readFile(workspacePath, "utf-8");
1795
1907
  exists = true;
1796
1908
  } catch {
1797
1909
  }
1798
1910
  if (!exists) {
1799
- const newContent = `manage-package-manager-versions: true
1800
-
1801
- packages:
1802
- - '.config/*'
1803
- - 'apps/*'
1804
- - 'packages/*'
1805
-
1806
- onlyBuiltDependencies:
1807
- - esbuild
1911
+ const newContent = `${workspace.renderPnpmWorkspaceConfig({
1912
+ profile,
1913
+ manageVersions: true,
1914
+ packages: defaultPackages
1915
+ })}
1808
1916
  `;
1809
1917
  changes.push({
1810
1918
  path: "pnpm-workspace.yaml",
@@ -1813,32 +1921,14 @@ onlyBuiltDependencies:
1813
1921
  });
1814
1922
  return changes;
1815
1923
  }
1816
- let updatedContent = currentContent;
1817
- let needsUpdate = false;
1818
- if (!currentContent.includes("manage-package-manager-versions")) {
1819
- updatedContent = `manage-package-manager-versions: true
1820
-
1821
- ${updatedContent}`;
1822
- needsUpdate = true;
1823
- }
1824
- if (!currentContent.includes("onlyBuiltDependencies")) {
1825
- updatedContent = `${updatedContent.trimEnd()}
1826
-
1827
- onlyBuiltDependencies:
1828
- - esbuild
1924
+ const buildDependencies = withDefaultBuildDependencies(extractBuildDependencies(currentContent));
1925
+ const updatedContent = `${patchPnpmWorkspaceManagedKeys(
1926
+ currentContent,
1927
+ profile,
1928
+ buildDependencies
1929
+ )}
1829
1930
  `;
1830
- needsUpdate = true;
1831
- }
1832
- if (!currentContent.includes(".config/*")) {
1833
- const lines = updatedContent.split("\n");
1834
- const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1835
- if (packagesIndex !== -1) {
1836
- lines.splice(packagesIndex + 1, 0, " - '.config/*'");
1837
- updatedContent = lines.join("\n");
1838
- needsUpdate = true;
1839
- }
1840
- }
1841
- if (needsUpdate) {
1931
+ if (updatedContent !== currentContent) {
1842
1932
  changes.push({
1843
1933
  path: "pnpm-workspace.yaml",
1844
1934
  status: "modified",
@@ -1859,8 +1949,8 @@ async function applyUpdates(changes, root) {
1859
1949
  for (const change of changes) {
1860
1950
  if (change.status === "unchanged") continue;
1861
1951
  const fullPath = path.join(root, change.path);
1862
- await promises.mkdir(path.dirname(fullPath), { recursive: true });
1863
- await promises.writeFile(fullPath, change.newContent);
1952
+ await promises$1.mkdir(path.dirname(fullPath), { recursive: true });
1953
+ await promises$1.writeFile(fullPath, change.newContent);
1864
1954
  }
1865
1955
  }
1866
1956
  function formatFileChange(change) {
@@ -1936,6 +2026,96 @@ function orderUpdateCategories(categories) {
1936
2026
  (left, right) => getCategoryOrder(left.category) - getCategoryOrder(right.category)
1937
2027
  );
1938
2028
  }
2029
+ function isPnpmMajorMigration(config) {
2030
+ const currentPackageManager = config.packageManagerSpec;
2031
+ const targetPackageManager = config.targetPackageManagerSpec;
2032
+ if (currentPackageManager?.name !== "pnpm" || targetPackageManager?.name !== "pnpm") {
2033
+ return false;
2034
+ }
2035
+ const currentMajor = workspace.getSemverMajor(currentPackageManager.version);
2036
+ const targetMajor = workspace.getSemverMajor(targetPackageManager.version);
2037
+ return currentMajor != null && targetMajor != null && currentMajor !== targetMajor;
2038
+ }
2039
+ function getPackageUpdateCommand(config) {
2040
+ const packageManagerName = config.packageManager.split("@")[0] ?? config.packageManager;
2041
+ if (packageManagerName === "pnpm") {
2042
+ if (isPnpmMajorMigration(config)) {
2043
+ return {
2044
+ command: "pnpm",
2045
+ args: ["install"],
2046
+ displayCommand: "pnpm install",
2047
+ promptMessage: "Install dependencies?",
2048
+ successMessage: "Dependencies installed",
2049
+ failureLabel: "Dependency install"
2050
+ };
2051
+ }
2052
+ return {
2053
+ command: "pnpm",
2054
+ args: ["update"],
2055
+ displayCommand: "pnpm update",
2056
+ promptMessage: "Update packages?",
2057
+ successMessage: "Packages updated",
2058
+ failureLabel: "Package update"
2059
+ };
2060
+ }
2061
+ if (packageManagerName === "npm") {
2062
+ return {
2063
+ command: "npm",
2064
+ args: ["update"],
2065
+ displayCommand: "npm update",
2066
+ promptMessage: "Update packages?",
2067
+ successMessage: "Packages updated",
2068
+ failureLabel: "Package update"
2069
+ };
2070
+ }
2071
+ return void 0;
2072
+ }
2073
+ async function runPackageUpdate(projectRoot, updateCommand) {
2074
+ await new Promise((resolve, reject) => {
2075
+ const child = node_child_process.spawn(updateCommand.command, updateCommand.args, {
2076
+ cwd: projectRoot,
2077
+ shell: process.platform === "win32",
2078
+ stdio: "inherit"
2079
+ });
2080
+ child.on("error", reject);
2081
+ child.on("exit", (code) => {
2082
+ if (code === 0) {
2083
+ resolve();
2084
+ return;
2085
+ }
2086
+ reject(new Error(`${updateCommand.displayCommand} exited with code ${code ?? "unknown"}`));
2087
+ });
2088
+ });
2089
+ }
2090
+ async function promptForPackageUpdate(projectRoot, config, options) {
2091
+ const updateCommand = getPackageUpdateCommand(config);
2092
+ if (!updateCommand) {
2093
+ console.log(color__default.dim(` Package updates are not supported for ${config.packageManager}`));
2094
+ return;
2095
+ }
2096
+ const shouldUpdatePackages = options.yes || await p__namespace.confirm({
2097
+ message: updateCommand.promptMessage,
2098
+ initialValue: true
2099
+ });
2100
+ if (p__namespace.isCancel(shouldUpdatePackages)) {
2101
+ p__namespace.cancel("Operation cancelled.");
2102
+ process.exit(0);
2103
+ }
2104
+ if (!shouldUpdatePackages) {
2105
+ console.log(color__default.dim(" Skipped package updates"));
2106
+ return;
2107
+ }
2108
+ console.log();
2109
+ console.log(color__default.cyan(`Running ${updateCommand.displayCommand}...`));
2110
+ try {
2111
+ await runPackageUpdate(projectRoot, updateCommand);
2112
+ console.log(color__default.green("\u2713") + ` ${updateCommand.successMessage}`);
2113
+ } catch (error) {
2114
+ const message = error instanceof Error ? error.message : String(error);
2115
+ console.log(color__default.red("\u2717") + ` ${updateCommand.failureLabel} failed: ${message}`);
2116
+ process.exit(1);
2117
+ }
2118
+ }
1939
2119
  async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1940
2120
  const expected = await planExpectedFiles(config);
1941
2121
  const categories = await compareWithDisk(expected, projectRoot);
@@ -1959,7 +2139,7 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1959
2139
  });
1960
2140
  }
1961
2141
  if (isMonorepo) {
1962
- const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2142
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot, config);
1963
2143
  if (workspaceConfigChanges.length > 0) {
1964
2144
  allCategories.push({
1965
2145
  category: "workspace-config",
@@ -1971,6 +2151,110 @@ async function collectUpdateCategories(projectRoot, config, isMonorepo) {
1971
2151
  }
1972
2152
  return orderUpdateCategories(allCategories);
1973
2153
  }
2154
+ async function resolveTargetPackageManagerSpec(options, config) {
2155
+ if (options.packageManager == null) return void 0;
2156
+ const packageManager = workspace.parsePackageManagerSpec(options.packageManager);
2157
+ if (packageManager == null) {
2158
+ console.log(color__default.red("\u2717") + ` Unsupported package manager: ${options.packageManager}`);
2159
+ process.exit(1);
2160
+ }
2161
+ return workspace.resolvePackageManager({
2162
+ name: config.name,
2163
+ packageManager
2164
+ });
2165
+ }
2166
+ function getPackageManagerMajorUpdateTarget(currentPackageManager, latestPackageManager) {
2167
+ if (currentPackageManager?.version == null) return void 0;
2168
+ const currentMajor = workspace.getSemverMajor(currentPackageManager.version);
2169
+ const latestMajor = workspace.getSemverMajor(latestPackageManager.version);
2170
+ if (currentMajor == null || latestMajor == null || latestMajor <= currentMajor) {
2171
+ return void 0;
2172
+ }
2173
+ return latestPackageManager;
2174
+ }
2175
+ function getRequiredNodeUpdateTarget(currentNodeVersion, requiredNodeVersion) {
2176
+ if (requiredNodeVersion == null) return void 0;
2177
+ if (currentNodeVersion == null) return requiredNodeVersion;
2178
+ return workspace.compareNumericSemver(currentNodeVersion, requiredNodeVersion) < 0 ? requiredNodeVersion : void 0;
2179
+ }
2180
+ function formatPackageManagerMajor(packageManager) {
2181
+ const major = workspace.getSemverMajor(packageManager.version);
2182
+ return major == null ? packageManager.name : `${packageManager.name}@${major}`;
2183
+ }
2184
+ async function promptForNodeRequirementUpdate(options, config, targetPackageManagerSpec) {
2185
+ if (targetPackageManagerSpec == null) return void 0;
2186
+ const profile = workspace.getPackageManagerProfile(targetPackageManagerSpec);
2187
+ const requiredNodeVersion = profile.requirements.node;
2188
+ const currentNodeVersion = config.engine?.version;
2189
+ const targetNodeVersion = getRequiredNodeUpdateTarget(currentNodeVersion, requiredNodeVersion);
2190
+ if (targetNodeVersion == null) return void 0;
2191
+ const currentNodeLabel = currentNodeVersion == null ? "not set" : `>=${currentNodeVersion}`;
2192
+ p__namespace.log.warn(
2193
+ `${workspace.formatPackageManager(targetPackageManagerSpec)} requires Node >=${targetNodeVersion}; current engines.node is ${currentNodeLabel}.`
2194
+ );
2195
+ const shouldUpdate = options.yes || await p__namespace.confirm({
2196
+ message: `Update engines.node to >=${targetNodeVersion} too?`,
2197
+ initialValue: true
2198
+ });
2199
+ if (p__namespace.isCancel(shouldUpdate)) {
2200
+ p__namespace.cancel("Operation cancelled.");
2201
+ process.exit(0);
2202
+ }
2203
+ return shouldUpdate ? targetNodeVersion : void 0;
2204
+ }
2205
+ async function promptForPackageManagerMajorUpdate(options, config) {
2206
+ if (options.packageManager != null || config.packageManagerSpec == null) return void 0;
2207
+ const currentPackageManager = config.packageManagerSpec;
2208
+ if (currentPackageManager.version == null) return void 0;
2209
+ const latestPackageManager = await workspace.resolvePackageManager({
2210
+ name: config.name,
2211
+ packageManager: { name: currentPackageManager.name }
2212
+ });
2213
+ const updateTarget = getPackageManagerMajorUpdateTarget(
2214
+ currentPackageManager,
2215
+ latestPackageManager
2216
+ );
2217
+ if (updateTarget == null) return void 0;
2218
+ const shouldUpdate = options.yes || await p__namespace.confirm({
2219
+ message: `Update ${currentPackageManager.name} from ${formatPackageManagerMajor(
2220
+ currentPackageManager
2221
+ )} to ${formatPackageManagerMajor(latestPackageManager)}?`,
2222
+ initialValue: true
2223
+ });
2224
+ if (p__namespace.isCancel(shouldUpdate)) {
2225
+ p__namespace.cancel("Operation cancelled.");
2226
+ process.exit(0);
2227
+ }
2228
+ return shouldUpdate ? updateTarget : void 0;
2229
+ }
2230
+ async function applyPackageManagerMigration(projectRoot, isMonorepo, options, detectedConfig) {
2231
+ const targetPackageManagerSpec = await resolveTargetPackageManagerSpec(options, detectedConfig) ?? await promptForPackageManagerMajorUpdate(options, detectedConfig);
2232
+ if (targetPackageManagerSpec == null) return detectedConfig;
2233
+ const targetNodeVersion = await promptForNodeRequirementUpdate(
2234
+ options,
2235
+ detectedConfig,
2236
+ targetPackageManagerSpec
2237
+ );
2238
+ const migrationConfig = {
2239
+ ...detectedConfig,
2240
+ packageManager: targetPackageManagerSpec.name,
2241
+ targetPackageManagerSpec,
2242
+ targetNodeVersion
2243
+ };
2244
+ const changes = [
2245
+ ...await getPackageManagerConfigUpdates(projectRoot, migrationConfig),
2246
+ ...isMonorepo || targetPackageManagerSpec.name === "pnpm" ? await getWorkspaceConfigUpdates(projectRoot, migrationConfig) : []
2247
+ ].filter((change) => change.status === "added" || change.status === "modified");
2248
+ if (changes.length === 0) return migrationConfig;
2249
+ console.log();
2250
+ console.log(color__default.cyan("Package Manager:"));
2251
+ for (const change of changes) {
2252
+ console.log(formatFileChange(change));
2253
+ }
2254
+ await applyUpdates(changes, projectRoot);
2255
+ console.log(color__default.green("\u2713") + ` Package Manager: updated ${changes.length}`);
2256
+ return migrationConfig;
2257
+ }
1974
2258
  async function processUpdateCategory(category, projectRoot, options) {
1975
2259
  const newChanges = category.changes.filter((change) => change.status === "added");
1976
2260
  const modifiedChanges = category.changes.filter((change) => change.status === "modified");
@@ -2010,11 +2294,8 @@ async function processUpdateCategory(category, projectRoot, options) {
2010
2294
  if (addedCount > 0) parts.push(`added ${addedCount}`);
2011
2295
  if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
2012
2296
  console.log(color__default.green("\u2713") + ` ${category.label}: ${parts.join(", ")}`);
2013
- console.log();
2014
2297
  return "updated";
2015
2298
  }
2016
- console.log(color__default.dim(` Skipped ${category.label}`));
2017
- console.log();
2018
2299
  return "skipped";
2019
2300
  }
2020
2301
  async function handleUpdateCommand(options, handleFixCommand) {
@@ -2027,7 +2308,7 @@ async function handleUpdateCommand(options, handleFixCommand) {
2027
2308
  }
2028
2309
  const isMonorepo = monorepoRoot != null;
2029
2310
  if (isMonorepo) {
2030
- const { valid, errors } = await validateWorkspace(projectRoot);
2311
+ const { valid, errors } = await workspace.validateWorkspace(projectRoot);
2031
2312
  if (!valid) {
2032
2313
  console.log(color__default.yellow("!") + " Workspace has issues:");
2033
2314
  for (const error of errors) {
@@ -2080,6 +2361,8 @@ async function handleUpdateCommand(options, handleFixCommand) {
2080
2361
  console.log(color__default.dim(` Skipped ${skippedCount}`));
2081
2362
  }
2082
2363
  }
2364
+ const finalConfig = await applyPackageManagerMigration(projectRoot, isMonorepo, options, config);
2365
+ await promptForPackageUpdate(projectRoot, finalConfig, options);
2083
2366
  process.exit(0);
2084
2367
  }
2085
2368
 
@@ -2097,9 +2380,10 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2097
2380
  message: "Package name?",
2098
2381
  initialValue: `@${scope}/`,
2099
2382
  validate: (value) => {
2100
- const validationError = workspace.validatePackageName(value);
2383
+ const packageName = value ?? "";
2384
+ const validationError = workspace.validatePackageName(packageName);
2101
2385
  if (validationError) return validationError;
2102
- const dirName = value.includes("/") ? value.split("/").pop() : value;
2386
+ const dirName = workspace.getPackageDirectoryName(packageName);
2103
2387
  if (!dirName) return "Package name is required";
2104
2388
  if (!hasCustomDirectories) {
2105
2389
  const targetPath = node_path.join(monorepoRoot, defaultDir, dirName);
@@ -2116,7 +2400,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
2116
2400
  return false;
2117
2401
  }
2118
2402
  const scopedName = packageNameInput;
2119
- const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
2403
+ const shortName = workspace.getPackageDirectoryName(scopedName);
2120
2404
  const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
2121
2405
  let targetDir = defaultDir;
2122
2406
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
@@ -2197,10 +2481,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2197
2481
  const template = options.template ?? "vanilla";
2198
2482
  const baseTemplate = workspace.getBaseTemplate(template);
2199
2483
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2200
- const fullPackagePath = node_path.join(monorepoRoot, targetDir, name);
2484
+ const packageDirName = workspace.getPackageDirectoryName(name);
2485
+ const fullPackagePath = node_path.join(monorepoRoot, targetDir, packageDirName);
2201
2486
  try {
2202
- await promises$1.access(fullPackagePath, node_fs.constants.F_OK);
2203
- console.error(color__default.red("Error:") + ` Directory ${targetDir}/${name} already exists`);
2487
+ await promises.access(fullPackagePath, node_fs.constants.F_OK);
2488
+ console.error(color__default.red("Error:") + ` Directory ${targetDir}/${packageDirName} already exists`);
2204
2489
  process.exit(1);
2205
2490
  } catch {
2206
2491
  }
@@ -2213,7 +2498,7 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2213
2498
  };
2214
2499
  const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
2215
2500
  const isLibrary = projectType === "library";
2216
- const relativePkgPath = node_path.join(targetDir, name);
2501
+ const relativePkgPath = node_path.join(targetDir, packageDirName);
2217
2502
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2218
2503
  const projectOptions = {
2219
2504
  name: scopedName,
@@ -2241,11 +2526,11 @@ async function handleWorkspaceCommand(name, options, writeGeneratedFiles) {
2241
2526
  triplex: options.triplex ? {} : void 0
2242
2527
  }
2243
2528
  };
2244
- console.log(color__default.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2529
+ console.log(color__default.cyan("Creating") + ` ${scopedName} in ${targetDir}/${packageDirName}...`);
2245
2530
  try {
2246
2531
  const { files } = await workspace.planProject(workspace.resolveProjectPlanInput(projectOptions));
2247
2532
  await writeGeneratedFiles(fullPackagePath, files);
2248
- console.log(color__default.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2533
+ console.log(color__default.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${packageDirName}`);
2249
2534
  process.exit(0);
2250
2535
  } catch (error) {
2251
2536
  console.error(color__default.red("Error:") + " Failed to create package");
@@ -2316,13 +2601,13 @@ async function writeGeneratedFiles(basePath, files) {
2316
2601
  const filePaths = Object.keys(files).sort();
2317
2602
  for (const filePath of filePaths) {
2318
2603
  const fullFilePath = node_path.join(basePath, filePath);
2319
- await promises$1.mkdir(node_path.dirname(fullFilePath), { recursive: true });
2604
+ await promises.mkdir(node_path.dirname(fullFilePath), { recursive: true });
2320
2605
  const file = files[filePath];
2321
2606
  if (file.type === "text") {
2322
- await promises$1.writeFile(fullFilePath, file.content);
2607
+ await promises.writeFile(fullFilePath, file.content);
2323
2608
  } else {
2324
2609
  const response = await undici.fetch(file.url);
2325
- await promises$1.writeFile(fullFilePath, response.body);
2610
+ await promises.writeFile(fullFilePath, response.body);
2326
2611
  }
2327
2612
  }
2328
2613
  }
@@ -2390,7 +2675,8 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2390
2675
  const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
2391
2676
  projectOptions.name ??= defaultFallbackName;
2392
2677
  const packageManager = workspace.getPackageManagerName(projectOptions.packageManager);
2393
- const projectPath = node_path.join(node_process.cwd(), projectOptions.name);
2678
+ const projectDirName = workspace.getPackageDirectoryName(projectOptions.name);
2679
+ const projectPath = node_path.join(node_process.cwd(), projectDirName);
2394
2680
  const spinner = p__namespace.spinner();
2395
2681
  spinner.start("Creating project...");
2396
2682
  try {
@@ -2399,15 +2685,11 @@ async function handleSingleWorkspaceCreation(projectOptions, isNonInteractive) {
2399
2685
  await writeGeneratedFiles(projectPath, files);
2400
2686
  spinner.stop(color__default.green.inverse(" \u2713 Project created! "));
2401
2687
  if (isNonInteractive) process.exit(0);
2402
- const nextSteps = projectOptions.projectType === "library" ? [
2403
- `cd ${projectOptions.name}`,
2404
- `${packageManager} install`,
2405
- `${packageManager} run build`
2406
- ].join("\n") : [
2407
- `cd ${projectOptions.name}`,
2408
- `${packageManager} install`,
2409
- `${packageManager} run dev`
2410
- ].join("\n");
2688
+ const nextSteps = projectOptions.projectType === "library" ? [`cd ${projectDirName}`, `${packageManager} install`, `${packageManager} run build`].join(
2689
+ "\n"
2690
+ ) : [`cd ${projectDirName}`, `${packageManager} install`, `${packageManager} run dev`].join(
2691
+ "\n"
2692
+ );
2411
2693
  p__namespace.note(nextSteps, "Next steps");
2412
2694
  p__namespace.outro(color__default.green("Happy coding! \u2728"));
2413
2695
  } catch (error) {
@@ -2423,7 +2705,7 @@ async function main() {
2423
2705
  ).option(
2424
2706
  "--template <type>",
2425
2707
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2426
- ).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(
2708
+ ).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(
2427
2709
  "--pnpm-manage-versions",
2428
2710
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2429
2711
  ).option(
@@ -2437,18 +2719,24 @@ async function main() {
2437
2719
  process.chdir(options.path);
2438
2720
  }
2439
2721
  if (options.clearConfig) {
2440
- clearConfig();
2722
+ workspace.clearConfig();
2441
2723
  console.log("Configuration cleared.");
2442
2724
  process.exit(0);
2443
2725
  }
2444
2726
  if (options.configPath) {
2445
- console.log(getConfigPath());
2727
+ console.log(workspace.getConfigPath());
2446
2728
  process.exit(0);
2447
2729
  }
2448
2730
  if (options.ide && !["vscode", "none"].includes(options.ide)) {
2449
2731
  console.error(color__default.red("Error:") + ' --ide must be "vscode" or "none"');
2450
2732
  process.exit(1);
2451
2733
  }
2734
+ const packageManagerSpec = workspace.parsePackageManagerSpec(options.packageManager);
2735
+ if (options.packageManager && packageManagerSpec == null) {
2736
+ console.error(color__default.red("Error:") + " --package-manager must be npm, yarn, or pnpm");
2737
+ console.log(color__default.dim(" Version specs are allowed, e.g. pnpm@10 or pnpm@11"));
2738
+ process.exit(1);
2739
+ }
2452
2740
  if (name?.startsWith("-")) {
2453
2741
  switch (name) {
2454
2742
  case "--version":
@@ -2460,11 +2748,11 @@ async function main() {
2460
2748
  program.help();
2461
2749
  break;
2462
2750
  case "--clear-config":
2463
- clearConfig();
2751
+ workspace.clearConfig();
2464
2752
  console.log("Configuration cleared.");
2465
2753
  process.exit(0);
2466
2754
  case "--config-path":
2467
- console.log(getConfigPath());
2755
+ console.log(workspace.getConfigPath());
2468
2756
  process.exit(0);
2469
2757
  case "--check":
2470
2758
  await handleCheckCommand();
@@ -2534,7 +2822,7 @@ async function main() {
2534
2822
  viverse: options.viverse ? {} : void 0,
2535
2823
  triplex: options.triplex ? {} : void 0
2536
2824
  },
2537
- packageManager: options.packageManager ? { name: options.packageManager } : void 0,
2825
+ packageManager: packageManagerSpec,
2538
2826
  pnpmManageVersions: options.pnpmManageVersions,
2539
2827
  engine: { name: "node", version: options.nodeVersion ?? "latest" }
2540
2828
  };
@@ -2545,7 +2833,7 @@ async function main() {
2545
2833
  bundler: options.bundler,
2546
2834
  linter: options.linter,
2547
2835
  formatter: options.formatter,
2548
- packageManager: options.packageManager,
2836
+ packageManager: packageManagerSpec,
2549
2837
  ide: options.ide,
2550
2838
  engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2551
2839
  pnpmManageVersions: options.pnpmManageVersions,