vite-plus 0.1.15 → 0.1.16-alpha.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.
@@ -3190,15 +3190,16 @@ function detectConfigs(projectPath) {
3190
3190
  configs.prettierConfig = config;
3191
3191
  break;
3192
3192
  }
3193
- if (!configs.prettierConfig) {
3194
- const packageJsonPath = path.join(projectPath, "package.json");
3195
- if (fs.existsSync(packageJsonPath)) try {
3196
- const content = fs.readFileSync(packageJsonPath, "utf8");
3197
- if (JSON.parse(content).prettier) configs.prettierConfig = PRETTIER_PACKAGE_JSON_CONFIG;
3198
- } catch {}
3199
- }
3200
3193
  if (fs.existsSync(path.join(projectPath, ".prettierignore"))) configs.prettierIgnore = true;
3201
3194
  if (fs.existsSync(path.join(projectPath, ".nvmrc"))) configs.nvmrcFile = true;
3195
+ const packageJsonPath = path.join(projectPath, "package.json");
3196
+ if (fs.existsSync(packageJsonPath)) try {
3197
+ const content = fs.readFileSync(packageJsonPath, "utf8");
3198
+ const pkg = JSON.parse(content);
3199
+ if (!configs.prettierConfig && pkg.prettier) configs.prettierConfig = PRETTIER_PACKAGE_JSON_CONFIG;
3200
+ const voltaNode = pkg.volta?.node;
3201
+ if (typeof voltaNode === "string") configs.voltaNode = voltaNode;
3202
+ } catch {}
3202
3203
  return configs;
3203
3204
  }
3204
3205
  //#endregion
@@ -3815,11 +3816,13 @@ function rewriteBunCatalog(projectPath) {
3815
3816
  const packageJsonPath = path.join(projectPath, "package.json");
3816
3817
  if (!fs.existsSync(packageJsonPath)) return;
3817
3818
  editJsonFile(packageJsonPath, (pkg) => {
3818
- const catalog = { ...pkg.catalog };
3819
+ const workspacesObj = pkg.workspaces && !Array.isArray(pkg.workspaces) ? pkg.workspaces : void 0;
3820
+ const catalog = { ...workspacesObj?.catalog ?? pkg.catalog };
3819
3821
  for (const [key, value] of Object.entries(VITE_PLUS_OVERRIDE_PACKAGES)) if (!value.startsWith("file:")) catalog[key] = value;
3820
3822
  if (!VITE_PLUS_VERSION.startsWith("file:")) catalog[VITE_PLUS_NAME] = VITE_PLUS_VERSION;
3821
3823
  for (const name of REMOVE_PACKAGES) delete catalog[name];
3822
- pkg.catalog = catalog;
3824
+ if (workspacesObj?.catalog != null) workspacesObj.catalog = catalog;
3825
+ else pkg.catalog = catalog;
3823
3826
  const overrides = { ...pkg.overrides };
3824
3827
  for (const key of Object.keys(VITE_PLUS_OVERRIDE_PACKAGES)) overrides[key] = "catalog:";
3825
3828
  pkg.overrides = overrides;
@@ -3937,6 +3940,14 @@ function rewritePackageJson(pkg, packageManager, isMonorepo, skipStagedMigration
3937
3940
  ...pkg.devDependencies,
3938
3941
  [VITE_PLUS_NAME]: version
3939
3942
  };
3943
+ const allDeps = {
3944
+ ...pkg.dependencies,
3945
+ ...pkg.devDependencies
3946
+ };
3947
+ if (!allDeps.vitest && Object.keys(allDeps).some((name) => name.includes("vitest"))) {
3948
+ const ver = VITE_PLUS_OVERRIDE_PACKAGES.vitest;
3949
+ pkg.devDependencies.vitest = supportCatalog && !ver.startsWith("file:") ? "catalog:" : ver;
3950
+ }
3940
3951
  }
3941
3952
  return extractedStagedConfig;
3942
3953
  }
@@ -4447,11 +4458,21 @@ function setPackageManager(projectDir, downloadPackageManager) {
4447
4458
  }
4448
4459
  /**
4449
4460
  * Detect a .nvmrc file in the project directory.
4461
+ * If not found, check for a Volta node version in package.json.
4462
+ * If either is found, return the relevant info for migration.
4450
4463
  * Returns undefined if not found or .node-version already exists.
4451
4464
  */
4452
4465
  function detectNodeVersionManagerFile(projectPath) {
4453
4466
  if (fs.existsSync(path.join(projectPath, ".node-version"))) return;
4454
- if (detectConfigs(projectPath).nvmrcFile) return { file: ".nvmrc" };
4467
+ const configs = detectConfigs(projectPath);
4468
+ if (configs.nvmrcFile) return configs.voltaNode ? {
4469
+ file: ".nvmrc",
4470
+ voltaPresent: true
4471
+ } : { file: ".nvmrc" };
4472
+ if (configs.voltaNode) return {
4473
+ file: "package.json",
4474
+ voltaNodeVersion: configs.voltaNode
4475
+ };
4455
4476
  }
4456
4477
  /**
4457
4478
  * Parse a version alias from a .nvmrc file into a .node-version compatible string.
@@ -4469,12 +4490,28 @@ function parseNvmrcVersion(alias) {
4469
4490
  return normalized;
4470
4491
  }
4471
4492
  /**
4472
- * Migrate .nvmrc to .node-version and remove .nvmrc.
4493
+ * Migrate .nvmrc or Volta node version from package.json to .node-version.
4494
+ * - For .nvmrc: the source file is removed after migration.
4495
+ * - For package.json (Volta): the volta field is left as-is; removal is left to the user's discretion.
4473
4496
  * Returns true on success, false if migration was skipped or failed.
4474
4497
  */
4475
- function migrateNodeVersionManagerFile(projectPath, _detection, report) {
4476
- const sourcePath = path.join(projectPath, ".nvmrc");
4498
+ function migrateNodeVersionManagerFile(projectPath, detection, report) {
4477
4499
  const nodeVersionPath = path.join(projectPath, ".node-version");
4500
+ if (detection.file === "package.json") {
4501
+ const { voltaNodeVersion } = detection;
4502
+ const resolvedVersion = voltaNodeVersion === "lts" ? "lts/*" : voltaNodeVersion;
4503
+ if (!import_semver.default.valid(resolvedVersion) && resolvedVersion !== "lts/*") {
4504
+ warnMigration(`package.json volta.node "${voltaNodeVersion}" is not an exact version. Pin an exact version (e.g. ${voltaNodeVersion}.0 or run \`volta pin node@${voltaNodeVersion}\`) then re-run migration.`, report);
4505
+ return false;
4506
+ }
4507
+ fs.writeFileSync(nodeVersionPath, `${resolvedVersion}\n`);
4508
+ if (report) {
4509
+ report.manualSteps.push("Remove the \"volta\" field from package.json");
4510
+ report.nodeVersionFileMigrated = true;
4511
+ } else log.info("You can now remove the \"volta\" field from package.json manually.");
4512
+ return true;
4513
+ }
4514
+ const sourcePath = path.join(projectPath, ".nvmrc");
4478
4515
  const originalAlias = fs.readFileSync(sourcePath, "utf8").split("\n")[0]?.trim() ?? "";
4479
4516
  const version = parseNvmrcVersion(originalAlias);
4480
4517
  if (!version) {
@@ -4484,7 +4521,10 @@ function migrateNodeVersionManagerFile(projectPath, _detection, report) {
4484
4521
  if (version === "lts/*" && (originalAlias === "node" || originalAlias === "stable")) log.info(`"${originalAlias}" in .nvmrc is not a specific version; automatically mapping to "lts/*"`);
4485
4522
  fs.writeFileSync(nodeVersionPath, `${version}\n`);
4486
4523
  fs.unlinkSync(sourcePath);
4487
- if (report) report.nodeVersionFileMigrated = true;
4524
+ if (report) {
4525
+ report.nodeVersionFileMigrated = true;
4526
+ if (detection.voltaPresent) report.manualSteps.push("Remove the \"volta\" field from package.json");
4527
+ } else if (detection.voltaPresent) log.info("You can now remove the \"volta\" field from package.json manually.");
4488
4528
  return true;
4489
4529
  }
4490
4530
  const AGENT_ALIASES = {
@@ -1,4 +1,4 @@
1
- import { D as promptGitHooks, T as defaultInteractive, d as ensurePreCommitHook, f as hasStagedConfigInViteConfig, i as updateExistingAgentInstructions } from "./agent-Dq7R_VUV.js";
1
+ import { D as promptGitHooks, T as defaultInteractive, d as ensurePreCommitHook, f as hasStagedConfigInViteConfig, i as updateExistingAgentInstructions } from "./agent-CTaSZwnH.js";
2
2
  import { t as lib_default } from "./lib-DxappLRQ.js";
3
3
  import { i as log, t as renderCliDoc } from "./help-HviKaKAU.js";
4
4
  import { join } from "node:path";
@@ -1,9 +1,9 @@
1
1
  import { r as __toESM, t as __commonJSMin } from "./chunk-BoAXSpZd.js";
2
- import { A as selectPackageManager, B as log, D as promptGitHooks, E as downloadPackageManager$1, F as PackageManager, G as text, K as Ct, L as cancel, M as displayRelative, N as templatesDir, O as runViteFmt, P as DependencyType, R as confirm, T as defaultInteractive, U as select, V as multiselect, W as spinner, a as writeAgentInstructions, b as rewriteMonorepoProject, k as runViteInstall, n as detectExistingAgentTargetPaths, p as installGitHooks, r as selectAgentTargetPaths, x as rewriteStandaloneProject, y as rewriteMonorepo, z as intro } from "./agent-Dq7R_VUV.js";
2
+ import { A as selectPackageManager, B as log, D as promptGitHooks, E as downloadPackageManager$1, F as PackageManager, G as text, K as Ct, L as cancel, M as displayRelative, N as templatesDir, O as runViteFmt, P as DependencyType, R as confirm, T as defaultInteractive, U as select, V as multiselect, W as spinner, a as writeAgentInstructions, b as rewriteMonorepoProject, k as runViteInstall, n as detectExistingAgentTargetPaths, p as installGitHooks, r as selectAgentTargetPaths, x as rewriteStandaloneProject, y as rewriteMonorepo, z as intro } from "./agent-CTaSZwnH.js";
3
3
  import { t as lib_default } from "./lib-DxappLRQ.js";
4
4
  import { c as readJsonFile, o as editJsonFile, t as checkNpmPackageExists } from "./package-2ArHHFnA.js";
5
5
  import { a as muted, i as log$1, n as accent, o as success, t as renderCliDoc } from "./help-HviKaKAU.js";
6
- import { a as detectExistingEditor, n as updatePackageJsonWithDeps, o as selectEditor, r as updateWorkspaceConfig, s as writeEditorConfigs, t as detectWorkspace$1 } from "./workspace-BCuLuHte.js";
6
+ import { a as detectExistingEditor, n as updatePackageJsonWithDeps, o as selectEditor, r as updateWorkspaceConfig, s as writeEditorConfigs, t as detectWorkspace$1 } from "./workspace-jYbI79UA.js";
7
7
  import path from "node:path";
8
8
  import { styleText } from "node:util";
9
9
  import color from "picocolors";
@@ -218,11 +218,11 @@ function discoverTemplate(templateName, templateArgs, workspaceInfo, interactive
218
218
  * This follows the same convention as `npm create` / `pnpm create`:
219
219
  * - `vite` → `create-vite`
220
220
  * - `vite@latest` → `create-vite@latest`
221
- * - `@tanstack/start` → `@tanstack/create-start`
222
- * - `@tanstack/start@latest` → `@tanstack/create-start@latest`
223
221
  *
224
222
  * Special cases for packages where the convention doesn't work:
225
223
  * - `nitro` → `create-nitro-app` (create-nitro is abandoned)
224
+ * - `svelte` → `sv`
225
+ * - `@tanstack/start` → `@tanstack/cli` (@tanstack/create-start is deprecated)
226
226
  *
227
227
  * Skips expansion for:
228
228
  * - Builtin templates (`vite:*`)
@@ -246,6 +246,7 @@ function expandCreateShorthand(templateName) {
246
246
  const name = atIndex === -1 ? rest : rest.slice(0, atIndex);
247
247
  const version = atIndex === -1 ? "" : rest.slice(atIndex);
248
248
  if (name.startsWith("create-")) return templateName;
249
+ if (scope === "@tanstack" && name === "start") return `@tanstack/cli${version}`;
249
250
  return `${scope}/create-${name}${version}`;
250
251
  }
251
252
  const atIndex = templateName.indexOf("@");
@@ -3490,7 +3491,8 @@ function autoFixRemoteTemplateCommand(templateInfo, workspaceInfo) {
3490
3491
  if (packageName === "create-vite") {
3491
3492
  templateInfo.args.push("--no-immediate");
3492
3493
  templateInfo.args.push("--no-rolldown");
3493
- } else if (packageName === "@tanstack/create-start") {
3494
+ } else if (packageName === "@tanstack/cli") {
3495
+ if (templateInfo.args[0] !== "create") templateInfo.args.unshift("create");
3494
3496
  templateInfo.args.push("--no-install");
3495
3497
  templateInfo.args.push("--no-toolchain");
3496
3498
  } else if (packageName === "sv") {
@@ -3499,7 +3501,7 @@ function autoFixRemoteTemplateCommand(templateInfo, workspaceInfo) {
3499
3501
  }
3500
3502
  if (workspaceInfo.isMonorepo) {
3501
3503
  if (packageName === "create-nuxt") templateInfo.args.push("--no-gitInit");
3502
- else if (packageName === "@tanstack/create-start") templateInfo.args.push("--no-git");
3504
+ else if (packageName === "@tanstack/cli") templateInfo.args.push("--no-git");
3503
3505
  }
3504
3506
  }
3505
3507
  //#endregion
@@ -3780,7 +3782,7 @@ const listTemplatesMessage = renderCliDoc({
3780
3782
  },
3781
3783
  {
3782
3784
  label: "@tanstack/start",
3783
- description: "TanStack applications (@tanstack/create-start)"
3785
+ description: "TanStack applications (@tanstack/cli create)"
3784
3786
  },
3785
3787
  {
3786
3788
  label: "next-app",
@@ -3809,7 +3811,7 @@ const listTemplatesMessage = renderCliDoc({
3809
3811
  lines: [
3810
3812
  ` ${accent("vp create")} ${muted("# interactive mode")}`,
3811
3813
  ` ${accent("vp create vite")} ${muted("# shorthand for create-vite")}`,
3812
- ` ${accent("vp create @tanstack/start")} ${muted("# shorthand for @tanstack/create-start")}`,
3814
+ ` ${accent("vp create @tanstack/start")} ${muted("# shorthand for @tanstack/cli create")}`,
3813
3815
  ` ${accent("vp create <template> -- <options>")} ${muted("# pass options to the template")}`
3814
3816
  ]
3815
3817
  },
@@ -1,10 +1,10 @@
1
1
  import { r as __toESM } from "./chunk-BoAXSpZd.js";
2
- import { A as selectPackageManager, B as log, D as promptGitHooks, E as downloadPackageManager$1, F as PackageManager, H as outro, I as require_semver, K as Ct, M as displayRelative, R as confirm, T as defaultInteractive, U as select, W as spinner, _ as migratePrettierToOxfmt, a as writeAgentInstructions, c as detectEslintProject, g as migrateNodeVersionManagerFile, h as migrateEslintToOxlint, j as upgradeYarn, k as runViteInstall, l as detectNodeVersionManagerFile, m as mergeViteConfigFiles, n as detectExistingAgentTargetPaths, o as checkViteVersion, p as installGitHooks, r as selectAgentTargetPaths, s as checkVitestVersion, t as detectAgentConflicts, u as detectPrettierProject, v as preflightGitHooksSetup, w as cancelAndExit, x as rewriteStandaloneProject, y as rewriteMonorepo } from "./agent-Dq7R_VUV.js";
2
+ import { A as selectPackageManager, B as log, D as promptGitHooks, E as downloadPackageManager$1, F as PackageManager, H as outro, I as require_semver, K as Ct, M as displayRelative, R as confirm, T as defaultInteractive, U as select, W as spinner, _ as migratePrettierToOxfmt, a as writeAgentInstructions, c as detectEslintProject, g as migrateNodeVersionManagerFile, h as migrateEslintToOxlint, j as upgradeYarn, k as runViteInstall, l as detectNodeVersionManagerFile, m as mergeViteConfigFiles, n as detectExistingAgentTargetPaths, o as checkViteVersion, p as installGitHooks, r as selectAgentTargetPaths, s as checkVitestVersion, t as detectAgentConflicts, u as detectPrettierProject, v as preflightGitHooksSetup, w as cancelAndExit, x as rewriteStandaloneProject, y as rewriteMonorepo } from "./agent-CTaSZwnH.js";
3
3
  import { t as lib_default } from "./lib-DxappLRQ.js";
4
4
  import { _ as isForceOverrideMode, a as readNearestPackageJson, i as hasVitePlusDependency } from "./package-2ArHHFnA.js";
5
5
  import { a as muted, i as log$1, n as accent, t as renderCliDoc } from "./help-HviKaKAU.js";
6
6
  import { r as createMigrationReport } from "./report-DGaKL5VQ.js";
7
- import { i as detectEditorConflicts, o as selectEditor, s as writeEditorConfigs, t as detectWorkspace$1 } from "./workspace-BCuLuHte.js";
7
+ import { i as detectEditorConflicts, o as selectEditor, s as writeEditorConfigs, t as detectWorkspace$1 } from "./workspace-jYbI79UA.js";
8
8
  import path from "node:path";
9
9
  import { styleText } from "node:util";
10
10
  import { vitePlusHeader } from "../../binding/index.js";
@@ -68,10 +68,14 @@ async function promptPrettierMigration(projectPath, interactive, packages) {
68
68
  if (!await migratePrettierToOxfmt(projectPath, interactive, prettierProject.configFile, packages)) cancelAndExit("Prettier migration failed. Fix the issue and re-run `vp migrate`.", 1);
69
69
  return true;
70
70
  }
71
- async function confirmNodeVersionFileMigration(interactive) {
71
+ async function confirmNodeVersionFileMigration(interactive, detection) {
72
+ const message = {
73
+ "package.json": "Migrate Volta node version (package.json) to .node-version?",
74
+ ".nvmrc": "Migrate .nvmrc to .node-version?"
75
+ }[detection.file];
72
76
  if (interactive) {
73
77
  const confirmed = await confirm({
74
- message: "Migrate .nvmrc to .node-version?",
78
+ message,
75
79
  initialValue: true
76
80
  });
77
81
  if (Ct(confirmed)) cancelAndExit();
@@ -284,7 +288,7 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, options, pa
284
288
  else if (prettierProject.hasDependency) warnPackageLevelPrettier();
285
289
  const nodeVersionDetection = detectNodeVersionManagerFile(rootDir);
286
290
  let migrateNodeVersionFile = false;
287
- if (nodeVersionDetection) migrateNodeVersionFile = await confirmNodeVersionFileMigration(options.interactive);
291
+ if (nodeVersionDetection) migrateNodeVersionFile = await confirmNodeVersionFileMigration(options.interactive, nodeVersionDetection);
288
292
  return {
289
293
  packageManager,
290
294
  shouldSetupHooks,
@@ -493,7 +497,7 @@ async function main() {
493
497
  const prettierMigrated = await promptPrettierMigration(workspaceInfoOptional.rootDir, options.interactive, workspaceInfoOptional.packages);
494
498
  const nodeVersionDetection = detectNodeVersionManagerFile(workspaceInfoOptional.rootDir);
495
499
  if (nodeVersionDetection) {
496
- if (await confirmNodeVersionFileMigration(options.interactive) && migrateNodeVersionManagerFile(workspaceInfoOptional.rootDir, nodeVersionDetection, report)) didMigrate = true;
500
+ if (await confirmNodeVersionFileMigration(options.interactive, nodeVersionDetection) && migrateNodeVersionManagerFile(workspaceInfoOptional.rootDir, nodeVersionDetection, report)) didMigrate = true;
497
501
  }
498
502
  if (eslintMigrated || prettierMigrated) {
499
503
  updateMigrationProgress("Rewriting configs");
@@ -1,4 +1,4 @@
1
- import { B as log, C as readYamlFile, F as PackageManager, K as Ct, S as editYamlFile, U as select } from "./agent-Dq7R_VUV.js";
1
+ import { B as log, C as readYamlFile, F as PackageManager, K as Ct, S as editYamlFile, U as select } from "./agent-CTaSZwnH.js";
2
2
  import { g as YAMLSeq, y as Scalar } from "./browser-09BZLUYM.js";
3
3
  import { c as readJsonFile, l as writeJsonFile, o as editJsonFile, r as getScopeFromPackageName } from "./package-2ArHHFnA.js";
4
4
  import path, { posix, win32 } from "node:path";
@@ -6277,6 +6277,7 @@ async function detectWorkspace$1(rootDir) {
6277
6277
  } else if (fs.existsSync(packageJsonFile)) {
6278
6278
  const pkg = readJsonFile(packageJsonFile);
6279
6279
  if (Array.isArray(pkg.workspaces)) result.workspacePatterns = pkg.workspaces;
6280
+ else if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) result.workspacePatterns = pkg.workspaces.packages;
6280
6281
  }
6281
6282
  const dirs = /* @__PURE__ */ new Set();
6282
6283
  for (const pattern of result.workspacePatterns) {
@@ -6332,7 +6333,8 @@ function updateWorkspaceConfig(projectPath, workspaceInfo) {
6332
6333
  doc.setIn(["packages"], packages);
6333
6334
  });
6334
6335
  else editJsonFile(path.join(workspaceInfo.rootDir, "package.json"), (pkg) => {
6335
- pkg.workspaces = [...pkg.workspaces || [], pattern];
6336
+ if (pkg.workspaces && !Array.isArray(pkg.workspaces)) pkg.workspaces.packages = [...pkg.workspaces.packages || [], pattern];
6337
+ else pkg.workspaces = [...pkg.workspaces || [], pattern];
6336
6338
  return pkg;
6337
6339
  });
6338
6340
  }
@@ -9,6 +9,7 @@ export interface ConfigFiles {
9
9
  prettierConfig?: string;
10
10
  prettierIgnore?: boolean;
11
11
  nvmrcFile?: boolean;
12
+ voltaNode?: string;
12
13
  }
13
14
  export declare const PRETTIER_PACKAGE_JSON_CONFIG = "package.json#prettier";
14
15
  export declare const PRETTIER_CONFIG_FILES: readonly [".prettierrc", ".prettierrc.json", ".prettierrc.jsonc", ".prettierrc.yaml", ".prettierrc.yml", ".prettierrc.toml", ".prettierrc.js", ".prettierrc.cjs", ".prettierrc.mjs", ".prettierrc.ts", ".prettierrc.cts", ".prettierrc.mts", "prettier.config.js", "prettier.config.cjs", "prettier.config.mjs", "prettier.config.ts", "prettier.config.cts", "prettier.config.mts"];
@@ -134,22 +134,6 @@ export function detectConfigs(projectPath) {
134
134
  break;
135
135
  }
136
136
  }
137
- // Check for "prettier" key in package.json if no config file found
138
- if (!configs.prettierConfig) {
139
- const packageJsonPath = path.join(projectPath, 'package.json');
140
- if (fs.existsSync(packageJsonPath)) {
141
- try {
142
- const content = fs.readFileSync(packageJsonPath, 'utf8');
143
- const pkg = JSON.parse(content);
144
- if (pkg.prettier) {
145
- configs.prettierConfig = PRETTIER_PACKAGE_JSON_CONFIG;
146
- }
147
- }
148
- catch {
149
- // ignore parse errors
150
- }
151
- }
152
- }
153
137
  // Check for .prettierignore
154
138
  if (fs.existsSync(path.join(projectPath, '.prettierignore'))) {
155
139
  configs.prettierIgnore = true;
@@ -158,5 +142,23 @@ export function detectConfigs(projectPath) {
158
142
  if (fs.existsSync(path.join(projectPath, '.nvmrc'))) {
159
143
  configs.nvmrcFile = true;
160
144
  }
145
+ // Check package.json for "prettier" key and Volta node version
146
+ const packageJsonPath = path.join(projectPath, 'package.json');
147
+ if (fs.existsSync(packageJsonPath)) {
148
+ try {
149
+ const content = fs.readFileSync(packageJsonPath, 'utf8');
150
+ const pkg = JSON.parse(content);
151
+ if (!configs.prettierConfig && pkg.prettier) {
152
+ configs.prettierConfig = PRETTIER_PACKAGE_JSON_CONFIG;
153
+ }
154
+ const voltaNode = pkg.volta?.node;
155
+ if (typeof voltaNode === 'string') {
156
+ configs.voltaNode = voltaNode;
157
+ }
158
+ }
159
+ catch {
160
+ // ignore parse errors
161
+ }
162
+ }
161
163
  return configs;
162
164
  }
@@ -100,11 +100,17 @@ export declare function createPreCommitHook(projectPath: string, dir?: string):
100
100
  * Called only when hooks are being set up (not with --no-hooks).
101
101
  */
102
102
  export declare function rewritePrepareScript(rootDir: string): string | undefined;
103
- export interface NodeVersionManagerDetection {
104
- file: string;
105
- }
103
+ export type NodeVersionManagerDetection = {
104
+ file: '.nvmrc';
105
+ voltaPresent?: true;
106
+ } | {
107
+ file: 'package.json';
108
+ voltaNodeVersion: string;
109
+ };
106
110
  /**
107
111
  * Detect a .nvmrc file in the project directory.
112
+ * If not found, check for a Volta node version in package.json.
113
+ * If either is found, return the relevant info for migration.
108
114
  * Returns undefined if not found or .node-version already exists.
109
115
  */
110
116
  export declare function detectNodeVersionManagerFile(projectPath: string): NodeVersionManagerDetection | undefined;
@@ -115,7 +121,9 @@ export declare function detectNodeVersionManagerFile(projectPath: string): NodeV
115
121
  */
116
122
  export declare function parseNvmrcVersion(alias: string): string | null;
117
123
  /**
118
- * Migrate .nvmrc to .node-version and remove .nvmrc.
124
+ * Migrate .nvmrc or Volta node version from package.json to .node-version.
125
+ * - For .nvmrc: the source file is removed after migration.
126
+ * - For package.json (Volta): the volta field is left as-is; removal is left to the user's discretion.
119
127
  * Returns true on success, false if migration was skipped or failed.
120
128
  */
121
- export declare function migrateNodeVersionManagerFile(projectPath: string, _detection: NodeVersionManagerDetection, report?: MigrationReport): boolean;
129
+ export declare function migrateNodeVersionManagerFile(projectPath: string, detection: NodeVersionManagerDetection, report?: MigrationReport): boolean;
@@ -887,8 +887,12 @@ function rewriteBunCatalog(projectPath) {
887
887
  return;
888
888
  }
889
889
  editJsonFile(packageJsonPath, (pkg) => {
890
- const catalog = { ...pkg.catalog };
891
- // Add vite-plus managed packages to catalog
890
+ // Bun supports catalogs in both workspaces.catalog and top-level catalog;
891
+ // prefer the location the user already chose to avoid moving their config.
892
+ const workspacesObj = pkg.workspaces && !Array.isArray(pkg.workspaces) ? pkg.workspaces : undefined;
893
+ const catalog = {
894
+ ...(workspacesObj?.catalog ?? pkg.catalog),
895
+ };
892
896
  for (const [key, value] of Object.entries(VITE_PLUS_OVERRIDE_PACKAGES)) {
893
897
  if (!value.startsWith('file:')) {
894
898
  catalog[key] = value;
@@ -897,11 +901,15 @@ function rewriteBunCatalog(projectPath) {
897
901
  if (!VITE_PLUS_VERSION.startsWith('file:')) {
898
902
  catalog[VITE_PLUS_NAME] = VITE_PLUS_VERSION;
899
903
  }
900
- // Remove replaced packages from catalog
901
904
  for (const name of REMOVE_PACKAGES) {
902
905
  delete catalog[name];
903
906
  }
904
- pkg.catalog = catalog;
907
+ if (workspacesObj?.catalog != null) {
908
+ workspacesObj.catalog = catalog;
909
+ }
910
+ else {
911
+ pkg.catalog = catalog;
912
+ }
905
913
  // bun overrides support catalog: references
906
914
  const overrides = { ...pkg.overrides };
907
915
  for (const key of Object.keys(VITE_PLUS_OVERRIDE_PACKAGES)) {
@@ -1073,6 +1081,15 @@ export function rewritePackageJson(pkg, packageManager, isMonorepo, skipStagedMi
1073
1081
  ...pkg.devDependencies,
1074
1082
  [VITE_PLUS_NAME]: version,
1075
1083
  };
1084
+ // Add vitest to devDependencies when a remaining dependency likely peer-depends
1085
+ // on vitest (e.g., vitest-browser-svelte). Without this, pnpm resolves the real
1086
+ // vitest for peer deps instead of @voidzero-dev/vite-plus-test, causing
1087
+ // third-party type augmentations to target the wrong module.
1088
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1089
+ if (!allDeps.vitest && Object.keys(allDeps).some((name) => name.includes('vitest'))) {
1090
+ const ver = VITE_PLUS_OVERRIDE_PACKAGES.vitest;
1091
+ pkg.devDependencies.vitest = supportCatalog && !ver.startsWith('file:') ? 'catalog:' : ver;
1092
+ }
1076
1093
  }
1077
1094
  return extractedStagedConfig;
1078
1095
  }
@@ -1769,6 +1786,8 @@ function setPackageManager(projectDir, downloadPackageManager) {
1769
1786
  }
1770
1787
  /**
1771
1788
  * Detect a .nvmrc file in the project directory.
1789
+ * If not found, check for a Volta node version in package.json.
1790
+ * If either is found, return the relevant info for migration.
1772
1791
  * Returns undefined if not found or .node-version already exists.
1773
1792
  */
1774
1793
  export function detectNodeVersionManagerFile(projectPath) {
@@ -1777,8 +1796,14 @@ export function detectNodeVersionManagerFile(projectPath) {
1777
1796
  return undefined;
1778
1797
  }
1779
1798
  const configs = detectConfigs(projectPath);
1799
+ // .nvmrc takes priority over volta.node when both are present.
1800
+ // voltaPresent is carried through so the migration step can remind the user
1801
+ // to remove the now-redundant volta field from package.json.
1780
1802
  if (configs.nvmrcFile) {
1781
- return { file: '.nvmrc' };
1803
+ return configs.voltaNode ? { file: '.nvmrc', voltaPresent: true } : { file: '.nvmrc' };
1804
+ }
1805
+ if (configs.voltaNode) {
1806
+ return { file: 'package.json', voltaNodeVersion: configs.voltaNode };
1782
1807
  }
1783
1808
  return undefined;
1784
1809
  }
@@ -1814,12 +1839,34 @@ export function parseNvmrcVersion(alias) {
1814
1839
  return normalized;
1815
1840
  }
1816
1841
  /**
1817
- * Migrate .nvmrc to .node-version and remove .nvmrc.
1842
+ * Migrate .nvmrc or Volta node version from package.json to .node-version.
1843
+ * - For .nvmrc: the source file is removed after migration.
1844
+ * - For package.json (Volta): the volta field is left as-is; removal is left to the user's discretion.
1818
1845
  * Returns true on success, false if migration was skipped or failed.
1819
1846
  */
1820
- export function migrateNodeVersionManagerFile(projectPath, _detection, report) {
1821
- const sourcePath = path.join(projectPath, '.nvmrc');
1847
+ export function migrateNodeVersionManagerFile(projectPath, detection, report) {
1822
1848
  const nodeVersionPath = path.join(projectPath, '.node-version');
1849
+ // Volta: node version was already extracted during detection — no package.json re-read needed
1850
+ if (detection.file === 'package.json') {
1851
+ const { voltaNodeVersion } = detection;
1852
+ // Normalize Volta's "lts" alias to the .node-version compatible form
1853
+ const resolvedVersion = voltaNodeVersion === 'lts' ? 'lts/*' : voltaNodeVersion;
1854
+ if (!semver.valid(resolvedVersion) && resolvedVersion !== 'lts/*') {
1855
+ warnMigration(`package.json volta.node "${voltaNodeVersion}" is not an exact version. Pin an exact version (e.g. ${voltaNodeVersion}.0 or run \`volta pin node@${voltaNodeVersion}\`) then re-run migration.`, report);
1856
+ return false;
1857
+ }
1858
+ fs.writeFileSync(nodeVersionPath, `${resolvedVersion}\n`);
1859
+ if (report) {
1860
+ report.manualSteps.push('Remove the "volta" field from package.json');
1861
+ report.nodeVersionFileMigrated = true;
1862
+ }
1863
+ else {
1864
+ prompts.log.info('You can now remove the "volta" field from package.json manually.');
1865
+ }
1866
+ return true;
1867
+ }
1868
+ // .nvmrc: parse version alias and write to .node-version
1869
+ const sourcePath = path.join(projectPath, '.nvmrc');
1823
1870
  const content = fs.readFileSync(sourcePath, 'utf8');
1824
1871
  const originalAlias = content.split('\n')[0]?.trim() ?? '';
1825
1872
  const version = parseNvmrcVersion(originalAlias);
@@ -1836,6 +1883,13 @@ export function migrateNodeVersionManagerFile(projectPath, _detection, report) {
1836
1883
  fs.unlinkSync(sourcePath);
1837
1884
  if (report) {
1838
1885
  report.nodeVersionFileMigrated = true;
1886
+ // Both .nvmrc and volta were present; .nvmrc was migrated but volta still lingers.
1887
+ if (detection.voltaPresent) {
1888
+ report.manualSteps.push('Remove the "volta" field from package.json');
1889
+ }
1890
+ }
1891
+ else if (detection.voltaPresent) {
1892
+ prompts.log.info('You can now remove the "volta" field from package.json manually.');
1839
1893
  }
1840
1894
  return true;
1841
1895
  }
@@ -10,7 +10,7 @@
10
10
  * Oxlint is a fast JavaScript/TypeScript linter written in Rust that
11
11
  * provides ESLint-compatible linting with significantly better performance.
12
12
  */
13
- import { existsSync } from 'node:fs';
13
+ import { existsSync, realpathSync } from 'node:fs';
14
14
  import { dirname, join } from 'node:path';
15
15
  import { relative } from 'node:path/win32';
16
16
  import { fileURLToPath } from 'node:url';
@@ -37,7 +37,8 @@ export async function lint() {
37
37
  let oxlintTsgolintPath = resolve('oxlint-tsgolint/bin/tsgolint');
38
38
  if (process.platform === 'win32') {
39
39
  // On Windows, try .exe first (bun creates .exe), then .cmd (npm/pnpm/yarn create .cmd)
40
- const localBinDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '.bin');
40
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
41
+ const localBinDir = join(scriptDir, '..', 'node_modules', '.bin');
41
42
  const cwdBinDir = join(process.cwd(), 'node_modules', '.bin');
42
43
  oxlintTsgolintPath =
43
44
  [
@@ -45,7 +46,22 @@ export async function lint() {
45
46
  join(localBinDir, 'tsgolint.cmd'),
46
47
  join(cwdBinDir, 'tsgolint.exe'),
47
48
  join(cwdBinDir, 'tsgolint.cmd'),
48
- ].find((p) => existsSync(p)) ?? join(cwdBinDir, 'tsgolint.cmd');
49
+ ].find((p) => existsSync(p)) ?? '';
50
+ // Bun stores packages in .bun/ cache dirs where the symlinked paths above won't match.
51
+ if (!oxlintTsgolintPath) {
52
+ try {
53
+ const realPkgDir = realpathSync(join(scriptDir, '..'));
54
+ const realBinDir = join(dirname(realPkgDir), '.bin');
55
+ oxlintTsgolintPath =
56
+ [join(realBinDir, 'tsgolint.exe'), join(realBinDir, 'tsgolint.cmd')].find((p) => existsSync(p)) ?? '';
57
+ }
58
+ catch {
59
+ // realpath failed, fall through to default
60
+ }
61
+ }
62
+ if (!oxlintTsgolintPath) {
63
+ oxlintTsgolintPath = join(cwdBinDir, 'tsgolint.cmd');
64
+ }
49
65
  const relativePath = relative(process.cwd(), oxlintTsgolintPath);
50
66
  // Only prepend .\ if it's actually a relative path (not an absolute path returned by relative())
51
67
  oxlintTsgolintPath = /^[a-zA-Z]:/.test(relativePath) ? relativePath : `.\\${relativePath}`;
@@ -1 +1,9 @@
1
- export type StagedConfig = Record<string, string | string[]>;
1
+ type SyncGenerateTask = (stagedFileNames: readonly string[]) => string | string[];
2
+ type AsyncGenerateTask = (stagedFileNames: readonly string[]) => Promise<string | string[]>;
3
+ type GenerateTask = SyncGenerateTask | AsyncGenerateTask;
4
+ type TaskFunction = {
5
+ title: string;
6
+ task: (stagedFileNames: readonly string[]) => void | Promise<void>;
7
+ };
8
+ export type StagedConfig = Record<string, string | TaskFunction | GenerateTask | (string | GenerateTask)[]> | GenerateTask;
9
+ export {};
@@ -1 +1,3 @@
1
+ // Copied from lint-staged@16.4.0 (node_modules/lint-staged/lib/index.d.ts)
2
+ // TODO: Re-export directly from lint-staged once we can bundle .d.ts files (#744).
1
3
  export {};
@@ -1,4 +1,8 @@
1
1
  import { DependencyType, type WorkspaceInfo, type WorkspaceInfoOptional, type WorkspacePackage } from '../types/index.js';
2
+ export type NpmWorkspaces = string[] | {
3
+ packages?: string[];
4
+ catalog?: Record<string, string>;
5
+ };
2
6
  export declare function findPackageJsonFilesFromPatterns(patterns: string[], cwd: string): string[];
3
7
  export declare function detectWorkspace(rootDir: string): Promise<WorkspaceInfoOptional>;
4
8
  export declare function discoverWorkspacePackages(workspacePatterns: string[], rootDir: string): WorkspacePackage[];
@@ -51,11 +51,14 @@ export async function detectWorkspace(rootDir) {
51
51
  }
52
52
  }
53
53
  else if (fs.existsSync(packageJsonFile)) {
54
- // Check for npm/yarn workspace
54
+ // Check for npm/yarn/bun workspace (array or object form)
55
55
  const pkg = readJsonFile(packageJsonFile);
56
56
  if (Array.isArray(pkg.workspaces)) {
57
57
  result.workspacePatterns = pkg.workspaces;
58
58
  }
59
+ else if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) {
60
+ result.workspacePatterns = pkg.workspaces.packages;
61
+ }
59
62
  }
60
63
  const dirs = new Set();
61
64
  for (const pattern of result.workspacePatterns) {
@@ -153,9 +156,15 @@ export function updateWorkspaceConfig(projectPath, workspaceInfo) {
153
156
  });
154
157
  }
155
158
  else {
156
- // Update package.json workspaces
159
+ // Update package.json workspaces (array or object form)
157
160
  editJsonFile(path.join(workspaceInfo.rootDir, 'package.json'), (pkg) => {
158
- pkg.workspaces = [...(pkg.workspaces || []), pattern];
161
+ if (pkg.workspaces && !Array.isArray(pkg.workspaces)) {
162
+ // Preserve object form (e.g., Bun catalogs, Yarn classic nohoist)
163
+ pkg.workspaces.packages = [...(pkg.workspaces.packages || []), pattern];
164
+ }
165
+ else {
166
+ pkg.workspaces = [...(pkg.workspaces || []), pattern];
167
+ }
159
168
  return pkg;
160
169
  });
161
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plus",
3
- "version": "0.1.15",
3
+ "version": "0.1.16-alpha.0",
4
4
  "description": "The Unified Toolchain for the Web",
5
5
  "homepage": "https://viteplus.dev/guide",
6
6
  "bugs": {
@@ -326,8 +326,8 @@
326
326
  "oxlint": "=1.58.0",
327
327
  "oxlint-tsgolint": "=0.18.1",
328
328
  "picocolors": "^1.1.1",
329
- "@voidzero-dev/vite-plus-core": "0.1.15",
330
- "@voidzero-dev/vite-plus-test": "0.1.15"
329
+ "@voidzero-dev/vite-plus-core": "0.1.16-alpha.0",
330
+ "@voidzero-dev/vite-plus-test": "0.1.16-alpha.0"
331
331
  },
332
332
  "devDependencies": {
333
333
  "@napi-rs/cli": "^3.4.1",
@@ -348,9 +348,9 @@
348
348
  "tsdown": "^0.21.7",
349
349
  "validate-npm-package-name": "^7.0.2",
350
350
  "yaml": "^2.8.1",
351
- "@voidzero-dev/vite-plus-prompts": "0.0.0",
352
351
  "rolldown": "1.0.0-rc.12",
353
- "vite": "npm:@voidzero-dev/vite-plus-core@0.1.15"
352
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.1.16-alpha.0",
353
+ "@voidzero-dev/vite-plus-prompts": "0.0.0"
354
354
  },
355
355
  "napi": {
356
356
  "binaryName": "vite-plus",
@@ -370,14 +370,14 @@
370
370
  "node": "^20.19.0 || >=22.12.0"
371
371
  },
372
372
  "optionalDependencies": {
373
- "@voidzero-dev/vite-plus-darwin-arm64": "0.1.15",
374
- "@voidzero-dev/vite-plus-darwin-x64": "0.1.15",
375
- "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.1.15",
376
- "@voidzero-dev/vite-plus-linux-arm64-musl": "0.1.15",
377
- "@voidzero-dev/vite-plus-linux-x64-gnu": "0.1.15",
378
- "@voidzero-dev/vite-plus-linux-x64-musl": "0.1.15",
379
- "@voidzero-dev/vite-plus-win32-x64-msvc": "0.1.15",
380
- "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.1.15"
373
+ "@voidzero-dev/vite-plus-darwin-arm64": "0.1.16-alpha.0",
374
+ "@voidzero-dev/vite-plus-darwin-x64": "0.1.16-alpha.0",
375
+ "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.1.16-alpha.0",
376
+ "@voidzero-dev/vite-plus-linux-arm64-musl": "0.1.16-alpha.0",
377
+ "@voidzero-dev/vite-plus-linux-x64-gnu": "0.1.16-alpha.0",
378
+ "@voidzero-dev/vite-plus-linux-x64-musl": "0.1.16-alpha.0",
379
+ "@voidzero-dev/vite-plus-win32-x64-msvc": "0.1.16-alpha.0",
380
+ "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.1.16-alpha.0"
381
381
  },
382
382
  "scripts": {
383
383
  "build": "oxnode -C dev ./build.ts",
@@ -26,3 +26,24 @@ vp update vite-plus
26
26
  ```
27
27
 
28
28
  You can also use `vp add vite-plus@latest` if you want to move the dependency explicitly to the latest version.
29
+
30
+ ### Updating Aliased Packages
31
+
32
+ Vite+ sets up npm aliases for its core packages during installation:
33
+
34
+ - `vite` is aliased to `npm:@voidzero-dev/vite-plus-core@latest`
35
+ - `vitest` is aliased to `npm:@voidzero-dev/vite-plus-test@latest`
36
+
37
+ `vp update vite-plus` does not re-resolve these aliases in the lockfile. To fully upgrade, update them separately:
38
+
39
+ ```bash
40
+ vp update @voidzero-dev/vite-plus-core @voidzero-dev/vite-plus-test
41
+ ```
42
+
43
+ Or update everything at once:
44
+
45
+ ```bash
46
+ vp update vite-plus @voidzero-dev/vite-plus-core @voidzero-dev/vite-plus-test
47
+ ```
48
+
49
+ You can verify with `vp outdated` that no Vite+ packages remain outdated.