create-krispya 0.6.0 → 0.8.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
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from 'module';
3
- import { cwd } from 'process';
4
- import { join, dirname, resolve } from 'path';
5
- import { access, constants, readFile, mkdir, writeFile, rm, readdir, unlink } from 'fs/promises';
6
- import { constants as constants$1 } from 'fs';
7
- import { Command } from 'commander';
8
2
  import * as p from '@clack/prompts';
9
3
  import color from 'chalk';
4
+ import { Command } from 'commander';
5
+ import { constants as constants$2 } from 'node:fs';
6
+ import { mkdir as mkdir$1, writeFile as writeFile$1, unlink, access as access$1, readFile as readFile$1 } from 'node:fs/promises';
7
+ import { createRequire } from 'node:module';
8
+ import { join as join$1, dirname as dirname$1, resolve } from 'node:path';
9
+ import { cwd } from 'node:process';
10
10
  import { fetch } from 'undici';
11
11
  import { spawn } from 'child_process';
12
- import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, d as detectTooling, c as generateAiFiles, A as ALL_AI_PLATFORMS, e as generateVscodeFiles, f as generateTypescriptConfigPackage, h as generateOxlintConfigPackage, i as generateEslintConfigPackage, j as generateOxfmtConfigPackage, k as generatePrettierConfigPackage, p as parseWorkspaceYamlContent, l as getLatestNpmVersion, m as generate, n as getLatestPnpmVersion, o as getLatestYarnVersion, q as getLatestNpmCliVersion, r as getLatestNodeVersion, s as AI_PLATFORM_LABELS, t as AI_PLATFORM_HINTS, v as validatePackageName } from './chunks/index.mjs';
12
+ import { g as getEngineName, a as getBaseTemplate, b as getLanguageFromTemplate, c as getPackageManagerName, d as generateRandomName, e as detectTooling, r as resolveMonorepoRootPackageVersions, f as generateAiFiles, A as ALL_AI_PLATFORMS, h as generateVscodeFiles, i as generateTypescriptConfigPackage, j as generateOxlintConfigPackage, k as generateEslintConfigPackage, l as generateOxfmtConfigPackage, m as generatePrettierConfigPackage, n as generateGitignore, o as getResolvedPackageVersion, p as parseWorkspaceYamlContent, q as formatResolvedPackageVersion, s as resolvePackageManager, t as resolveEngine, u as resolveProjectPackageVersions, v as generate, w as parsePackageManager, x as parseEngine, y as AI_PLATFORM_LABELS, z as AI_PLATFORM_HINTS, B as validatePackageName } from './chunks/index.mjs';
13
13
  import Conf from 'conf';
14
+ import { readFile, mkdir, writeFile, rm, access, readdir, constants as constants$1 } from 'fs/promises';
15
+ import { constants } from 'fs';
16
+ import { join, dirname } from 'path';
14
17
 
15
18
  const editorNames = {
16
19
  cursor: "Cursor",
@@ -64,11 +67,19 @@ function formatConfigSummary(options, inherited) {
64
67
  } else {
65
68
  lines.push(formatRow("Bundler", "vite"));
66
69
  }
67
- const nodeVersionInherited = inherited?.nodeVersion !== void 0;
68
- lines.push(formatRow("Node version", options.nodeVersion || "latest", nodeVersionInherited));
70
+ const engineInherited = inherited?.engine !== void 0;
71
+ lines.push(
72
+ formatRow(
73
+ "Engine",
74
+ `${getEngineName(options.engine)}@${options.engine?.version ?? "latest"}`,
75
+ engineInherited
76
+ )
77
+ );
69
78
  const pmInherited = inherited?.packageManager !== void 0;
70
- lines.push(formatRow("Package manager", options.packageManager || "pnpm", pmInherited));
71
- if (options.packageManager === "pnpm") {
79
+ lines.push(
80
+ formatRow("Package manager", getPackageManagerName(options.packageManager), pmInherited)
81
+ );
82
+ if (getPackageManagerName(options.packageManager) === "pnpm") {
72
83
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
73
84
  const pnpmVersionInherited = inherited?.pnpmManageVersions !== void 0;
74
85
  lines.push(formatRow("\u21B3 Version managed", versionManaged, pnpmVersionInherited, ""));
@@ -122,8 +133,10 @@ function formatMonorepoConfigSummary(options) {
122
133
  const dots = color.gray(".".repeat(dotCount));
123
134
  return `${indent}${label} ${dots} ${value}`;
124
135
  };
125
- lines.push(formatRow("Node version", options.nodeVersion || "latest"));
126
- lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
136
+ lines.push(
137
+ formatRow("Engine", `${getEngineName(options.engine)}@${options.engine.version ?? "latest"}`)
138
+ );
139
+ lines.push(formatRow("Package manager", options.packageManager));
127
140
  if (options.packageManager === "pnpm") {
128
141
  const versionManaged = options.pnpmManageVersions ? "yes" : "no";
129
142
  lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
@@ -151,9 +164,6 @@ function setReuseWindow(reuse) {
151
164
  function getAiPlatforms() {
152
165
  return config.get("aiPlatforms");
153
166
  }
154
- function setAiPlatforms(platforms) {
155
- config.set("aiPlatforms", platforms);
156
- }
157
167
  function getConfigStrategy() {
158
168
  return config.get("configStrategy") ?? "stealth";
159
169
  }
@@ -174,9 +184,9 @@ function getDefaultOptions(template, name, projectType = "app", libraryBundler,
174
184
  template,
175
185
  projectType,
176
186
  libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
177
- packageManager: inheritedSettings?.packageManager ?? "pnpm",
187
+ packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
178
188
  pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
179
- nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
189
+ engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
180
190
  linter: inheritedSettings?.linter ?? "oxlint",
181
191
  formatter: inheritedSettings?.formatter ?? "prettier",
182
192
  // Libraries get vitest by default, apps don't
@@ -271,14 +281,14 @@ async function promptForCustomization(template, name, projectType, integrations,
271
281
  }
272
282
  libraryBundler = bundler;
273
283
  }
274
- let nodeVersion = inheritedSettings?.nodeVersion ?? presets?.nodeVersion ?? "latest";
275
- let finalPackageManager = inheritedSettings?.packageManager ?? presets?.packageManager ?? "pnpm";
284
+ let engine = inheritedSettings?.engine ?? presets?.engine ?? { name: "node", version: "latest" };
285
+ let finalPackageManager = inheritedSettings?.packageManager?.name ?? presets?.packageManager ?? "pnpm";
276
286
  let pnpmManageVersions = inheritedSettings?.pnpmManageVersions ?? presets?.pnpmManageVersions ?? true;
277
- if (!inheritedSettings?.nodeVersion) {
287
+ if (!inheritedSettings?.engine?.version) {
278
288
  const nodeVersionInput = await p.text({
279
289
  message: "Node.js version",
280
- placeholder: presets?.nodeVersion ?? "latest",
281
- defaultValue: presets?.nodeVersion ?? "latest",
290
+ placeholder: presets?.engine?.version ?? "latest",
291
+ defaultValue: presets?.engine?.version ?? "latest",
282
292
  validate: (value) => {
283
293
  if (!value.length) return "Required";
284
294
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
@@ -290,7 +300,7 @@ async function promptForCustomization(template, name, projectType, integrations,
290
300
  p.cancel("Operation cancelled.");
291
301
  process.exit(0);
292
302
  }
293
- nodeVersion = nodeVersionInput;
303
+ engine = { name: "node", version: nodeVersionInput };
294
304
  }
295
305
  if (!inheritedSettings?.packageManager) {
296
306
  const packageManager = await p.select({
@@ -396,8 +406,8 @@ async function promptForCustomization(template, name, projectType, integrations,
396
406
  template: finalTemplate,
397
407
  projectType,
398
408
  libraryBundler: projectType === "library" ? libraryBundler : void 0,
399
- nodeVersion,
400
- packageManager: finalPackageManager,
409
+ engine,
410
+ packageManager: { name: finalPackageManager },
401
411
  pnpmManageVersions,
402
412
  linter,
403
413
  formatter,
@@ -443,9 +453,9 @@ function getDefaultMonorepoOptions(name) {
443
453
  return {
444
454
  name,
445
455
  projectType: "monorepo",
446
- packageManager: "pnpm",
456
+ packageManager: { name: "pnpm" },
447
457
  pnpmManageVersions: true,
448
- nodeVersion: "latest",
458
+ engine: { name: "node", version: "latest" },
449
459
  linter: "oxlint",
450
460
  formatter: "prettier"
451
461
  };
@@ -453,8 +463,8 @@ function getDefaultMonorepoOptions(name) {
453
463
  async function promptForMonorepoCustomization(name, presets) {
454
464
  const nodeVersion = await p.text({
455
465
  message: "Node.js version",
456
- placeholder: presets?.nodeVersion ?? "latest",
457
- defaultValue: presets?.nodeVersion ?? "latest",
466
+ placeholder: presets?.engine?.version ?? "latest",
467
+ defaultValue: presets?.engine?.version ?? "latest",
458
468
  validate: (value) => {
459
469
  if (!value.length) return "Required";
460
470
  if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
@@ -503,8 +513,8 @@ async function promptForMonorepoCustomization(name, presets) {
503
513
  return {
504
514
  name,
505
515
  projectType: "monorepo",
506
- nodeVersion,
507
- packageManager: "pnpm",
516
+ engine: { name: "node", version: nodeVersion },
517
+ packageManager: { name: "pnpm" },
508
518
  pnpmManageVersions: managePnpm,
509
519
  linter,
510
520
  formatter
@@ -515,15 +525,15 @@ async function promptForMonorepo(workspaceName, presets) {
515
525
  if (presets) {
516
526
  if (presets.linter) defaultOptions.linter = presets.linter;
517
527
  if (presets.formatter) defaultOptions.formatter = presets.formatter;
518
- if (presets.nodeVersion) defaultOptions.nodeVersion = presets.nodeVersion;
528
+ if (presets.engine) defaultOptions.engine = presets.engine;
519
529
  if (presets.pnpmManageVersions !== void 0)
520
530
  defaultOptions.pnpmManageVersions = presets.pnpmManageVersions;
521
531
  }
522
532
  p.note(
523
533
  formatMonorepoConfigSummary({
524
534
  name: defaultOptions.name,
525
- nodeVersion: defaultOptions.nodeVersion ?? "latest",
526
- packageManager: defaultOptions.packageManager ?? "pnpm",
535
+ engine: defaultOptions.engine ?? { name: "node", version: "latest" },
536
+ packageManager: getPackageManagerName(defaultOptions.packageManager),
527
537
  pnpmManageVersions: defaultOptions.pnpmManageVersions,
528
538
  linter: defaultOptions.linter ?? "oxlint",
529
539
  formatter: defaultOptions.formatter ?? "prettier"
@@ -580,12 +590,7 @@ async function promptForOptions(name, presets) {
580
590
  if (projectType === "monorepo") {
581
591
  return promptForMonorepo(projectName, presets);
582
592
  }
583
- return promptForPackageOptions(
584
- projectName,
585
- projectType,
586
- void 0,
587
- presets
588
- );
593
+ return promptForPackageOptions(projectName, projectType, void 0, presets);
589
594
  }
590
595
  function customTemplateToOptions(customTemplate, name, projectType, inheritedSettings) {
591
596
  const baseTemplate = customTemplate.baseTemplate;
@@ -594,9 +599,9 @@ function customTemplateToOptions(customTemplate, name, projectType, inheritedSet
594
599
  name,
595
600
  template,
596
601
  projectType,
597
- packageManager: inheritedSettings?.packageManager ?? "pnpm",
602
+ packageManager: inheritedSettings?.packageManager ?? { name: "pnpm" },
598
603
  pnpmManageVersions: inheritedSettings?.pnpmManageVersions ?? true,
599
- nodeVersion: inheritedSettings?.nodeVersion ?? "latest",
604
+ engine: inheritedSettings?.engine ?? { name: "node", version: "latest" },
600
605
  linter: inheritedSettings?.linter ?? customTemplate.linter,
601
606
  formatter: inheritedSettings?.formatter ?? customTemplate.formatter,
602
607
  testing: customTemplate.testing,
@@ -627,8 +632,8 @@ function presetsToInheritedSettings(presets) {
627
632
  return {
628
633
  linter: presets.linter,
629
634
  formatter: presets.formatter,
630
- packageManager: presets.packageManager,
631
- nodeVersion: presets.nodeVersion,
635
+ packageManager: presets.packageManager ? { name: presets.packageManager } : void 0,
636
+ engine: presets.engine,
632
637
  pnpmManageVersions: presets.pnpmManageVersions
633
638
  };
634
639
  }
@@ -730,53 +735,9 @@ async function promptForPackageOptions(projectName, projectType, inheritedSettin
730
735
  );
731
736
  }
732
737
 
733
- async function checkAnyExists(paths) {
734
- for (const path of paths) {
735
- try {
736
- await access(path, constants.F_OK);
737
- return true;
738
- } catch {
739
- }
740
- }
741
- return false;
742
- }
743
- async function validateWorkspace(monorepoRoot) {
744
- const errors = [];
745
- const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
746
- try {
747
- await access(tsConfigPath, constants.F_OK);
748
- } catch {
749
- errors.push("Missing .config/typescript package");
750
- }
751
- const linterPaths = [
752
- join(monorepoRoot, ".config/oxlint/package.json"),
753
- join(monorepoRoot, ".config/eslint/package.json"),
754
- join(monorepoRoot, "eslint.config.js"),
755
- join(monorepoRoot, "biome.json")
756
- ];
757
- const hasLinter = await checkAnyExists(linterPaths);
758
- if (!hasLinter) {
759
- errors.push(
760
- "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
761
- );
762
- }
763
- const formatterPaths = [
764
- join(monorepoRoot, ".config/oxfmt/package.json"),
765
- join(monorepoRoot, ".config/prettier/package.json"),
766
- join(monorepoRoot, ".prettierrc.json"),
767
- join(monorepoRoot, "biome.json")
768
- ];
769
- const hasFormatter = await checkAnyExists(formatterPaths);
770
- if (!hasFormatter) {
771
- errors.push(
772
- "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
773
- );
774
- }
775
- return { valid: errors.length === 0, errors };
776
- }
777
-
778
- async function detectCurrentConfig(root) {
738
+ async function detectCurrentConfig(root, isMonorepo = true) {
779
739
  let name = root.split(/[/\\]/).pop() ?? "workspace";
740
+ let packageManager = "pnpm";
780
741
  try {
781
742
  const pkgPath = join(root, "package.json");
782
743
  const content = await readFile(pkgPath, "utf-8");
@@ -784,47 +745,63 @@ async function detectCurrentConfig(root) {
784
745
  if (pkgJson.name) {
785
746
  name = pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
786
747
  }
748
+ if (pkgJson.packageManager) {
749
+ packageManager = pkgJson.packageManager.split("@")[0] ?? packageManager;
750
+ }
787
751
  } catch {
788
752
  }
789
753
  const tooling = await detectTooling(root);
754
+ const configStrategy = isMonorepo ? void 0 : await detectStandaloneConfigStrategy(root);
790
755
  return {
791
756
  name,
792
757
  linter: tooling.linter ?? "oxlint",
793
758
  formatter: tooling.formatter ?? "prettier",
794
- packageManager: "pnpm"
759
+ packageManager,
760
+ isMonorepo,
761
+ configStrategy
795
762
  };
796
763
  }
797
- function generateExpectedFiles(config) {
798
- const { name, linter, formatter, packageManager } = config;
764
+ async function detectStandaloneConfigStrategy(root) {
765
+ const hasStealthConfig = await Promise.all([
766
+ fileExists$1(join(root, ".config/tsconfig.app.json")),
767
+ fileExists$1(join(root, ".config/tsconfig.node.json")),
768
+ fileExists$1(join(root, ".config/prettier.json")),
769
+ fileExists$1(join(root, ".config/oxlint.json"))
770
+ ]).then((matches) => matches.some(Boolean));
771
+ return hasStealthConfig ? "stealth" : "root";
772
+ }
773
+ async function generateExpectedFiles(config) {
774
+ const { name, linter, formatter, packageManager, isMonorepo, configStrategy } = config;
775
+ const versions = linter === "biome" || formatter === "biome" ? await resolveMonorepoRootPackageVersions({ linter, formatter }) : {};
799
776
  const aiFilesMap = {};
800
777
  generateAiFiles(aiFilesMap, {
801
778
  name,
802
779
  packageManager,
803
780
  linter,
804
781
  formatter,
805
- isMonorepo: true,
782
+ isMonorepo,
783
+ configStrategy,
806
784
  platforms: ALL_AI_PLATFORMS
807
785
  });
808
786
  const vscodeFiles = {};
809
787
  generateVscodeFiles(vscodeFiles, linter, formatter);
810
788
  const configPackages = {};
811
- generateTypescriptConfigPackage(configPackages);
812
- if (linter === "oxlint") {
813
- generateOxlintConfigPackage(configPackages);
814
- } else if (linter === "eslint") {
815
- generateEslintConfigPackage(configPackages);
816
- }
817
- if (formatter === "oxfmt") {
818
- generateOxfmtConfigPackage(configPackages);
819
- } else if (formatter === "prettier") {
820
- generatePrettierConfigPackage(configPackages);
789
+ if (isMonorepo) {
790
+ generateTypescriptConfigPackage(configPackages);
791
+ if (linter === "oxlint") {
792
+ generateOxlintConfigPackage(configPackages);
793
+ } else if (linter === "eslint") {
794
+ generateEslintConfigPackage(configPackages);
795
+ }
796
+ if (formatter === "oxfmt") {
797
+ generateOxfmtConfigPackage(configPackages);
798
+ } else if (formatter === "prettier") {
799
+ generatePrettierConfigPackage(configPackages);
800
+ }
821
801
  }
822
802
  const workspaceConfig = {};
823
803
  const rootConfig = {};
824
- rootConfig[".gitignore"] = {
825
- type: "text",
826
- content: ["node_modules", "dist", "*.tsbuildinfo", ".DS_Store"].join("\n")
827
- };
804
+ rootConfig[".gitignore"] = generateGitignore(isMonorepo ? "workspace-root" : "standalone");
828
805
  rootConfig[".gitattributes"] = {
829
806
  type: "text",
830
807
  content: `* text=auto eol=lf
@@ -833,8 +810,9 @@ function generateExpectedFiles(config) {
833
810
  `
834
811
  };
835
812
  if (linter === "biome" || formatter === "biome") {
813
+ const biomeVersion = getResolvedPackageVersion(versions, "@biomejs/biome");
836
814
  const biomeConfig = {
837
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
815
+ $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
838
816
  vcs: {
839
817
  enabled: true,
840
818
  clientKind: "git",
@@ -865,7 +843,7 @@ function generateExpectedFiles(config) {
865
843
  }
866
844
  async function fileExists$1(path) {
867
845
  try {
868
- await access(path, constants$1.F_OK);
846
+ await access(path, constants.F_OK);
869
847
  return true;
870
848
  } catch {
871
849
  return false;
@@ -968,9 +946,7 @@ onlyBuiltDependencies:
968
946
  }
969
947
  if (!currentContent.includes(".config/*") && !currentContent.includes('".config/*"')) {
970
948
  const lines = updatedContent.split("\n");
971
- const packagesIndex = lines.findIndex(
972
- (line) => line.trim().startsWith("packages:")
973
- );
949
+ const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
974
950
  if (packagesIndex !== -1) {
975
951
  lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
976
952
  updatedContent = lines.join("\n");
@@ -1036,6 +1012,14 @@ function needsMigration(current, target) {
1036
1012
  async function getMigrationPlan(current, target, root) {
1037
1013
  const toLinter = target.linter ?? current.linter;
1038
1014
  const toFormatter = target.formatter ?? current.formatter;
1015
+ const targetVersions = toLinter === "biome" || toFormatter === "biome" ? await resolveMonorepoRootPackageVersions({
1016
+ linter: toLinter,
1017
+ formatter: toFormatter
1018
+ }) : {};
1019
+ const biomeSchemaUrl = toLinter === "biome" || toFormatter === "biome" ? `https://biomejs.dev/schemas/${getResolvedPackageVersion(
1020
+ targetVersions,
1021
+ "@biomejs/biome"
1022
+ )}/schema.json` : "";
1039
1023
  const changes = [];
1040
1024
  if (toLinter !== current.linter) {
1041
1025
  if (current.linter !== "biome") {
@@ -1070,7 +1054,7 @@ async function getMigrationPlan(current, target, root) {
1070
1054
  description: "Add biome.json config",
1071
1055
  content: JSON.stringify(
1072
1056
  {
1073
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1057
+ $schema: biomeSchemaUrl,
1074
1058
  vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1075
1059
  linter: { enabled: true, rules: { recommended: true } },
1076
1060
  formatter: { enabled: true }
@@ -1086,7 +1070,7 @@ async function getMigrationPlan(current, target, root) {
1086
1070
  description: "Add biome.json config (linter only)",
1087
1071
  content: JSON.stringify(
1088
1072
  {
1089
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1073
+ $schema: biomeSchemaUrl,
1090
1074
  vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1091
1075
  linter: { enabled: true, rules: { recommended: true } },
1092
1076
  formatter: { enabled: false }
@@ -1139,7 +1123,7 @@ async function getMigrationPlan(current, target, root) {
1139
1123
  description: "Add biome.json config (formatter only)",
1140
1124
  content: JSON.stringify(
1141
1125
  {
1142
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
1126
+ $schema: biomeSchemaUrl,
1143
1127
  vcs: { enabled: true, clientKind: "git", useIgnoreFile: true },
1144
1128
  linter: { enabled: false },
1145
1129
  formatter: { enabled: true }
@@ -1162,12 +1146,7 @@ async function getMigrationPlan(current, target, root) {
1162
1146
  path: "package.json",
1163
1147
  description: "Update root package.json (devDependencies, scripts)"
1164
1148
  });
1165
- const subPackageUpdates = await getSubPackageUpdates(
1166
- root,
1167
- current,
1168
- toLinter,
1169
- toFormatter
1170
- );
1149
+ const subPackageUpdates = await getSubPackageUpdates(root, current, toLinter, toFormatter);
1171
1150
  return {
1172
1151
  fromLinter: current.linter,
1173
1152
  toLinter,
@@ -1277,23 +1256,15 @@ async function updateRootPackageJson(root, plan) {
1277
1256
  const oldFormatterDep = FORMATTER_DEPS[plan.fromFormatter];
1278
1257
  delete devDeps[oldFormatterDep];
1279
1258
  }
1259
+ const resolvedVersions = await resolveMonorepoRootPackageVersions({
1260
+ linter: plan.toLinter,
1261
+ formatter: plan.toFormatter
1262
+ });
1280
1263
  const newLinterDep = LINTER_DEPS[plan.toLinter];
1281
- if (plan.toLinter === "oxlint") {
1282
- devDeps[newLinterDep] = "^1.36.0";
1283
- } else if (plan.toLinter === "eslint") {
1284
- devDeps[newLinterDep] = "^9.17.0";
1285
- } else if (plan.toLinter === "biome") {
1286
- devDeps[newLinterDep] = "^1.9.4";
1287
- }
1264
+ devDeps[newLinterDep] = formatResolvedPackageVersion(resolvedVersions, newLinterDep);
1288
1265
  if (plan.toFormatter !== plan.toLinter) {
1289
1266
  const newFormatterDep = FORMATTER_DEPS[plan.toFormatter];
1290
- if (plan.toFormatter === "oxfmt") {
1291
- devDeps[newFormatterDep] = "^0.21.0";
1292
- } else if (plan.toFormatter === "prettier") {
1293
- devDeps[newFormatterDep] = "^3.4.2";
1294
- } else if (plan.toFormatter === "biome") {
1295
- devDeps[newFormatterDep] = "^1.9.4";
1296
- }
1267
+ devDeps[newFormatterDep] = formatResolvedPackageVersion(resolvedVersions, newFormatterDep);
1297
1268
  }
1298
1269
  pkg.devDependencies = Object.fromEntries(
1299
1270
  Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))
@@ -1337,6 +1308,51 @@ function formatMigrationChange(change) {
1337
1308
  return ` ${icon} ${change.description}`;
1338
1309
  }
1339
1310
 
1311
+ async function checkAnyExists(paths) {
1312
+ for (const path of paths) {
1313
+ try {
1314
+ await access(path, constants$1.F_OK);
1315
+ return true;
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ return false;
1320
+ }
1321
+ async function validateWorkspace(monorepoRoot) {
1322
+ const errors = [];
1323
+ const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
1324
+ try {
1325
+ await access(tsConfigPath, constants$1.F_OK);
1326
+ } catch {
1327
+ errors.push("Missing .config/typescript package");
1328
+ }
1329
+ const linterPaths = [
1330
+ join(monorepoRoot, ".config/oxlint/package.json"),
1331
+ join(monorepoRoot, ".config/eslint/package.json"),
1332
+ join(monorepoRoot, "eslint.config.js"),
1333
+ join(monorepoRoot, "biome.json")
1334
+ ];
1335
+ const hasLinter = await checkAnyExists(linterPaths);
1336
+ if (!hasLinter) {
1337
+ errors.push(
1338
+ "Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
1339
+ );
1340
+ }
1341
+ const formatterPaths = [
1342
+ join(monorepoRoot, ".config/oxfmt/package.json"),
1343
+ join(monorepoRoot, ".config/prettier/package.json"),
1344
+ join(monorepoRoot, ".prettierrc.json"),
1345
+ join(monorepoRoot, "biome.json")
1346
+ ];
1347
+ const hasFormatter = await checkAnyExists(formatterPaths);
1348
+ if (!hasFormatter) {
1349
+ errors.push(
1350
+ "Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
1351
+ );
1352
+ }
1353
+ return { valid: errors.length === 0, errors };
1354
+ }
1355
+
1340
1356
  const require$1 = createRequire(import.meta.url);
1341
1357
  const pkg = require$1("../package.json");
1342
1358
  const META_OPTIONS = [
@@ -1357,7 +1373,7 @@ function hasConfigOptions(options) {
1357
1373
  }
1358
1374
  async function fileExists(path) {
1359
1375
  try {
1360
- await access(path, constants$1.F_OK);
1376
+ await access$1(path, constants$2.F_OK);
1361
1377
  return true;
1362
1378
  } catch {
1363
1379
  return false;
@@ -1388,7 +1404,7 @@ async function promptForAiPlatforms(isNonInteractive) {
1388
1404
  label: AI_PLATFORM_LABELS[platform],
1389
1405
  hint: AI_PLATFORM_HINTS[platform]
1390
1406
  })),
1391
- initialValues: [],
1407
+ initialValues: ["agents"],
1392
1408
  required: false
1393
1409
  });
1394
1410
  if (p.isCancel(selected)) {
@@ -1398,26 +1414,19 @@ async function promptForAiPlatforms(isNonInteractive) {
1398
1414
  if (platforms.length === 0) {
1399
1415
  return [];
1400
1416
  }
1401
- const saveChoice = await p.confirm({
1402
- message: "Save selection for future projects?",
1403
- initialValue: true
1404
- });
1405
- if (!p.isCancel(saveChoice) && saveChoice) {
1406
- setAiPlatforms(platforms);
1407
- }
1408
1417
  return platforms;
1409
1418
  }
1410
1419
  async function writeGeneratedFiles(basePath, files) {
1411
1420
  const filePaths = Object.keys(files).sort();
1412
1421
  for (const filePath of filePaths) {
1413
- const fullFilePath = join(basePath, filePath);
1414
- await mkdir(dirname(fullFilePath), { recursive: true });
1422
+ const fullFilePath = join$1(basePath, filePath);
1423
+ await mkdir$1(dirname$1(fullFilePath), { recursive: true });
1415
1424
  const file = files[filePath];
1416
1425
  if (file.type === "text") {
1417
- await writeFile(fullFilePath, file.content);
1426
+ await writeFile$1(fullFilePath, file.content);
1418
1427
  } else {
1419
1428
  const response = await fetch(file.url);
1420
- await writeFile(fullFilePath, response.body);
1429
+ await writeFile$1(fullFilePath, response.body);
1421
1430
  }
1422
1431
  }
1423
1432
  }
@@ -1429,23 +1438,34 @@ async function detectMonorepoRoot() {
1429
1438
  let currentDir = cwd();
1430
1439
  const root = resolve("/");
1431
1440
  while (currentDir !== root) {
1432
- const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
1441
+ const workspaceFile = join$1(currentDir, "pnpm-workspace.yaml");
1433
1442
  try {
1434
- await access(workspaceFile, constants$1.F_OK);
1435
- const content = await readFile(workspaceFile, "utf-8");
1443
+ await access$1(workspaceFile, constants$2.F_OK);
1444
+ const content = await readFile$1(workspaceFile, "utf-8");
1436
1445
  if (content.includes("packages:")) {
1437
1446
  return currentDir;
1438
1447
  }
1439
1448
  } catch {
1440
1449
  }
1441
- currentDir = dirname(currentDir);
1450
+ currentDir = dirname$1(currentDir);
1442
1451
  }
1443
1452
  return null;
1444
1453
  }
1454
+ async function detectPackageRoot() {
1455
+ let currentDir = cwd();
1456
+ const root = resolve("/");
1457
+ while (currentDir !== root) {
1458
+ if (await fileExists(join$1(currentDir, "package.json"))) {
1459
+ return currentDir;
1460
+ }
1461
+ currentDir = dirname$1(currentDir);
1462
+ }
1463
+ return await fileExists(join$1(root, "package.json")) ? root : null;
1464
+ }
1445
1465
  async function parseWorkspaceDirectories(monorepoRoot) {
1446
1466
  try {
1447
- const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
1448
- const content = await readFile(workspaceFile, "utf-8");
1467
+ const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
1468
+ const content = await readFile$1(workspaceFile, "utf-8");
1449
1469
  return parseWorkspaceYamlContent(content);
1450
1470
  } catch {
1451
1471
  return [];
@@ -1454,34 +1474,23 @@ async function parseWorkspaceDirectories(monorepoRoot) {
1454
1474
  async function detectWorkspaceSettings(monorepoRoot) {
1455
1475
  try {
1456
1476
  const tooling = await detectTooling(monorepoRoot);
1457
- const pkgPath = join(monorepoRoot, "package.json");
1458
- const content = await readFile(pkgPath, "utf-8");
1477
+ const pkgPath = join$1(monorepoRoot, "package.json");
1478
+ const content = await readFile$1(pkgPath, "utf-8");
1459
1479
  const pkgJson = JSON.parse(content);
1460
- let packageManager;
1461
- if (pkgJson.packageManager) {
1462
- packageManager = pkgJson.packageManager.split("@")[0];
1463
- }
1464
- let nodeVersion;
1465
- if (pkgJson.engines?.node) {
1466
- const match = pkgJson.engines.node.match(/(\d+)/);
1467
- if (match) {
1468
- nodeVersion = match[1];
1469
- }
1470
- }
1480
+ const packageManager = parsePackageManager(pkgJson.packageManager);
1481
+ const engine = parseEngine(pkgJson.engines);
1471
1482
  let pnpmManageVersions;
1472
1483
  try {
1473
- const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
1474
- const workspaceContent = await readFile(workspaceFile, "utf-8");
1475
- pnpmManageVersions = workspaceContent.includes(
1476
- "manage-package-manager-versions: true"
1477
- );
1484
+ const workspaceFile = join$1(monorepoRoot, "pnpm-workspace.yaml");
1485
+ const workspaceContent = await readFile$1(workspaceFile, "utf-8");
1486
+ pnpmManageVersions = workspaceContent.includes("manage-package-manager-versions: true");
1478
1487
  } catch {
1479
1488
  }
1480
1489
  return {
1481
1490
  linter: tooling.linter,
1482
1491
  formatter: tooling.formatter,
1483
1492
  packageManager,
1484
- nodeVersion,
1493
+ engine,
1485
1494
  pnpmManageVersions
1486
1495
  };
1487
1496
  } catch {
@@ -1490,17 +1499,17 @@ async function detectWorkspaceSettings(monorepoRoot) {
1490
1499
  }
1491
1500
  async function detectExistingConfigs(monorepoRoot) {
1492
1501
  const configs = {};
1493
- const eslintPath = join(monorepoRoot, "eslint.config.js");
1502
+ const eslintPath = join$1(monorepoRoot, "eslint.config.js");
1494
1503
  if (await fileExists(eslintPath)) {
1495
1504
  configs.linter = "eslint";
1496
1505
  configs.eslintConfigPath = eslintPath;
1497
1506
  }
1498
- const prettierPath = join(monorepoRoot, ".prettierrc.json");
1507
+ const prettierPath = join$1(monorepoRoot, ".prettierrc.json");
1499
1508
  if (await fileExists(prettierPath)) {
1500
1509
  configs.formatter = "prettier";
1501
1510
  configs.prettierConfigPath = prettierPath;
1502
1511
  }
1503
- const biomePath = join(monorepoRoot, "biome.json");
1512
+ const biomePath = join$1(monorepoRoot, "biome.json");
1504
1513
  if (await fileExists(biomePath)) {
1505
1514
  configs.biomeConfigPath = biomePath;
1506
1515
  if (!configs.linter) configs.linter = "biome";
@@ -1510,8 +1519,8 @@ async function detectExistingConfigs(monorepoRoot) {
1510
1519
  }
1511
1520
  async function getMonorepoScope(monorepoRoot) {
1512
1521
  try {
1513
- const pkgPath = join(monorepoRoot, "package.json");
1514
- const content = await readFile(pkgPath, "utf-8");
1522
+ const pkgPath = join$1(monorepoRoot, "package.json");
1523
+ const content = await readFile$1(pkgPath, "utf-8");
1515
1524
  const pkgJson = JSON.parse(content);
1516
1525
  if (pkgJson.name) {
1517
1526
  return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
@@ -1521,7 +1530,7 @@ async function getMonorepoScope(monorepoRoot) {
1521
1530
  return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
1522
1531
  }
1523
1532
  async function getWorkspacePackages(monorepoRoot) {
1524
- const packagesDir = join(monorepoRoot, "packages");
1533
+ const packagesDir = join$1(monorepoRoot, "packages");
1525
1534
  try {
1526
1535
  const { readdir } = await import('fs/promises');
1527
1536
  const entries = await readdir(packagesDir, { withFileTypes: true });
@@ -1529,8 +1538,8 @@ async function getWorkspacePackages(monorepoRoot) {
1529
1538
  for (const entry of entries) {
1530
1539
  if (!entry.isDirectory()) continue;
1531
1540
  try {
1532
- const content = await readFile(
1533
- join(packagesDir, entry.name, "package.json"),
1541
+ const content = await readFile$1(
1542
+ join$1(packagesDir, entry.name, "package.json"),
1534
1543
  "utf-8"
1535
1544
  );
1536
1545
  const pkg2 = JSON.parse(content);
@@ -1544,25 +1553,23 @@ async function getWorkspacePackages(monorepoRoot) {
1544
1553
  }
1545
1554
  }
1546
1555
  async function ensureConfigInWorkspace(monorepoRoot) {
1547
- const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
1556
+ const workspacePath = join$1(monorepoRoot, "pnpm-workspace.yaml");
1548
1557
  let content;
1549
1558
  try {
1550
- content = await readFile(workspacePath, "utf-8");
1559
+ content = await readFile$1(workspacePath, "utf-8");
1551
1560
  } catch {
1552
1561
  content = `packages:
1553
1562
  - ".config/*"
1554
1563
  - "packages/*"
1555
1564
  `;
1556
- await writeFile(workspacePath, content);
1565
+ await writeFile$1(workspacePath, content);
1557
1566
  return;
1558
1567
  }
1559
1568
  if (content.includes(".config/*") || content.includes('".config/*"')) {
1560
1569
  return;
1561
1570
  }
1562
1571
  const lines = content.split("\n");
1563
- const packagesIndex = lines.findIndex(
1564
- (line) => line.trim().startsWith("packages:")
1565
- );
1572
+ const packagesIndex = lines.findIndex((line) => line.trim().startsWith("packages:"));
1566
1573
  if (packagesIndex === -1) {
1567
1574
  content = `packages:
1568
1575
  - ".config/*"
@@ -1571,14 +1578,14 @@ ${content}`;
1571
1578
  lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
1572
1579
  content = lines.join("\n");
1573
1580
  }
1574
- await writeFile(workspacePath, content);
1581
+ await writeFile$1(workspacePath, content);
1575
1582
  }
1576
1583
  async function migrateEslintConfig(monorepoRoot, files) {
1577
1584
  const configBasePath = ".config/eslint";
1578
- const existingConfigPath = join(monorepoRoot, "eslint.config.js");
1585
+ const existingConfigPath = join$1(monorepoRoot, "eslint.config.js");
1579
1586
  let existingContent;
1580
1587
  try {
1581
- existingContent = await readFile(existingConfigPath, "utf-8");
1588
+ existingContent = await readFile$1(existingConfigPath, "utf-8");
1582
1589
  } catch {
1583
1590
  generateEslintConfigPackage(files);
1584
1591
  return;
@@ -1654,10 +1661,10 @@ export default [
1654
1661
  }
1655
1662
  async function migratePrettierConfig(monorepoRoot, files) {
1656
1663
  const configBasePath = ".config/prettier";
1657
- const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
1664
+ const existingConfigPath = join$1(monorepoRoot, ".prettierrc.json");
1658
1665
  let existingContent;
1659
1666
  try {
1660
- existingContent = await readFile(existingConfigPath, "utf-8");
1667
+ existingContent = await readFile$1(existingConfigPath, "utf-8");
1661
1668
  } catch {
1662
1669
  generatePrettierConfigPackage(files);
1663
1670
  return;
@@ -1727,7 +1734,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
1727
1734
  const dirName = value.includes("/") ? value.split("/").pop() : value;
1728
1735
  if (!dirName) return "Package name is required";
1729
1736
  if (!hasCustomDirectories) {
1730
- const targetPath = join(monorepoRoot, defaultDir, dirName);
1737
+ const targetPath = join$1(monorepoRoot, defaultDir, dirName);
1731
1738
  try {
1732
1739
  const { statSync } = require$1("fs");
1733
1740
  statSync(targetPath);
@@ -1742,11 +1749,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
1742
1749
  }
1743
1750
  const scopedName = packageNameInput;
1744
1751
  const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
1745
- const packageOptions = await promptForPackageOptions(
1746
- scopedName,
1747
- packageType,
1748
- inheritedSettings
1749
- );
1752
+ const packageOptions = await promptForPackageOptions(scopedName, packageType, inheritedSettings);
1750
1753
  let targetDir = defaultDir;
1751
1754
  if (hasCustomDirectories && workspaceDirectories.length > 0) {
1752
1755
  const dirChoice = await p.select({
@@ -1761,7 +1764,7 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
1761
1764
  return false;
1762
1765
  }
1763
1766
  targetDir = dirChoice;
1764
- const targetPath = join(monorepoRoot, targetDir, shortName);
1767
+ const targetPath = join$1(monorepoRoot, targetDir, shortName);
1765
1768
  try {
1766
1769
  const { statSync } = require$1("fs");
1767
1770
  statSync(targetPath);
@@ -1770,81 +1773,13 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
1770
1773
  } catch {
1771
1774
  }
1772
1775
  }
1773
- const relativePkgPath = join(targetDir, shortName);
1776
+ const relativePkgPath = join$1(targetDir, shortName);
1774
1777
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
1775
1778
  packageOptions.workspaceRoot = workspaceRoot;
1776
1779
  packageOptions.name = scopedName;
1777
- if (packageManager === "pnpm") {
1778
- packageOptions.pnpmVersion = await getLatestPnpmVersion();
1779
- } else if (packageManager === "yarn") {
1780
- packageOptions.yarnVersion = await getLatestYarnVersion();
1781
- } else if (packageManager === "npm") {
1782
- packageOptions.npmVersion = await getLatestNpmCliVersion();
1783
- }
1784
- const nodeVersion = packageOptions.nodeVersion ?? "latest";
1785
- if (nodeVersion === "latest") {
1786
- packageOptions.nodeVersion = await getLatestNodeVersion();
1787
- }
1788
- const versions = {};
1789
- const versionPromises = [];
1790
- const pkgIsLibrary = packageOptions.projectType === "library";
1791
- const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
1792
- if (pkgTesting === "vitest") {
1793
- versionPromises.push(
1794
- getLatestNpmVersion("vitest", "4.0.0").then((v) => {
1795
- versions.vitest = v;
1796
- })
1797
- );
1798
- }
1799
- if (!pkgIsLibrary) {
1800
- versionPromises.push(
1801
- getLatestNpmVersion("vite", "6.3.4").then((v) => {
1802
- versions.vite = v;
1803
- })
1804
- );
1805
- }
1806
- const linter = packageOptions.linter ?? "oxlint";
1807
- if (linter === "eslint") {
1808
- versionPromises.push(
1809
- getLatestNpmVersion("eslint", "9.17.0").then((v) => {
1810
- versions.eslint = v;
1811
- })
1812
- );
1813
- } else if (linter === "oxlint") {
1814
- versionPromises.push(
1815
- getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
1816
- versions.oxlint = v;
1817
- })
1818
- );
1819
- } else if (linter === "biome") {
1820
- versionPromises.push(
1821
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1822
- versions.biome = v;
1823
- })
1824
- );
1825
- }
1826
- const formatter = packageOptions.formatter ?? "prettier";
1827
- if (formatter === "prettier") {
1828
- versionPromises.push(
1829
- getLatestNpmVersion("prettier", "3.4.2").then((v) => {
1830
- versions.prettier = v;
1831
- })
1832
- );
1833
- } else if (formatter === "oxfmt") {
1834
- versionPromises.push(
1835
- getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
1836
- versions.oxfmt = v;
1837
- })
1838
- );
1839
- } else if (formatter === "biome" && linter !== "biome") {
1840
- versionPromises.push(
1841
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
1842
- versions.biome = v;
1843
- })
1844
- );
1845
- }
1846
- await Promise.all(versionPromises);
1847
- packageOptions.versions = versions;
1780
+ packageOptions.packageManager = await resolvePackageManager(packageOptions);
1781
+ packageOptions.engine = await resolveEngine(packageOptions);
1782
+ packageOptions.versions = await resolveProjectPackageVersions(packageOptions);
1848
1783
  const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
1849
1784
  if (workspacePackages.length > 0) {
1850
1785
  const selectedDeps = await p.multiselect({
@@ -1856,15 +1791,13 @@ async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedS
1856
1791
  packageOptions.workspaceDependencies = selectedDeps;
1857
1792
  }
1858
1793
  }
1859
- const outputPath = join(monorepoRoot, relativePkgPath);
1794
+ const outputPath = join$1(monorepoRoot, relativePkgPath);
1860
1795
  const spinner = p.spinner();
1861
1796
  spinner.start("Creating package...");
1862
1797
  try {
1863
1798
  const files = generate(packageOptions);
1864
1799
  await writeGeneratedFiles(outputPath, files);
1865
- spinner.stop(
1866
- color.green.inverse(` \u2713 Package created at ${relativePkgPath}! `)
1867
- );
1800
+ spinner.stop(color.green.inverse(` \u2713 Package created at ${relativePkgPath}! `));
1868
1801
  const addAnother = await p.select({
1869
1802
  message: "Add another package?",
1870
1803
  options: [
@@ -2042,19 +1975,17 @@ async function handleFixCommand(options) {
2042
1975
  try {
2043
1976
  const files = {};
2044
1977
  const tsConfigExists = await fileExists(
2045
- join(monorepoRoot, ".config/typescript/package.json")
1978
+ join$1(monorepoRoot, ".config/typescript/package.json")
2046
1979
  );
2047
1980
  if (!tsConfigExists) {
2048
1981
  generateTypescriptConfigPackage(files);
2049
1982
  }
2050
1983
  if (linter === "oxlint") {
2051
- const oxlintExists = await fileExists(
2052
- join(monorepoRoot, ".config/oxlint/package.json")
2053
- );
1984
+ const oxlintExists = await fileExists(join$1(monorepoRoot, ".config/oxlint/package.json"));
2054
1985
  if (!oxlintExists) generateOxlintConfigPackage(files);
2055
1986
  } else if (linter === "eslint") {
2056
1987
  const eslintPkgExists = await fileExists(
2057
- join(monorepoRoot, ".config/eslint/package.json")
1988
+ join$1(monorepoRoot, ".config/eslint/package.json")
2058
1989
  );
2059
1990
  if (!eslintPkgExists) {
2060
1991
  if (existingConfigs.eslintConfigPath) {
@@ -2065,13 +1996,11 @@ async function handleFixCommand(options) {
2065
1996
  }
2066
1997
  }
2067
1998
  if (formatter === "oxfmt") {
2068
- const oxfmtExists = await fileExists(
2069
- join(monorepoRoot, ".config/oxfmt/package.json")
2070
- );
1999
+ const oxfmtExists = await fileExists(join$1(monorepoRoot, ".config/oxfmt/package.json"));
2071
2000
  if (!oxfmtExists) generateOxfmtConfigPackage(files);
2072
2001
  } else if (formatter === "prettier") {
2073
2002
  const prettierPkgExists = await fileExists(
2074
- join(monorepoRoot, ".config/prettier/package.json")
2003
+ join$1(monorepoRoot, ".config/prettier/package.json")
2075
2004
  );
2076
2005
  if (!prettierPkgExists) {
2077
2006
  if (existingConfigs.prettierConfigPath) {
@@ -2082,8 +2011,13 @@ async function handleFixCommand(options) {
2082
2011
  }
2083
2012
  }
2084
2013
  if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
2014
+ const versions = await resolveMonorepoRootPackageVersions({
2015
+ linter,
2016
+ formatter
2017
+ });
2018
+ const biomeVersion = getResolvedPackageVersion(versions, "@biomejs/biome");
2085
2019
  const biomeConfig = {
2086
- $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
2020
+ $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
2087
2021
  vcs: {
2088
2022
  enabled: true,
2089
2023
  clientKind: "git",
@@ -2105,9 +2039,9 @@ async function handleFixCommand(options) {
2105
2039
  };
2106
2040
  }
2107
2041
  for (const [filePath, file] of Object.entries(files)) {
2108
- const fullPath = join(monorepoRoot, filePath);
2109
- await mkdir(dirname(fullPath), { recursive: true });
2110
- await writeFile(fullPath, file.content);
2042
+ const fullPath = join$1(monorepoRoot, filePath);
2043
+ await mkdir$1(dirname$1(fullPath), { recursive: true });
2044
+ await writeFile$1(fullPath, file.content);
2111
2045
  }
2112
2046
  await ensureConfigInWorkspace(monorepoRoot);
2113
2047
  if (existingConfigs.eslintConfigPath && linter === "eslint") {
@@ -2123,18 +2057,14 @@ async function handleFixCommand(options) {
2123
2057
  }
2124
2058
  }
2125
2059
  spinner.stop(color.green("\u2713") + " Workspace fixed!");
2126
- const generated = Object.keys(files).filter(
2127
- (f) => f.endsWith("package.json")
2128
- );
2060
+ const generated = Object.keys(files).filter((f) => f.endsWith("package.json"));
2129
2061
  for (const pkgFile of generated) {
2130
2062
  const pkgName = pkgFile.replace("/package.json", "");
2131
2063
  console.log(color.dim(` Generated ${pkgName}`));
2132
2064
  }
2133
- const vscodeSettingsExists = await fileExists(
2134
- join(monorepoRoot, ".vscode/settings.json")
2135
- );
2065
+ const vscodeSettingsExists = await fileExists(join$1(monorepoRoot, ".vscode/settings.json"));
2136
2066
  const vscodeExtensionsExists = await fileExists(
2137
- join(monorepoRoot, ".vscode/extensions.json")
2067
+ join$1(monorepoRoot, ".vscode/extensions.json")
2138
2068
  );
2139
2069
  const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
2140
2070
  if (!vscodeExists) {
@@ -2152,17 +2082,15 @@ async function handleFixCommand(options) {
2152
2082
  const vscodeFiles = {};
2153
2083
  generateVscodeFiles(vscodeFiles, linter, formatter);
2154
2084
  for (const [filePath, file] of Object.entries(vscodeFiles)) {
2155
- const fullPath = join(monorepoRoot, filePath);
2156
- await mkdir(dirname(fullPath), { recursive: true });
2157
- await writeFile(fullPath, file.content);
2085
+ const fullPath = join$1(monorepoRoot, filePath);
2086
+ await mkdir$1(dirname$1(fullPath), { recursive: true });
2087
+ await writeFile$1(fullPath, file.content);
2158
2088
  }
2159
2089
  console.log(color.dim(" Generated .vscode/settings.json"));
2160
2090
  console.log(color.dim(" Generated .vscode/extensions.json"));
2161
2091
  }
2162
2092
  }
2163
- const aiRulesExist = await fileExists(
2164
- join(monorepoRoot, ".ai/workspace.md")
2165
- );
2093
+ const aiRulesExist = await fileExists(join$1(monorepoRoot, ".ai/workspace.md"));
2166
2094
  if (!aiRulesExist) {
2167
2095
  const platforms = await promptForAiPlatforms(isNonInteractive);
2168
2096
  if (platforms.length > 0) {
@@ -2177,9 +2105,9 @@ async function handleFixCommand(options) {
2177
2105
  platforms
2178
2106
  });
2179
2107
  for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2180
- const fullPath = join(monorepoRoot, filePath);
2181
- await mkdir(dirname(fullPath), { recursive: true });
2182
- await writeFile(fullPath, file.content);
2108
+ const fullPath = join$1(monorepoRoot, filePath);
2109
+ await mkdir$1(dirname$1(fullPath), { recursive: true });
2110
+ await writeFile$1(fullPath, file.content);
2183
2111
  console.log(color.dim(` Generated ${filePath}`));
2184
2112
  }
2185
2113
  }
@@ -2195,15 +2123,11 @@ async function handleMigration(config, target, root, options) {
2195
2123
  const plan = await getMigrationPlan(config, target, root);
2196
2124
  console.log(color.cyan("Migration:"));
2197
2125
  if (plan.fromLinter !== plan.toLinter) {
2198
- console.log(
2199
- ` Linter: ${color.dim(plan.fromLinter)} \u2192 ${color.green(plan.toLinter)}`
2200
- );
2126
+ console.log(` Linter: ${color.dim(plan.fromLinter)} \u2192 ${color.green(plan.toLinter)}`);
2201
2127
  }
2202
2128
  if (plan.fromFormatter !== plan.toFormatter) {
2203
2129
  console.log(
2204
- ` Formatter: ${color.dim(plan.fromFormatter)} \u2192 ${color.green(
2205
- plan.toFormatter
2206
- )}`
2130
+ ` Formatter: ${color.dim(plan.fromFormatter)} \u2192 ${color.green(plan.toFormatter)}`
2207
2131
  );
2208
2132
  }
2209
2133
  console.log();
@@ -2234,17 +2158,17 @@ async function handleMigration(config, target, root, options) {
2234
2158
  }
2235
2159
  }
2236
2160
  await applyMigration(plan, root);
2237
- const aiWorkspacePath = join(root, ".ai/workspace.md");
2161
+ const aiWorkspacePath = join$1(root, ".ai/workspace.md");
2238
2162
  const aiRulesExist = await fileExists(aiWorkspacePath);
2239
2163
  if (aiRulesExist) {
2240
2164
  console.log();
2241
2165
  console.log(color.cyan("Updating AI rules..."));
2242
2166
  const scope = await getMonorepoScope(root);
2243
2167
  const existingPlatforms = [];
2244
- if (await fileExists(join(root, "AGENTS.md"))) {
2168
+ if (await fileExists(join$1(root, "AGENTS.md"))) {
2245
2169
  existingPlatforms.push("agents");
2246
2170
  }
2247
- if (await fileExists(join(root, "CLAUDE.md"))) {
2171
+ if (await fileExists(join$1(root, "CLAUDE.md"))) {
2248
2172
  existingPlatforms.push("claude");
2249
2173
  }
2250
2174
  const aiFilesOutput = {};
@@ -2257,94 +2181,94 @@ async function handleMigration(config, target, root, options) {
2257
2181
  platforms: existingPlatforms.length > 0 ? existingPlatforms : ["agents"]
2258
2182
  });
2259
2183
  for (const [filePath, file] of Object.entries(aiFilesOutput)) {
2260
- const fullPath = join(root, filePath);
2261
- await mkdir(dirname(fullPath), { recursive: true });
2262
- await writeFile(fullPath, file.content);
2184
+ const fullPath = join$1(root, filePath);
2185
+ await mkdir$1(dirname$1(fullPath), { recursive: true });
2186
+ await writeFile$1(fullPath, file.content);
2263
2187
  console.log(color.dim(` ${filePath}`));
2264
2188
  }
2265
2189
  }
2266
2190
  console.log();
2267
- console.log(
2268
- color.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`
2269
- );
2191
+ console.log(color.green("\u2713") + ` Migrated to ${plan.toLinter}/${plan.toFormatter}`);
2270
2192
  console.log(color.dim(" Run `pnpm install` to update dependencies"));
2271
2193
  process.exit(0);
2272
2194
  }
2273
2195
  async function handleUpdateCommand(options) {
2274
2196
  const monorepoRoot = await detectMonorepoRoot();
2275
- if (!monorepoRoot) {
2276
- console.log(color.red("\u2717") + " Not a monorepo workspace");
2277
- console.log(color.dim(" --update only supports pnpm monorepos"));
2197
+ const projectRoot = monorepoRoot ?? await detectPackageRoot();
2198
+ if (!projectRoot) {
2199
+ console.log(color.red("\u2717") + " Could not find a project root");
2200
+ console.log(color.dim(" Run this command from inside a generated project"));
2278
2201
  process.exit(1);
2279
2202
  }
2280
- const { valid, errors } = await validateWorkspace(monorepoRoot);
2281
- if (!valid) {
2282
- console.log(color.yellow("!") + " Workspace has issues:");
2283
- for (const error of errors) {
2284
- console.log(color.dim(` \u2022 ${error}`));
2203
+ const isMonorepo = monorepoRoot != null;
2204
+ if (isMonorepo) {
2205
+ const { valid, errors } = await validateWorkspace(projectRoot);
2206
+ if (!valid) {
2207
+ console.log(color.yellow("!") + " Workspace has issues:");
2208
+ for (const error of errors) {
2209
+ console.log(color.dim(` \u2022 ${error}`));
2210
+ }
2211
+ console.log();
2212
+ const shouldFix = options.yes || await p.confirm({
2213
+ message: "Run fix first to resolve these issues?",
2214
+ initialValue: true
2215
+ });
2216
+ if (p.isCancel(shouldFix) || !shouldFix) {
2217
+ console.log(color.dim(" Run `pnpm create krispya --fix` to fix manually"));
2218
+ process.exit(1);
2219
+ }
2220
+ const preFixConfig = await detectCurrentConfig(projectRoot);
2221
+ const fixOptions = {
2222
+ ...options,
2223
+ linter: options.linter ?? preFixConfig.linter,
2224
+ formatter: options.formatter ?? preFixConfig.formatter
2225
+ };
2226
+ await handleFixCommand(fixOptions);
2285
2227
  }
2228
+ } else if (options.linter || options.formatter) {
2229
+ console.log(
2230
+ color.yellow("!") + " Linter/formatter migrations in --update are currently monorepo-only"
2231
+ );
2232
+ console.log(color.dim(" Continuing with standalone shared config updates"));
2286
2233
  console.log();
2287
- const shouldFix = options.yes || await p.confirm({
2288
- message: "Run fix first to resolve these issues?",
2289
- initialValue: true
2290
- });
2291
- if (p.isCancel(shouldFix) || !shouldFix) {
2292
- console.log(
2293
- color.dim(" Run `pnpm create krispya --fix` to fix manually")
2294
- );
2295
- process.exit(1);
2296
- }
2297
- const preFixConfig = await detectCurrentConfig(monorepoRoot);
2298
- const fixOptions = {
2299
- ...options,
2300
- linter: options.linter ?? preFixConfig.linter,
2301
- formatter: options.formatter ?? preFixConfig.formatter
2302
- };
2303
- await handleFixCommand(fixOptions);
2304
2234
  }
2305
- const config = await detectCurrentConfig(monorepoRoot);
2235
+ const config = await detectCurrentConfig(projectRoot, isMonorepo);
2306
2236
  const targetLinter = options.linter;
2307
2237
  const targetFormatter = options.formatter;
2308
2238
  const migrationTarget = { linter: targetLinter, formatter: targetFormatter };
2309
- if (needsMigration(config, migrationTarget)) {
2310
- await handleMigration(config, migrationTarget, monorepoRoot, options);
2239
+ if (isMonorepo && needsMigration(config, migrationTarget)) {
2240
+ await handleMigration(config, migrationTarget, projectRoot, options);
2311
2241
  return;
2312
2242
  }
2313
2243
  console.log(
2314
2244
  color.cyan("Checking for updates...") + color.dim(` (${config.linter}/${config.formatter})`)
2315
2245
  );
2316
2246
  console.log();
2317
- const expected = generateExpectedFiles(config);
2318
- const categories = await compareWithDisk(expected, monorepoRoot);
2319
- const workspaceConfigChanges = await getWorkspaceConfigUpdates(monorepoRoot);
2320
- const workspaceCategory = {
2321
- category: "workspace-config",
2322
- label: "Workspace Config",
2323
- changes: workspaceConfigChanges,
2324
- hasUserModifications: workspaceConfigChanges.some(
2325
- (c) => c.status === "modified"
2326
- )
2327
- };
2328
- const allCategories = categories.filter(
2329
- (c) => c.category !== "workspace-config"
2330
- );
2331
- if (workspaceConfigChanges.length > 0) {
2332
- const configPkgIndex = allCategories.findIndex(
2333
- (c) => c.category === "config-packages"
2334
- );
2335
- if (configPkgIndex !== -1) {
2336
- allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2337
- } else {
2338
- allCategories.push(workspaceCategory);
2247
+ const expected = await generateExpectedFiles(config);
2248
+ const categories = await compareWithDisk(expected, projectRoot);
2249
+ const allCategories = categories.filter((c) => c.category !== "workspace-config");
2250
+ if (isMonorepo) {
2251
+ const workspaceConfigChanges = await getWorkspaceConfigUpdates(projectRoot);
2252
+ const workspaceCategory = {
2253
+ category: "workspace-config",
2254
+ label: "Workspace Config",
2255
+ changes: workspaceConfigChanges,
2256
+ hasUserModifications: workspaceConfigChanges.some((c) => c.status === "modified")
2257
+ };
2258
+ if (workspaceConfigChanges.length > 0) {
2259
+ const configPkgIndex = allCategories.findIndex((c) => c.category === "config-packages");
2260
+ if (configPkgIndex !== -1) {
2261
+ allCategories.splice(configPkgIndex + 1, 0, workspaceCategory);
2262
+ } else {
2263
+ allCategories.push(workspaceCategory);
2264
+ }
2339
2265
  }
2340
2266
  }
2341
2267
  let updatedCount = 0;
2342
2268
  let skippedCount = 0;
2343
2269
  for (const category of allCategories) {
2344
2270
  const newChanges = category.changes.filter((c) => c.status === "added");
2345
- const modifiedChanges = category.changes.filter(
2346
- (c) => c.status === "modified"
2347
- );
2271
+ const modifiedChanges = category.changes.filter((c) => c.status === "modified");
2348
2272
  const hasNew = newChanges.length > 0;
2349
2273
  const hasModified = modifiedChanges.length > 0;
2350
2274
  const hasChanges = hasNew || hasModified;
@@ -2355,19 +2279,15 @@ async function handleUpdateCommand(options) {
2355
2279
  if (category.category === "ai-files") {
2356
2280
  if (hasNew) {
2357
2281
  console.log(color.cyan(category.label + ":"));
2358
- console.log(
2359
- color.dim(` ${newChanges.length} AI file(s) can be added`)
2360
- );
2282
+ console.log(color.dim(` ${newChanges.length} AI file(s) can be added`));
2361
2283
  console.log();
2362
2284
  const applyAi = options.yes ? true : await p.confirm({
2363
2285
  message: "Add AI rules?",
2364
2286
  initialValue: true
2365
2287
  });
2366
2288
  if (!p.isCancel(applyAi) && applyAi) {
2367
- await applyUpdates(newChanges, monorepoRoot);
2368
- console.log(
2369
- color.green("\u2713") + ` Added ${newChanges.length} AI file(s)`
2370
- );
2289
+ await applyUpdates(newChanges, projectRoot);
2290
+ console.log(color.green("\u2713") + ` Added ${newChanges.length} AI file(s)`);
2371
2291
  updatedCount++;
2372
2292
  } else {
2373
2293
  console.log(color.dim(` Skipped ${category.label}`));
@@ -2388,7 +2308,7 @@ async function handleUpdateCommand(options) {
2388
2308
  initialValue: false
2389
2309
  });
2390
2310
  if (!p.isCancel(updateExisting) && updateExisting) {
2391
- await applyUpdates(modifiedChanges, monorepoRoot);
2311
+ await applyUpdates(modifiedChanges, projectRoot);
2392
2312
  console.log(color.green("\u2713") + " Updated existing AI files");
2393
2313
  }
2394
2314
  }
@@ -2431,9 +2351,7 @@ async function handleUpdateCommand(options) {
2431
2351
  process.exit(0);
2432
2352
  }
2433
2353
  if (selectedFiles.length > 0) {
2434
- changesToApply = allChanges.filter(
2435
- (c) => selectedFiles.includes(c.path)
2436
- );
2354
+ changesToApply = allChanges.filter((c) => selectedFiles.includes(c.path));
2437
2355
  }
2438
2356
  } else if (hasNew) {
2439
2357
  console.log(color.cyan(category.label + ":"));
@@ -2471,13 +2389,9 @@ async function handleUpdateCommand(options) {
2471
2389
  }
2472
2390
  }
2473
2391
  if (changesToApply.length > 0) {
2474
- await applyUpdates(changesToApply, monorepoRoot);
2475
- const addedCount = changesToApply.filter(
2476
- (c) => c.status === "added"
2477
- ).length;
2478
- const updatedFilesCount = changesToApply.filter(
2479
- (c) => c.status === "modified"
2480
- ).length;
2392
+ await applyUpdates(changesToApply, projectRoot);
2393
+ const addedCount = changesToApply.filter((c) => c.status === "added").length;
2394
+ const updatedFilesCount = changesToApply.filter((c) => c.status === "modified").length;
2481
2395
  const parts = [];
2482
2396
  if (addedCount > 0) parts.push(`added ${addedCount}`);
2483
2397
  if (updatedFilesCount > 0) parts.push(`updated ${updatedFilesCount}`);
@@ -2504,20 +2418,12 @@ async function handleUpdateCommand(options) {
2504
2418
  async function handleWorkspaceCommand(name, options) {
2505
2419
  const monorepoRoot = await detectMonorepoRoot();
2506
2420
  if (!monorepoRoot) {
2507
- console.error(
2508
- color.red("Error:") + " --workspace flag requires being inside a monorepo"
2509
- );
2421
+ console.error(color.red("Error:") + " --workspace flag requires being inside a monorepo");
2510
2422
  process.exit(1);
2511
2423
  }
2512
2424
  if (!name) {
2513
- console.error(
2514
- color.red("Error:") + " Package name is required with --workspace flag"
2515
- );
2516
- console.log(
2517
- color.dim(
2518
- " Example: pnpm create krispya my-lib --workspace --type library"
2519
- )
2520
- );
2425
+ console.error(color.red("Error:") + " Package name is required with --workspace flag");
2426
+ console.log(color.dim(" Example: pnpm create krispya my-lib --workspace --type library"));
2521
2427
  process.exit(1);
2522
2428
  }
2523
2429
  const scope = await getMonorepoScope(monorepoRoot);
@@ -2528,32 +2434,23 @@ async function handleWorkspaceCommand(name, options) {
2528
2434
  const template = options.template ?? "vanilla";
2529
2435
  const baseTemplate = getBaseTemplate(template);
2530
2436
  const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
2531
- const fullPackagePath = join(monorepoRoot, targetDir, name);
2437
+ const fullPackagePath = join$1(monorepoRoot, targetDir, name);
2532
2438
  try {
2533
- await access(fullPackagePath, constants$1.F_OK);
2534
- console.error(
2535
- color.red("Error:") + ` Directory ${targetDir}/${name} already exists`
2536
- );
2439
+ await access$1(fullPackagePath, constants$2.F_OK);
2440
+ console.error(color.red("Error:") + ` Directory ${targetDir}/${name} already exists`);
2537
2441
  process.exit(1);
2538
2442
  } catch {
2539
2443
  }
2540
- const versions = {};
2541
- const versionPromises = [];
2542
- const isLibrary = projectType === "library";
2543
- if (!isLibrary) {
2544
- versionPromises.push(
2545
- getLatestNpmVersion("vite", "6.3.4").then((v) => {
2546
- versions.vite = v;
2547
- })
2548
- );
2549
- }
2550
2444
  const linter = inheritedSettings.linter ?? options.linter ?? "oxlint";
2551
2445
  const formatter = inheritedSettings.formatter ?? options.formatter ?? "prettier";
2552
- const packageManager = inheritedSettings.packageManager ?? "pnpm";
2553
- const nodeVersion = inheritedSettings.nodeVersion ?? "latest";
2446
+ const packageManager = inheritedSettings.packageManager?.name ?? "pnpm";
2447
+ const engine = inheritedSettings.engine ?? {
2448
+ name: "node",
2449
+ version: "latest"
2450
+ };
2554
2451
  const pnpmManageVersions = inheritedSettings.pnpmManageVersions ?? true;
2555
- await Promise.all(versionPromises);
2556
- const relativePkgPath = join(targetDir, name);
2452
+ const isLibrary = projectType === "library";
2453
+ const relativePkgPath = join$1(targetDir, name);
2557
2454
  const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
2558
2455
  const generateOptions = {
2559
2456
  name: scopedName,
@@ -2562,11 +2459,10 @@ async function handleWorkspaceCommand(name, options) {
2562
2459
  template,
2563
2460
  linter,
2564
2461
  formatter,
2565
- packageManager,
2566
- nodeVersion,
2462
+ packageManager: { name: packageManager },
2463
+ engine,
2567
2464
  pnpmManageVersions,
2568
2465
  workspaceRoot,
2569
- versions,
2570
2466
  ...baseTemplate === "r3f" && {
2571
2467
  drei: options.drei ? {} : void 0,
2572
2468
  handle: options.handle ? {} : void 0,
@@ -2582,15 +2478,14 @@ async function handleWorkspaceCommand(name, options) {
2582
2478
  triplex: options.triplex ? {} : void 0
2583
2479
  }
2584
2480
  };
2585
- console.log(
2586
- color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`
2587
- );
2481
+ generateOptions.packageManager = await resolvePackageManager(generateOptions);
2482
+ generateOptions.engine = await resolveEngine(generateOptions);
2483
+ generateOptions.versions = await resolveProjectPackageVersions(generateOptions);
2484
+ console.log(color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`);
2588
2485
  try {
2589
2486
  const files = generate(generateOptions);
2590
2487
  await writeGeneratedFiles(fullPackagePath, files);
2591
- console.log(
2592
- color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`
2593
- );
2488
+ console.log(color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`);
2594
2489
  process.exit(0);
2595
2490
  } catch (error) {
2596
2491
  console.error(color.red("Error:") + " Failed to create package");
@@ -2599,21 +2494,17 @@ async function handleWorkspaceCommand(name, options) {
2599
2494
  }
2600
2495
  }
2601
2496
  async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2602
- const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.w; });
2603
- const packageManager = generateOptions.packageManager || "pnpm";
2604
- if (packageManager === "pnpm") {
2605
- generateOptions.pnpmVersion = await getLatestPnpmVersion();
2606
- } else if (packageManager === "yarn") {
2607
- generateOptions.yarnVersion = await getLatestYarnVersion();
2608
- } else if (packageManager === "npm") {
2609
- generateOptions.npmVersion = await getLatestNpmCliVersion();
2610
- }
2611
- const nodeVersion = generateOptions.nodeVersion ?? "latest";
2612
- if (nodeVersion === "latest") {
2613
- generateOptions.nodeVersion = await getLatestNodeVersion();
2614
- }
2497
+ const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.I; });
2498
+ const packageManager = getPackageManagerName(generateOptions.packageManager);
2499
+ generateOptions.packageManager = await resolvePackageManager(generateOptions);
2500
+ generateOptions.engine = await resolveEngine(generateOptions);
2501
+ generateOptions.versions = await resolveMonorepoRootPackageVersions({
2502
+ linter: generateOptions.linter ?? "oxlint",
2503
+ formatter: generateOptions.formatter ?? "prettier",
2504
+ versions: generateOptions.versions
2505
+ });
2615
2506
  const aiPlatforms = await promptForAiPlatforms(isNonInteractive);
2616
- const projectPath = join(cwd(), generateOptions.name);
2507
+ const projectPath = join$1(cwd(), generateOptions.name);
2617
2508
  const spinner = p.spinner();
2618
2509
  spinner.start("Creating monorepo workspace...");
2619
2510
  try {
@@ -2621,19 +2512,21 @@ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2621
2512
  name: generateOptions.name,
2622
2513
  linter: generateOptions.linter ?? "oxlint",
2623
2514
  formatter: generateOptions.formatter ?? "prettier",
2624
- packageManager,
2625
- pnpmVersion: generateOptions.pnpmVersion,
2515
+ packageManager: generateOptions.packageManager ?? {
2516
+ name: packageManager
2517
+ },
2626
2518
  pnpmManageVersions: generateOptions.pnpmManageVersions,
2627
- nodeVersion: generateOptions.nodeVersion,
2519
+ engine: generateOptions.engine,
2520
+ versions: generateOptions.versions,
2628
2521
  aiPlatforms: aiPlatforms.length > 0 ? aiPlatforms : void 0
2629
2522
  });
2630
2523
  const filePaths = Object.keys(files).sort();
2631
2524
  for (const filePath of filePaths) {
2632
- const fullFilePath = join(projectPath, filePath);
2633
- await mkdir(dirname(fullFilePath), { recursive: true });
2525
+ const fullFilePath = join$1(projectPath, filePath);
2526
+ await mkdir$1(dirname$1(fullFilePath), { recursive: true });
2634
2527
  const file = files[filePath];
2635
2528
  if (file.type === "text") {
2636
- await writeFile(fullFilePath, file.content);
2529
+ await writeFile$1(fullFilePath, file.content);
2637
2530
  }
2638
2531
  }
2639
2532
  spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
@@ -2643,8 +2536,10 @@ async function handleMonorepoCreation(generateOptions, isNonInteractive) {
2643
2536
  const newWorkspaceSettings = {
2644
2537
  linter: generateOptions.linter,
2645
2538
  formatter: generateOptions.formatter,
2646
- packageManager,
2647
- nodeVersion: generateOptions.nodeVersion,
2539
+ packageManager: generateOptions.packageManager ?? {
2540
+ name: packageManager
2541
+ },
2542
+ engine: generateOptions.engine,
2648
2543
  pnpmManageVersions: generateOptions.pnpmManageVersions
2649
2544
  };
2650
2545
  const scope = generateOptions.name;
@@ -2680,88 +2575,19 @@ async function handleStandaloneProjectCreation(generateOptions, isNonInteractive
2680
2575
  if (aiPlatforms.length > 0) {
2681
2576
  generateOptions.aiPlatforms = aiPlatforms;
2682
2577
  }
2683
- const packageManager = generateOptions.packageManager || "pnpm";
2684
- if (packageManager === "pnpm") {
2685
- generateOptions.pnpmVersion = await getLatestPnpmVersion();
2686
- } else if (packageManager === "yarn") {
2687
- generateOptions.yarnVersion = await getLatestYarnVersion();
2688
- } else if (packageManager === "npm") {
2689
- generateOptions.npmVersion = await getLatestNpmCliVersion();
2690
- }
2691
- const nodeVersion = generateOptions.nodeVersion ?? "latest";
2692
- if (nodeVersion === "latest") {
2693
- generateOptions.nodeVersion = await getLatestNodeVersion();
2694
- }
2695
- const versions = {};
2696
- const versionPromises = [];
2578
+ const packageManager = getPackageManagerName(generateOptions.packageManager);
2697
2579
  const isLibrary = generateOptions.projectType === "library";
2698
- const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
2699
- if (testing === "vitest") {
2700
- versionPromises.push(
2701
- getLatestNpmVersion("vitest", "4.0.0").then((v) => {
2702
- versions.vitest = v;
2703
- })
2704
- );
2705
- }
2706
- if (!isLibrary) {
2707
- versionPromises.push(
2708
- getLatestNpmVersion("vite", "6.3.4").then((v) => {
2709
- versions.vite = v;
2710
- })
2711
- );
2712
- }
2713
- const linter = generateOptions.linter ?? "oxlint";
2714
- if (linter === "eslint") {
2715
- versionPromises.push(
2716
- getLatestNpmVersion("eslint", "9.17.0").then((v) => {
2717
- versions.eslint = v;
2718
- })
2719
- );
2720
- } else if (linter === "oxlint") {
2721
- versionPromises.push(
2722
- getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
2723
- versions.oxlint = v;
2724
- })
2725
- );
2726
- } else if (linter === "biome") {
2727
- versionPromises.push(
2728
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
2729
- versions.biome = v;
2730
- })
2731
- );
2732
- }
2733
- const formatter = generateOptions.formatter ?? "prettier";
2734
- if (formatter === "prettier") {
2735
- versionPromises.push(
2736
- getLatestNpmVersion("prettier", "3.4.2").then((v) => {
2737
- versions.prettier = v;
2738
- })
2739
- );
2740
- } else if (formatter === "oxfmt") {
2741
- versionPromises.push(
2742
- getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
2743
- versions.oxfmt = v;
2744
- })
2745
- );
2746
- } else if (formatter === "biome" && linter !== "biome") {
2747
- versionPromises.push(
2748
- getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
2749
- versions.biome = v;
2750
- })
2751
- );
2752
- }
2753
- await Promise.all(versionPromises);
2754
- generateOptions.versions = versions;
2755
- const projectPath = join(cwd(), generateOptions.name);
2580
+ generateOptions.packageManager = await resolvePackageManager(generateOptions);
2581
+ generateOptions.engine = await resolveEngine(generateOptions);
2582
+ generateOptions.versions = await resolveProjectPackageVersions(generateOptions);
2583
+ const projectPath = join$1(cwd(), generateOptions.name);
2756
2584
  const spinner = p.spinner();
2757
2585
  spinner.start("Creating project...");
2758
2586
  try {
2759
2587
  const files = generate(generateOptions);
2760
2588
  await writeGeneratedFiles(projectPath, files);
2761
2589
  spinner.stop(color.green.inverse(" \u2713 Project created! "));
2762
- if (isNonInteractive) {
2763
- process.exit(0);
2764
- }
2590
+ if (isNonInteractive) process.exit(0);
2765
2591
  const nextSteps = isLibrary ? [
2766
2592
  `cd ${generateOptions.name}`,
2767
2593
  `${packageManager} install`,
@@ -2800,7 +2626,7 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
2800
2626
  const settingsInfo = [
2801
2627
  inheritedSettings.linter && `linter: ${inheritedSettings.linter}`,
2802
2628
  inheritedSettings.formatter && `formatter: ${inheritedSettings.formatter}`,
2803
- inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager}`
2629
+ inheritedSettings.packageManager && `pm: ${inheritedSettings.packageManager.name}`
2804
2630
  ].filter(Boolean).join(", ");
2805
2631
  p.log.info(`Using workspace settings (${settingsInfo})`);
2806
2632
  }
@@ -2809,39 +2635,25 @@ async function handleInteractiveMonorepoMode(monorepoRoot) {
2809
2635
  while (addMore) {
2810
2636
  addMore = await createPackageInWorkspace(
2811
2637
  monorepoRoot,
2812
- inheritedSettings.packageManager ?? "pnpm",
2638
+ inheritedSettings.packageManager?.name ?? "pnpm",
2813
2639
  inheritedSettings,
2814
2640
  scope
2815
2641
  );
2816
2642
  }
2817
- p.note(
2818
- [`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"),
2819
- "Next steps"
2820
- );
2643
+ p.note([`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"), "Next steps");
2821
2644
  await promptAndOpenEditor(monorepoRoot);
2822
2645
  p.outro(color.green("Happy coding! \u2728"));
2823
2646
  process.exit(0);
2824
2647
  }
2825
2648
  }
2826
2649
  async function main() {
2827
- const program = new Command().name("create-krispya").description(
2828
- "CLI for creating Vanilla, React, and React Three Fiber projects"
2829
- ).argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
2650
+ const program = new Command().name("create-krispya").description("CLI for creating Vanilla, React, and React Three Fiber projects").argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
2830
2651
  "--bundler <bundler>",
2831
2652
  "library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
2832
2653
  ).option(
2833
2654
  "--template <type>",
2834
2655
  "project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
2835
- ).option(
2836
- "--linter <type>",
2837
- "linter: eslint, oxlint, or biome (default: oxlint)"
2838
- ).option(
2839
- "--formatter <type>",
2840
- "formatter: prettier, oxfmt, or biome (default: prettier)"
2841
- ).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(
2842
- "--package-manager <manager>",
2843
- "specify package manager (e.g. npm, yarn, pnpm)"
2844
- ).option(
2656
+ ).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(
2845
2657
  "--pnpm-manage-versions",
2846
2658
  "enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
2847
2659
  ).option(
@@ -2850,16 +2662,7 @@ async function main() {
2850
2662
  ).option(
2851
2663
  "--node-version <version>",
2852
2664
  'set Node.js version for engines.node field (default: "latest")'
2853
- ).option(
2854
- "--workspace",
2855
- "Add package to current monorepo workspace (non-interactive)"
2856
- ).option(
2857
- "--dir <directory>",
2858
- "Target directory for --workspace (default: apps/ or packages/)"
2859
- ).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
2860
- "--check",
2861
- "Check if current directory is in a valid monorepo workspace"
2862
- ).option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
2665
+ ).option("--workspace", "Add package to current monorepo workspace (non-interactive)").option("--dir <directory>", "Target directory for --workspace (default: apps/ or packages/)").option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option("--check", "Check if current directory is in a valid monorepo workspace").option("--fix", "Fix monorepo by generating missing .config packages").option("--update", "Update monorepo workspace to latest configuration").option("-y, --yes", "Non-interactive mode - accept all prompts").option(
2863
2666
  "--path <directory>",
2864
2667
  "Run in specified directory instead of current working directory"
2865
2668
  ).action(async (name, options) => {
@@ -2921,9 +2724,7 @@ async function main() {
2921
2724
  if (options.dir && !options.workspace) {
2922
2725
  console.error(color.red("Error:") + " --dir requires --workspace flag");
2923
2726
  console.log(
2924
- color.dim(
2925
- " Example: pnpm create krispya my-lib --workspace --dir examples"
2926
- )
2727
+ color.dim(" Example: pnpm create krispya my-lib --workspace --dir examples")
2927
2728
  );
2928
2729
  process.exit(1);
2929
2730
  }
@@ -2963,9 +2764,9 @@ async function main() {
2963
2764
  viverse: options.viverse ? {} : void 0,
2964
2765
  triplex: options.triplex ? {} : void 0
2965
2766
  },
2966
- packageManager: options.packageManager,
2767
+ packageManager: options.packageManager ? { name: options.packageManager } : void 0,
2967
2768
  pnpmManageVersions: options.pnpmManageVersions,
2968
- nodeVersion: options.nodeVersion ?? "latest"
2769
+ engine: { name: "node", version: options.nodeVersion ?? "latest" }
2969
2770
  };
2970
2771
  } else {
2971
2772
  const presets = hasConfigOptions(options) ? {
@@ -2975,7 +2776,7 @@ async function main() {
2975
2776
  linter: options.linter,
2976
2777
  formatter: options.formatter,
2977
2778
  packageManager: options.packageManager,
2978
- nodeVersion: options.nodeVersion,
2779
+ engine: options.nodeVersion ? { name: "node", version: options.nodeVersion } : void 0,
2979
2780
  pnpmManageVersions: options.pnpmManageVersions,
2980
2781
  drei: options.drei,
2981
2782
  handle: options.handle,