vite-plus 1.2.0 → 1.4.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.
Files changed (94) hide show
  1. package/AGENTS.md +4 -0
  2. package/LICENSE +1 -849
  3. package/README.md +1 -1
  4. package/bin/vpx +9 -0
  5. package/binding/index.cjs +59 -54
  6. package/binding/index.d.cts +132 -2
  7. package/dist/{agent-KeDc6vmg.js → agent-D8oxxhVR.js} +7 -9
  8. package/dist/bin.js +27 -34
  9. package/dist/{cli-parse-DHE8NrBU.js → cli-parse-aeUeFDlC.js} +2 -3
  10. package/dist/command-TjS_6y9O.js +1 -0
  11. package/dist/{command-CguLh2KL.js → command-rY79K5NI.js} +40 -42
  12. package/dist/{comparison-CmVirWIW-DUGaiYqF.js → comparison-CmVirWIW-UlEf2maz.js} +1 -2
  13. package/dist/config/bin.js +8 -10
  14. package/dist/{constants-BW3g7VnD.js → constants-DRUnJ_Dz.js} +4 -6
  15. package/dist/create/bin.d.ts +4 -8
  16. package/dist/create/bin.js +64 -68
  17. package/dist/{define-config-CIlk9scU.js → define-config-ChwJcu5e.js} +6 -9
  18. package/dist/{define-config-Dq_23Eyr.cjs → define-config-Cv2P6NT2.cjs} +8 -13
  19. package/dist/{define-config-Bexdd6RZ.d.ts → define-config-rPhYIFSn.d.ts} +1 -11
  20. package/dist/define-config.cjs +1 -1
  21. package/dist/define-config.d.ts +1 -1
  22. package/dist/define-config.js +1 -1
  23. package/dist/{dist-Bi7ZEK3c.js → dist-Bs0SObNC.js} +77 -149
  24. package/dist/{dist-Cqz8OtiO.js → dist-CdkMvIiQ.js} +11 -5
  25. package/dist/dist-zDZV5eEc.js +3 -0
  26. package/dist/{documentation-DqcZSthV.js → documentation-cv_y0Auv.js} +1 -2
  27. package/dist/{editor-b1XUwt9v.js → editor-ClL78D8u.js} +1492 -4211
  28. package/dist/{help-BmKpeOP9.js → help-B03t_iQ4.js} +1 -2
  29. package/dist/hooks/bin.js +4 -5
  30. package/dist/{hooks-DhwsWb46.js → hooks-xRcJPc-O.js} +5 -4
  31. package/dist/index.cjs +2 -3
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/{json-JP24qHUF.js → json-kl84kDyH.js} +9 -18
  35. package/dist/lint-plugins.cjs +1 -1
  36. package/dist/migration/bin.js +719 -184
  37. package/dist/migration/compat/worker.js +4 -6
  38. package/dist/{oxlint-plugin-config-Dx-ZY2MN.js → oxlint-plugin-config-BVpqYdcC.js} +3 -4
  39. package/dist/oxlint-plugin.d.ts +0 -2
  40. package/dist/oxlint-plugin.js +6 -7
  41. package/dist/{pack-BK38MLVZ.d.ts → pack-ARPFMefW.d.ts} +0 -2
  42. package/dist/pack-bin.d.ts +1 -3
  43. package/dist/pack-bin.js +3 -4
  44. package/dist/pack.d.ts +1 -1
  45. package/dist/{package-BP16iQby.js → package-DzsomcUD.js} +10 -95
  46. package/dist/{package-overrides-D1RcXSFl.js → package-overrides-Ci8GXouY.js} +5 -8
  47. package/dist/picomatch-DEUoj81q.js +1916 -0
  48. package/dist/{prompts-BGpQtC5z.js → prompts-GL1vj0wk.js} +113 -239
  49. package/dist/{protocol-D99W10Qi.js → protocol-CkNmY-Mr.js} +1 -2
  50. package/dist/{report-NhQk8VHb.js → report-DLHO30yZ.js} +2 -2
  51. package/dist/{resolve-vite-config-x47JetlI.js → resolve-vite-config-Dn5896A4.js} +9 -25
  52. package/dist/resolve-vite-config-QO6cC0SP.js +1 -0
  53. package/dist/{rolldown-runtime-CMFfr-1z.js → rolldown-runtime-DegdyBIC.js} +2 -3
  54. package/dist/script-esm-hooks.d.ts +8 -0
  55. package/dist/script-esm-hooks.js +24 -0
  56. package/dist/script-hooks-9Fof5UMY.js +176 -0
  57. package/dist/script-preload.cjs +468 -0
  58. package/dist/script-register.d.ts +1 -0
  59. package/dist/script-register.js +22 -0
  60. package/dist/script-watch.d.ts +1 -0
  61. package/dist/script-watch.js +1908 -0
  62. package/dist/staged/bin.js +81 -2011
  63. package/dist/sync-versions/bin.js +29 -50
  64. package/dist/sync-versions/bin.mjs +117 -224
  65. package/dist/{terminal-MKGAuy-p.js → terminal-BcuPUy-j.js} +1 -2
  66. package/dist/toolchain.js +36 -10
  67. package/dist/toolchain.json +36 -10
  68. package/dist/{tsconfig-C5PnJfW4.js → tsconfig-Do6MAG_f.js} +1 -1
  69. package/dist/{tsconfig-D2DzAQ_D.js → tsconfig-LE_mAwe_.js} +8 -10
  70. package/dist/version.d.ts +1 -3
  71. package/dist/version.js +5 -6
  72. package/dist/versions.js +5 -5
  73. package/dist/{vitest-ecosystem-BU0AqZq-.js → vitest-ecosystem-BeEYz-qF.js} +1 -2
  74. package/dist/{vitest-v5-entry-points-DthU1e_O.js → vitest-v5-entry-points-NyJsFeux.js} +1 -2
  75. package/dist/vpx-bin.d.ts +1 -0
  76. package/dist/vpx-bin.js +232 -0
  77. package/dist/vpx-script-EXgV5QOf.js +434 -0
  78. package/docs/config/run.md +4 -0
  79. package/docs/guide/check.md +1 -0
  80. package/docs/guide/ci.md +13 -3
  81. package/docs/guide/create.md +34 -0
  82. package/docs/guide/env.md +27 -13
  83. package/docs/guide/global-cli.md +52 -0
  84. package/docs/guide/local-cli.md +4 -4
  85. package/docs/guide/migrate-rules.md +48 -5
  86. package/docs/guide/migrate.md +6 -6
  87. package/docs/guide/package-management.md +19 -2
  88. package/docs/guide/test.md +1 -1
  89. package/docs/guide/vitest-v5.md +2 -2
  90. package/docs/guide/vpx.md +170 -9
  91. package/package.json +32 -29
  92. package/dist/command-DaRqSNpP.js +0 -1
  93. package/dist/dist-CBWGibx8.js +0 -3
  94. package/dist/resolve-vite-config-CGzlyhGr.js +0 -1
@@ -1,129 +1,623 @@
1
- import { t as documentationUrl$1 } from "../documentation-DqcZSthV.js";
2
- import { a as muted, c as warnMsg, i as log, o as printHeader, r as formatDuration, t as accent } from "../terminal-MKGAuy-p.js";
3
- import { _ as engines, a as SETUP_VP_VERSION, d as VITE_PLUS_OVERRIDE_PACKAGES, f as VITE_PLUS_VERSION, m as isForceOverrideMode } from "../constants-BW3g7VnD.js";
4
- import { a as writeJsonFile, c as modify, i as readJsonFile, l as parse, o as applyEdits, s as findNodeAtLocation, u as parseTree } from "../json-JP24qHUF.js";
5
- import { a as hasVitePlusDependency, i as hasPackageManagerDeclaration, n as detectPackageMetadata, o as readNearestPackageJson } from "../package-BP16iQby.js";
6
- import { t as vitest_v5_entry_points_default } from "../vitest-v5-entry-points-DthU1e_O.js";
7
- import { C as confirm, D as outro, M as isCancel, O as select, S as normalize$1, T as log$1, c as selectPackageManager, i as promptGitHooks, k as spinner, l as upgradeYarn, n as defaultInteractive, o as runViteFmt, r as downloadPackageManager$1, s as runViteInstall, t as cancelAndExit, x as getMajor, y as PackageManager } from "../prompts-BGpQtC5z.js";
8
- import { _ as isGreaterThanOrEqual, p as compare } from "../comparison-CmVirWIW-DUGaiYqF.js";
9
- import { a as rangeToComparators, i as normalizeRange, n as findMinimumForRange, o as rangesIntersect, r as isRangeSubset, s as satisfies, t as extractOverrideTargetName } from "../package-overrides-D1RcXSFl.js";
10
- import { i as selectAgentTargetPaths, l as displayRelative, n as detectAgentConflicts, r as detectExistingAgentTargetPaths, s as writeAgentInstructions } from "../agent-KeDc6vmg.js";
11
- import { n as addMigrationWarning, r as createMigrationReport } from "../report-NhQk8VHb.js";
12
- import { $ as confirmEslintMigration, A as createCatalogDependencyResolver, B as addFrameworkShim, C as mergeViteConfigFiles, D as usesWebdriverioProvider, E as ensureVitePlusBootstrap, G as migrateTsupToTsdown, H as hasFrameworkShim, I as minimatch, J as confirmPrettierMigration, L as findGitRoot, M as detectWorkspace$1, O as configureYarnNodeModulesMode, Q as warnPackageLevelPrettier, S as mergeTsdownConfigFile, T as detectVitePlusBootstrapPending, U as confirmTsupMigration, V as detectFramework, W as detectTsupProject, X as migratePrettierToOxfmt, Y as detectPrettierProject, _ as installGitHooks, at as warnLegacyEslintConfig, ct as checkViteVersion, d as finalizeCoreMigrationForExistingVitePlus, et as detectEslintProject, f as detectNodeVersionManagerFile, g as detectLegacyGitHooksMigrationCandidate, i as selectEditor, it as warnIncompatibleEslintIntegration, k as detectYarnPnpMode, l as rewriteStandaloneProject, lt as checkVitestVersion, m as migrateSetupVpVersion, nt as migrateEslintToOxlint, o as writeEditorConfigs, ot as warnPackageLevelEslint, p as migrateNodeVersionManagerFile, q as warnMissingTsupConfig, r as isJsonLikeFile, s as rewriteMonorepo, st as WEBDRIVERIO_PROVIDER, t as detectEditorConflicts, tt as detectIncompatibleEslintIntegration, u as detectPendingCoreMigration, v as preflightGitHooksSetup, w as collectToolchainVersionChanges, x as injectLintTypeCheckDefaults, y as shouldSkipStagedMigrationForHooks, z as extractOverrideParentSegments } from "../editor-b1XUwt9v.js";
13
- import { n as runCommandSilently } from "../command-CguLh2KL.js";
14
- import { t as require_dist } from "../dist-Bi7ZEK3c.js";
15
- import { a as hasBaseUrlInTsconfig, i as fixBaseUrlInTsconfig, t as confirmBaseUrlFix } from "../tsconfig-D2DzAQ_D.js";
16
- import { t as unwrapCliParseOutcome } from "../cli-parse-DHE8NrBU.js";
17
- import { t as ROLLDOWN_COMPAT_RESULT_PREFIX } from "../protocol-D99W10Qi.js";
1
+ import { t as documentationUrl$1 } from "../documentation-cv_y0Auv.js";
2
+ import { a as muted, c as warnMsg, i as log, o as printHeader, r as formatDuration, t as accent } from "../terminal-BcuPUy-j.js";
3
+ import { _ as engines, a as SETUP_VP_VERSION, d as VITE_PLUS_OVERRIDE_PACKAGES, f as VITE_PLUS_VERSION, m as isForceOverrideMode } from "../constants-DRUnJ_Dz.js";
4
+ import { a as writeJsonFile, c as modify, i as readJsonFile, l as parse, n as editJsonFile, o as applyEdits, r as isJsonFile, s as findNodeAtLocation, u as parseTree } from "../json-kl84kDyH.js";
5
+ import { a as hasVitePlusDependency, i as hasPackageManagerDeclaration, n as detectPackageMetadata, o as readNearestPackageJson } from "../package-DzsomcUD.js";
6
+ import { t as vitest_v5_entry_points_default } from "../vitest-v5-entry-points-NyJsFeux.js";
7
+ import { C as confirm, D as outro, M as isCancel, O as select, S as normalize$1, T as log$1, c as selectPackageManager, i as promptGitHooks, k as spinner, l as upgradeYarn, n as defaultInteractive, o as runViteFmt, r as downloadPackageManager$1, s as runViteInstall, t as cancelAndExit, x as getMajor, y as PackageManager } from "../prompts-GL1vj0wk.js";
8
+ import { _ as isGreaterThanOrEqual, p as compare } from "../comparison-CmVirWIW-UlEf2maz.js";
9
+ import { a as rangeToComparators, i as normalizeRange, n as findMinimumForRange, o as rangesIntersect, r as isRangeSubset, s as satisfies, t as extractOverrideTargetName } from "../package-overrides-Ci8GXouY.js";
10
+ import { i as selectAgentTargetPaths, l as displayRelative, n as detectAgentConflicts, r as detectExistingAgentTargetPaths, s as writeAgentInstructions } from "../agent-D8oxxhVR.js";
11
+ import { n as addMigrationWarning, r as createMigrationReport } from "../report-DLHO30yZ.js";
12
+ import { A as detectVitePlusBootstrapPending, C as removeMigratedOxlintConfigs, Ct as warnMigration, D as mergeViteConfigFiles, E as mergeTsdownConfigFile, F as detectYarnPnpMode, I as createCatalogDependencyResolver, J as detectFramework, K as extractOverrideParentSegments, M as sourceTreeMatches, N as usesWebdriverioProvider, O as retryPreservedOxlintConfigs, P as configureYarnNodeModulesMode, Q as migrateTsupToTsdown, R as detectWorkspace$1, S as prepareOxlintConfigs, St as readPackageJsonIfExists, T as injectLintTypeCheckDefaults, U as minimatch, W as findGitRoot, X as confirmTsupMigration, Y as hasFrameworkShim, Z as detectTsupProject, _ as hasExistingViteHooksPolicy, _t as WEBDRIVERIO_PROVIDER, at as warnPackageLevelPrettier, b as preflightGitHooksSetup, bt as getCommandStartIndex, ct as detectIncompatibleEslintIntegration, d as finalizeCoreMigrationForExistingVitePlus, dt as warnIncompatibleEslintIntegration, et as warnMissingTsupConfig, f as detectNodeVersionManagerFile, ft as warnLegacyEslintConfig, g as detectLegacyGitHooksMigrationCandidate, gt as SHELL_STATE_COMMANDS, ht as RETENTION_SCAN_SKIP_DIRS, i as selectEditor, j as ensureVitePlusBootstrap, k as collectToolchainVersionChanges, l as rewriteStandaloneProject, lt as migrateEslintToOxlint, m as migrateSetupVpVersion, mt as LINT_STAGED_JSON_CONFIG_FILES, nt as detectPrettierProject, o as writeEditorConfigs, ot as confirmEslintMigration, p as migrateNodeVersionManagerFile, pt as warnPackageLevelEslint, q as addFrameworkShim, r as isJsonLikeFile, rt as migratePrettierToOxfmt, s as rewriteMonorepo, st as detectEslintProject, t as detectEditorConflicts, tt as confirmPrettierMigration, u as detectPendingCoreMigration, v as installGitHooks, vt as checkViteVersion, x as shouldSkipStagedMigrationForHooks, xt as isPlainRecord, y as isViteHooksSetupComplete, yt as checkVitestVersion } from "../editor-ClL78D8u.js";
13
+ import { i as runCommandSilently } from "../command-rY79K5NI.js";
14
+ import { t as require_dist } from "../dist-Bs0SObNC.js";
15
+ import { a as hasBaseUrlInTsconfig, i as fixBaseUrlInTsconfig, t as confirmBaseUrlFix } from "../tsconfig-LE_mAwe_.js";
16
+ import { i as WATCH_OPTIONS_WITH_VALUE, n as SCRIPT_EXTENSIONS, t as NODE_OPTIONS_WITH_VALUE } from "../vpx-script-EXgV5QOf.js";
17
+ import { t as unwrapCliParseOutcome } from "../cli-parse-aeUeFDlC.js";
18
+ import { t as ROLLDOWN_COMPAT_RESULT_PREFIX } from "../protocol-CkNmY-Mr.js";
18
19
  import path from "node:path";
19
20
  import { fileURLToPath } from "node:url";
20
- import { analyzeMigrationSource, isDirectoryGitignored, parseMigrateArgs } from "../../binding/index.js";
21
+ import { analyzeMigrationSource, isDirectoryGitignored, parseLiteralCommands, parseMigrateArgs } from "../../binding/index.js";
21
22
  import { styleText } from "node:util";
22
23
  import fs, { existsSync } from "node:fs";
23
- //#region src/migration/vitest-v5/commands.ts
24
- /** Parse only a single literal argv command. Shell expansion, pipelines, and
25
- * compound commands need review; treating them as argv could change behavior. */
26
- function literalArgv(command) {
27
- const tokens = [];
28
- let value = "";
29
- let start = 0;
30
- let quote = "";
31
- let active = false;
32
- for (let i = 0; i < command.length; i++) {
33
- const character = command[i];
34
- if (quote) {
35
- if (character === quote) quote = "";
36
- else if (character === "\\" || quote === "\"" && (character === "$" || character === "`")) return;
37
- else value += character;
38
- } else if (character === "'" || character === "\"") {
39
- if (!active) start = i;
40
- quote = character;
41
- active = true;
42
- } else if (character === "\n" || character === "\r") return;
43
- else if (/\s/.test(character)) {
44
- if (active) tokens.push({
45
- value,
46
- start,
47
- end: i
48
- });
49
- active = false;
50
- value = "";
51
- } else if (/[|&;<>($`\\)]/.test(character)) return;
52
- else {
53
- if (!active) start = i;
54
- value += character;
55
- active = true;
24
+ import os from "node:os";
25
+ // src/migration/migrator/tsx.ts
26
+ const TSX_COMMAND = /(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?(?:\/(?:esm|cjs))?(?=$|[\s;&|)"'`])/i;
27
+ const LOADER_OPTIONS = /* @__PURE__ */ new Set([
28
+ "--import",
29
+ "--loader",
30
+ "--experimental-loader",
31
+ "--require",
32
+ "-r"
33
+ ]);
34
+ const TSX_LOADER = /^tsx(?:\/(?:esm|cjs))?$/;
35
+ const LOADER_SETTINGS = /* @__PURE__ */ new Set([
36
+ "import",
37
+ "loader",
38
+ "require"
39
+ ]);
40
+ const KEEP_EVAL = "`vpx` does not run TypeScript passed to --eval or --print";
41
+ const KEEP_CHECK = "`vpx` does not transform TypeScript for -c or --check";
42
+ const KEEP_REPL = "`vpx` does not start a TypeScript REPL";
43
+ const KEEP_TEST_GLOBS = "`--test` without files finds `.ts` tests with tsx globs; list the test files for `vpx --test`";
44
+ const KEEP_WATCH_PATH = "`--watch-path` has no `vpx` equivalent; use `--include`";
45
+ const KEEP_SHELL = "the command uses shell syntax that vp migrate does not rewrite, such as pipes, variables, or subshells";
46
+ const KEEP_OTHER = "vp migrate does not rewrite this use of tsx";
47
+ const KEEP_TSX_TSCONFIG = "`TSX_TSCONFIG_PATH` selects the tsconfig for tsx only; pass it to `vpx` as `--tsconfig`";
48
+ const KEEP_NODE_OPTIONS = "`NODE_OPTIONS` loads tsx, whose hooks would compete with those of `vpx`; remove it first";
49
+ const KEEP_WATCH_TEST = "`vpx --watch` does not run `--test`";
50
+ const KEEP_OPTIONS = /* @__PURE__ */ new Map([
51
+ ["-e", KEEP_EVAL],
52
+ ["--eval", KEEP_EVAL],
53
+ ["-p", KEEP_EVAL],
54
+ ["--print", KEEP_EVAL],
55
+ ["-pe", KEEP_EVAL],
56
+ ["-c", KEEP_CHECK],
57
+ ["--check", KEEP_CHECK],
58
+ ["-i", KEEP_REPL],
59
+ ["--interactive", KEEP_REPL],
60
+ ["--watch-path", KEEP_WATCH_PATH]
61
+ ]);
62
+ const TSX_ENV_SHEBANG = /^(#!\s*\S*\benv\s+(?:-S\s+)?)(tsx(?=[ \t\r\n]|$)[^\r\n]*)/;
63
+ const SHEBANG_ENTRY = "./shebang.ts";
64
+ const TSX_SHEBANG = /^#![^\n]*[\s/]tsx(?=\s|$)/;
65
+ const TSX_LOADER_FLAG = /(?:(?:--import|--loader|--experimental-loader|--require|-r)[= \t]+|(?<![\w-])(?:import|loader|experimental-loader|require)=)['"]?tsx(?:\/(?:esm|cjs))?(?![\w./-])/;
66
+ const TSX_CODE_REFERENCE = new RegExp([
67
+ /(?:\bfrom|\bimport|\bimport\.meta\.resolve|\brequire(?:\.resolve)?)\s*\(?\s*(?:\/\*[\s\S]*?\*\/\s*)*['"`]tsx(?:\/[^'"`]*)?['"`]/.source,
68
+ /['"`](?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/.source,
69
+ /\(\s*\[?\s*['"`]tsx['"`]\s*[,)\]]/.source,
70
+ TSX_LOADER_FLAG.source,
71
+ /['"`](?:--import|--loader|--experimental-loader|--require|-r)['"`]\s*,\s*['"`]tsx(?:\/(?:esm|cjs))?['"`]/.source
72
+ ].join("|"));
73
+ const TSX_TEXT_REFERENCE = new RegExp([/(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?(?:\/(?:esm|cjs))?['"]?(?:[ \t]+\S|[ \t]*$)/.source, TSX_LOADER_FLAG.source].join("|"), "m");
74
+ const TSX_CONFIG_VALUE = new RegExp([
75
+ /(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?[ \t]+\S/.source,
76
+ TSX_LOADER_FLAG.source,
77
+ /^tsx\//.source
78
+ ].join("|"), "i");
79
+ const IGNORED_FIELDS = /* @__PURE__ */ new Set([
80
+ "bundleDependencies",
81
+ "bundledDependencies",
82
+ "dependencies",
83
+ "description",
84
+ "devDependencies",
85
+ "keywords",
86
+ "name",
87
+ "optionalDependencies",
88
+ "overrides",
89
+ "peerDependencies",
90
+ "peerDependenciesMeta",
91
+ "pnpm",
92
+ "resolutions"
93
+ ]);
94
+ const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
95
+ ...SCRIPT_EXTENSIONS,
96
+ ".astro",
97
+ ".svelte",
98
+ ".vue"
99
+ ]);
100
+ const REFERENCE_EXTENSIONS = /* @__PURE__ */ new Set([
101
+ ...CODE_EXTENSIONS,
102
+ ".json",
103
+ ".jsonc",
104
+ ".toml",
105
+ ".yml",
106
+ ".yaml",
107
+ ".sh",
108
+ ".bash",
109
+ ".zsh",
110
+ ".fish",
111
+ ".ps1",
112
+ ".cmd",
113
+ ".bat"
114
+ ]);
115
+ const LOCKFILES = /* @__PURE__ */ new Set([
116
+ "bun.lock",
117
+ "npm-shrinkwrap.json",
118
+ "package-lock.json",
119
+ "pnpm-lock.yaml",
120
+ "yarn.lock"
121
+ ]);
122
+ const USE_SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
123
+ ...RETENTION_SCAN_SKIP_DIRS,
124
+ ".cache",
125
+ ".next",
126
+ ".nuxt",
127
+ ".svelte-kit",
128
+ ".turbo",
129
+ ".vite",
130
+ "coverage"
131
+ ]);
132
+ const ESM_SYNTAX = /(?:^|;)\s*(?:import(?![\w$])\s*(?![\w$]+\s*=)[\w*{'"]|export(?![\w$])\s*[\w*{])|\bimport\.meta\b/m;
133
+ const COMMONJS_GLOBALS = [
134
+ ["__dirname", /(?<![.\w$])__dirname\b/],
135
+ ["__filename", /(?<![.\w$])__filename\b/],
136
+ ["require", /(?<![.\w$])require\s*(?:\(|\.\s*[\w$])/],
137
+ ["exports", /(?<![.\w$'"`])exports(?![\w$'"`]|\s*:)/],
138
+ ["module", /(?<![.\w$])module\s*(?:\.\s*[\w$]|\[)/]
139
+ ];
140
+ const EXPORT_ASSIGNMENT = /(?:^|;)\s*export\s*=/m;
141
+ const RELATIVE_REQUIRE = /(?<![.\w$])require\s*\(\s*['"`](\.{1,2}\/[^'"`]+?)\.(js|cjs|mjs|jsx)['"`]/g;
142
+ const TYPESCRIPT_EXTENSIONS = {
143
+ js: ["ts", "tsx"],
144
+ cjs: ["cts"],
145
+ mjs: ["mts"],
146
+ jsx: ["tsx"]
147
+ };
148
+ const shellCommand = (previous) => /^-[a-z]*c$/.test(previous);
149
+ const COMMAND_LAUNCHERS = /* @__PURE__ */ new Map([
150
+ ["bash", shellCommand],
151
+ ["concurrently", () => true],
152
+ ["nodemon", (previous) => previous === "--exec" || previous === "-x"],
153
+ ["sh", shellCommand],
154
+ ["zsh", shellCommand]
155
+ ]);
156
+ /**
157
+ * Rewrite the tsx invocations in a command to `vpx`: `tsx a.ts` becomes `vpx a.ts`,
158
+ * `tsx watch a.ts` becomes `vpx --watch a.ts` (`vpx watch` runs the npm package `watch`),
159
+ * and `node --import tsx a.ts` becomes `vpx a.ts`. Package-manager wrappers such as
160
+ * `npx tsx` go away with tsx, since `vpx` is a bin of vite-plus. A form `vpx` cannot run
161
+ * stays as it is, with the reason in `kept`. With `cwd`, the directory the command runs
162
+ * in, the scripts it runs are checked for syntax `vpx` runs differently.
163
+ */
164
+ function rewriteTsxCommand(command, cwd) {
165
+ if (!TSX_COMMAND.test(command)) return {
166
+ command,
167
+ kept: []
168
+ };
169
+ const segments = parseLiteralCommands(command, true);
170
+ if (!segments || /%\w+%/.test(command)) return {
171
+ command,
172
+ kept: [KEEP_SHELL]
173
+ };
174
+ const rewrite = {
175
+ command,
176
+ cwd,
177
+ edits: [],
178
+ kept: []
179
+ };
180
+ for (const tokens of segments) {
181
+ const name = tokens[0].value;
182
+ if (SHELL_STATE_COMMANDS.includes(name) || name === "chdir") {
183
+ rewrite.cwd = void 0;
184
+ continue;
56
185
  }
186
+ rewriteSegment(rewrite, tokens);
57
187
  }
58
- if (quote) return;
59
- if (active) tokens.push({
60
- value,
61
- start,
62
- end: command.length
188
+ let rewritten = command;
189
+ for (const edit of rewrite.edits.toSorted((a, b) => b.start - a.start)) rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end);
190
+ if (!rewrite.kept.length && TSX_COMMAND.test(rewritten)) rewrite.kept.push(KEEP_OTHER);
191
+ return {
192
+ command: rewritten,
193
+ kept: rewrite.kept
194
+ };
195
+ }
196
+ function rewriteSegment(rewrite, tokens) {
197
+ const values = tokens.map(({ value }) => value);
198
+ let first = 0;
199
+ const skipAssignments = () => {
200
+ while (/^[A-Za-z_]\w*=/.test(values[first] ?? "")) first++;
201
+ };
202
+ skipAssignments();
203
+ const shell = values[first] === "cross-env-shell";
204
+ if (values[first] === "cross-env" || shell) {
205
+ first++;
206
+ skipAssignments();
207
+ }
208
+ if (first >= values.length) return;
209
+ const envReason = envKeepReason(values.slice(0, first));
210
+ const start = first + getCommandStartIndex(values.slice(first));
211
+ const launcher = COMMAND_LAUNCHERS.get(values[start]);
212
+ if (values[start] === "tsx") rewriteTsx(rewrite, tokens, first, start, envReason);
213
+ else if (values[start] === "node") rewriteNodeLoader(rewrite, tokens, first, start, envReason);
214
+ else if (shell) for (const token of tokens.slice(start)) rewriteQuotedCommand(rewrite, token, envReason);
215
+ else if (launcher) {
216
+ for (let index = start + 1; index < tokens.length; index++) if (launcher(values[index - 1])) rewriteQuotedCommand(rewrite, tokens[index], envReason);
217
+ }
218
+ }
219
+ /**
220
+ * Why environment assignments keep tsx for the command they apply to, or undefined.
221
+ * Names match in any case, as on Windows.
222
+ */
223
+ function envKeepReason(assignments) {
224
+ if (assignments.some((value) => /^TSX_TSCONFIG_PATH=/i.test(value))) return KEEP_TSX_TSCONFIG;
225
+ if (assignments.some((value) => /^NODE_OPTIONS=/i.test(value) && TSX_COMMAND.test(value.slice(13)))) return KEEP_NODE_OPTIONS;
226
+ }
227
+ /**
228
+ * An option's name without `=value`. Node.js reads `_` as `-` in long option names, as
229
+ * in `--watch_path`, and so does `vpx`.
230
+ */
231
+ function optionName(arg) {
232
+ const name = arg.split("=", 1)[0];
233
+ return name.startsWith("--") ? `--${name.slice(2).replaceAll("_", "-")}` : name;
234
+ }
235
+ /** Remove `tokens[index]` with the space before it. */
236
+ function removal(tokens, index) {
237
+ return {
238
+ start: tokens[index - 1].end,
239
+ end: tokens[index].end,
240
+ text: ""
241
+ };
242
+ }
243
+ /**
244
+ * Why `vpx` would fail on a script that tsx runs, judged from its source. tsx resolves
245
+ * `require('./a.js')` to `a.ts`, and compiles ES module syntax to CommonJS in a `.cts`
246
+ * file and in a package without `"type": "module"`. `vpx` resolves `require()` as
247
+ * Node.js does, rejects ES module syntax in a `.cts` file, and runs the other files as ES
248
+ * modules, so `__dirname`, `__filename`, and `require` are not defined.
249
+ */
250
+ function scriptKeepReason(cwd, script) {
251
+ if (cwd === void 0 || /[*?[{]/.test(script) || !SCRIPT_EXTENSIONS.some((extension) => script.endsWith(extension))) return;
252
+ const file = script.startsWith("~/") ? path.join(os.homedir(), script.slice(2)) : path.resolve(cwd, script);
253
+ let source;
254
+ try {
255
+ source = fs.readFileSync(file, "utf8");
256
+ } catch (error) {
257
+ return error.code === "ENOENT" ? `\`${script}\` does not exist yet, so vp migrate cannot check it` : void 0;
258
+ }
259
+ for (const [, base, extension] of source.matchAll(RELATIVE_REQUIRE)) {
260
+ const resolve = (ext) => path.resolve(path.dirname(file), `${base}.${ext}`);
261
+ const typescript = fs.existsSync(resolve(extension)) ? void 0 : TYPESCRIPT_EXTENSIONS[extension].find((ext) => fs.existsSync(resolve(ext)));
262
+ if (typescript) return `\`${script}\` requires \`${base}.${extension}\`, which only tsx resolves to \`${base}.${typescript}\`; write the TypeScript extension`;
263
+ }
264
+ const code = stripComments(source);
265
+ if (!/\.(?:c?ts|tsx|jsx?)$/.test(script) || !(ESM_SYNTAX.test(source) || ESM_SYNTAX.test(code))) return;
266
+ if (script.endsWith(".cts")) return `\`${script}\` uses import or export, which \`vpx\` does not compile to CommonJS; rename it to .mts`;
267
+ if ((usesCommonJsGlobals(code) || EXPORT_ASSIGNMENT.test(code)) && readNearestPackageJson(path.dirname(file))?.type !== "module") return `\`${script}\` mixes import or export with CommonJS globals such as __dirname, require, or module.exports, and \`vpx\` runs it as an ES module; use import.meta.dirname, import.meta.filename, createRequire(), or export`;
268
+ }
269
+ /** `source` without comments; a string that only looks like one, as in `'http://x'`, stays. */
270
+ function stripComments(source) {
271
+ return source.replaceAll(/("(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`(?:\\.|[^`\\])*`)|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (_, string) => string ?? "");
272
+ }
273
+ /**
274
+ * Whether `code`, without comments, uses a CommonJS global it does not declare or import
275
+ * at module level, as in `const require = createRequire(import.meta.url)` or `import
276
+ * module from 'node:module'`. A declaration inside a function, which an unindented line
277
+ * does not start, covers only that function.
278
+ */
279
+ function usesCommonJsGlobals(code) {
280
+ return COMMONJS_GLOBALS.some(([name, use]) => use.test(code) && !new RegExp(`^(?:(?:export\\s+)?(?:const|let|var|function|class)\\s+${name}\\b|import\\s+(?:\\*\\s+as\\s+)?${name}\\b)`, "m").test(code));
281
+ }
282
+ /**
283
+ * Why `vpx` cannot run what tsx or `node --import tsx` runs: `positionals` are the entry
284
+ * and its arguments, or with `--test`, the test files; `preloads` are local `--require`
285
+ * and `--import` modules, which load first.
286
+ */
287
+ function runKeepReason(cwd, test, positionals, preloads) {
288
+ const [entry] = positionals;
289
+ if (entry === void 0) return test ? KEEP_TEST_GLOBS : KEEP_REPL;
290
+ if (!test && !SCRIPT_EXTENSIONS.some((extension) => entry.endsWith(extension))) return `\`${entry}\` has no file extension, which \`vpx\` needs to run it as a script`;
291
+ const scripts = test ? positionals.filter((value) => !value.startsWith("-")) : [entry];
292
+ for (const script of [...preloads, ...scripts]) {
293
+ const reason = scriptKeepReason(cwd, script);
294
+ if (reason) return reason;
295
+ }
296
+ }
297
+ function rewriteTsx(rewrite, tokens, first, start, keepReason) {
298
+ if (keepReason) {
299
+ rewrite.kept.push(keepReason);
300
+ return;
301
+ }
302
+ let index = start + 1;
303
+ const watch = tokens[index]?.value === "watch";
304
+ if (watch) index++;
305
+ const scanned = scanOptions(tokens, index, true);
306
+ const reason = runReason(rewrite.cwd, tokens, scanned, watch);
307
+ if (reason) {
308
+ rewrite.kept.push(reason);
309
+ return;
310
+ }
311
+ rewrite.edits.push({
312
+ start: tokens[first].start,
313
+ end: tokens[index - 1].end,
314
+ text: watch ? "vpx --watch" : "vpx"
315
+ }, ...scanned.removals);
316
+ }
317
+ function rewriteNodeLoader(rewrite, tokens, first, start, keepReason) {
318
+ const scanned = scanOptions(tokens, start + 1, false);
319
+ if (!scanned.loaders) {
320
+ if (keepReason === KEEP_NODE_OPTIONS) rewrite.kept.push(keepReason);
321
+ return;
322
+ }
323
+ const reason = keepReason ?? runReason(rewrite.cwd, tokens, scanned, false);
324
+ if (reason) {
325
+ rewrite.kept.push(reason);
326
+ return;
327
+ }
328
+ rewrite.edits.push({
329
+ start: tokens[first].start,
330
+ end: tokens[start].end,
331
+ text: "vpx"
332
+ }, ...scanned.removals);
333
+ }
334
+ /** Read the options of `tsx` (`tsx` true) or `node` from `tokens[index]` up to the entry. */
335
+ function scanOptions(tokens, index, tsx) {
336
+ const scanned = {
337
+ entryIndex: tokens.length,
338
+ removals: [],
339
+ loaders: 0,
340
+ preloads: [],
341
+ test: false,
342
+ watch: false
343
+ };
344
+ for (; index < tokens.length; index++) {
345
+ const { value } = tokens[index];
346
+ if (value === "--" || !value.startsWith("-")) {
347
+ scanned.entryIndex = value === "--" ? index + 1 : index;
348
+ break;
349
+ }
350
+ const name = optionName(value);
351
+ const inline = value.includes("=");
352
+ if (tsx && name === "--no-cache") {
353
+ scanned.removals.push(removal(tokens, index));
354
+ continue;
355
+ }
356
+ if (LOADER_OPTIONS.has(name)) {
357
+ const loader = inline ? value.slice(value.indexOf("=") + 1) : tokens[index + 1]?.value;
358
+ if (loader !== void 0 && TSX_LOADER.test(loader)) {
359
+ scanned.removals.push({
360
+ ...removal(tokens, index),
361
+ end: tokens[inline ? index : index + 1].end
362
+ });
363
+ scanned.loaders++;
364
+ } else if (loader !== void 0 && (loader.startsWith(".") || path.isAbsolute(loader))) scanned.preloads.push(loader);
365
+ if (!inline) index++;
366
+ continue;
367
+ }
368
+ scanned.keepReason ??= KEEP_OPTIONS.get(name);
369
+ scanned.test ||= name === "--test";
370
+ scanned.watch ||= name === "--watch";
371
+ if (!inline && (NODE_OPTIONS_WITH_VALUE.has(name) || tsx && (WATCH_OPTIONS_WITH_VALUE.has(name) || name === "--tsconfig"))) index++;
372
+ }
373
+ return scanned;
374
+ }
375
+ /** Why `vpx` cannot run what the scanned options and the scripts after them run. */
376
+ function runReason(cwd, tokens, scanned, watch) {
377
+ if (scanned.keepReason) return scanned.keepReason;
378
+ if ((watch || scanned.watch) && scanned.test) return KEEP_WATCH_TEST;
379
+ return runKeepReason(cwd, scanned.test, tokens.slice(scanned.entryIndex).map(({ value }) => value), scanned.preloads);
380
+ }
381
+ /**
382
+ * Rewrite a command that a launcher runs, given as one quoted argument. `inherited` keeps
383
+ * tsx for it, as when the launcher passes on `TSX_TSCONFIG_PATH`.
384
+ */
385
+ function rewriteQuotedCommand(rewrite, token, inherited) {
386
+ const raw = rewrite.command.slice(token.start, token.end);
387
+ const quote = raw[0];
388
+ if (quote !== "\"" && quote !== "'" || raw.length < 2 || !raw.endsWith(quote)) return;
389
+ const inner = rewriteTsxCommand(token.value, rewrite.cwd);
390
+ rewrite.kept.push(...inner.kept);
391
+ if (inner.command === token.value) return;
392
+ if (inherited) rewrite.kept.push(inherited);
393
+ else if (!inner.command.includes(quote)) rewrite.edits.push({
394
+ start: token.start,
395
+ end: token.end,
396
+ text: `${quote}${inner.command}${quote}`
397
+ });
398
+ }
399
+ /** The commands in a scripts or lint-staged record. */
400
+ function recordCommands(record) {
401
+ if (!isPlainRecord(record)) return [];
402
+ return Object.values(record).flatMap((value) => Array.isArray(value) ? value : [value]).filter((command) => typeof command === "string");
403
+ }
404
+ /** The commands a package.json runs: its scripts and lint-staged tasks. */
405
+ function packageCommands(pkg) {
406
+ return [...recordCommands(pkg.scripts), ...recordCommands(pkg["lint-staged"])];
407
+ }
408
+ function lintStagedJsonFiles(dir) {
409
+ return LINT_STAGED_JSON_CONFIG_FILES.map((name) => path.join(dir, name)).filter((file) => fs.existsSync(file) && isJsonFile(file));
410
+ }
411
+ /**
412
+ * A package that lists tsx outside devDependencies keeps it: its published code or
413
+ * production scripts can need tsx, and `vpx` comes from the vite-plus devDependency.
414
+ */
415
+ function ownsTsxAtRuntime(pkg) {
416
+ return pkg.dependencies?.tsx !== void 0 || pkg.optionalDependencies?.tsx !== void 0 || pkg.peerDependencies?.tsx !== void 0;
417
+ }
418
+ function packageDirs(rootDir, packages) {
419
+ return [rootDir, ...(packages ?? []).map((pkg) => path.join(rootDir, pkg.path))];
420
+ }
421
+ /**
422
+ * Whether a script, lint-staged task, or `#!/usr/bin/env tsx` shebang runs tsx in a form
423
+ * `vpx` replaces.
424
+ */
425
+ function detectTsxProject(rootDir, packages) {
426
+ return packageDirs(rootDir, packages).some((dir) => {
427
+ const pkg = readPackageJsonIfExists(path.join(dir, "package.json"));
428
+ if (!pkg || ownsTsxAtRuntime(pkg)) return false;
429
+ return [...packageCommands(pkg), ...lintStagedJsonFiles(dir).flatMap((file) => recordCommands(readJsonFile(file)))].some((command) => rewriteTsxCommand(command, dir).command !== command) || sourceTreeMatches(dir, (content, file) => {
430
+ const result = rewriteTsxShebang(content, file);
431
+ return result !== void 0 && "content" in result;
432
+ });
433
+ });
434
+ }
435
+ /** Rewrite each command of a scripts or lint-staged record; returns whether one changed. */
436
+ function rewriteCommandRecord(record, label, cwd, report) {
437
+ let edited = false;
438
+ for (const [key, value] of Object.entries(record)) {
439
+ const commands = Array.isArray(value) ? value : [value];
440
+ const rewritten = commands.map((command) => {
441
+ if (typeof command !== "string") return command;
442
+ const result = rewriteTsxCommand(command, cwd);
443
+ for (const reason of result.kept) warnMigration(`Kept tsx in ${label} "${key}": ${reason}.`, report);
444
+ return result.command;
445
+ });
446
+ if (rewritten.some((command, index) => command !== commands[index])) {
447
+ record[key] = Array.isArray(value) ? rewritten : rewritten[0];
448
+ edited = true;
449
+ }
450
+ }
451
+ return edited;
452
+ }
453
+ /**
454
+ * The `vpx` version of a script file's tsx shebang, or why it keeps tsx; undefined when
455
+ * the file has no `#!/usr/bin/env tsx` shebang.
456
+ */
457
+ function rewriteTsxShebang(content, file) {
458
+ const match = TSX_ENV_SHEBANG.exec(content);
459
+ if (!match) return;
460
+ const [line, prefix, command] = match;
461
+ const result = rewriteTsxCommand(`${command.trimEnd()} ${SHEBANG_ENTRY}`);
462
+ const reason = result.kept[0] ?? scriptKeepReason(path.dirname(file), `./${path.basename(file)}`);
463
+ if (reason) return { reason };
464
+ return { content: `${prefix}${result.command.slice(0, -` ${SHEBANG_ENTRY}`.length)}${content.slice(line.length)}` };
465
+ }
466
+ /** Point `#!/usr/bin/env tsx` shebangs of a package's script files at `vpx`. */
467
+ function rewriteTsxShebangs(dir, report) {
468
+ let changed = false;
469
+ sourceTreeMatches(dir, (content, file) => {
470
+ const result = rewriteTsxShebang(content, file);
471
+ if (result && "reason" in result) warnMigration(`Kept tsx in the shebang of ${displayRelative(file)}: ${result.reason}.`, report);
472
+ else if (result) {
473
+ fs.writeFileSync(file, result.content);
474
+ changed = true;
475
+ }
476
+ return false;
63
477
  });
64
- return tokens;
478
+ return changed;
479
+ }
480
+ /**
481
+ * What still runs or imports tsx under `dir`, or undefined. The scan crosses into nested
482
+ * packages, which can resolve this package's tsx, and reads build output, which can
483
+ * still load it.
484
+ */
485
+ function findTsxUse(dir) {
486
+ let found;
487
+ sourceTreeMatches(dir, (content, file) => {
488
+ if (LOCKFILES.has(path.basename(file))) return false;
489
+ const uses = fileUsesTsx(content, file);
490
+ if (uses) found = `${displayRelative(file)} still uses tsx`;
491
+ return uses;
492
+ }, {
493
+ crossPackageBoundaries: true,
494
+ extensions: REFERENCE_EXTENSIONS,
495
+ includeExtensionless: true,
496
+ includePackageReferences: true,
497
+ matchesPackage: (packageDir, pkg) => {
498
+ const field = Object.entries(pkg).find(([key, value]) => !IGNORED_FIELDS.has(key) && jsonUsesTsx(value, [
499
+ "imports",
500
+ "lint-staged",
501
+ "scripts"
502
+ ].includes(key)))?.[0];
503
+ if (field !== void 0) found = `\`${field}\` in ${displayRelative(path.join(packageDir, "package.json"))} still uses tsx`;
504
+ return field !== void 0;
505
+ },
506
+ skipDirs: USE_SCAN_SKIP_DIRS
507
+ });
508
+ return found;
65
509
  }
66
- /** Read command lists without executing a shell. Directory-changing builtins,
67
- * expansions, pipelines, and shell programs remain unresolved. */
510
+ /** Whether `file` runs, imports, or loads tsx. */
511
+ function fileUsesTsx(content, file) {
512
+ if (TSX_SHEBANG.test(content)) return true;
513
+ const name = path.basename(file);
514
+ if (LINT_STAGED_JSON_CONFIG_FILES.some((config) => config === name) && jsonUsesTsx(parse(content), true)) return true;
515
+ const extension = path.extname(file);
516
+ if (extension === ".json" || extension === ".jsonc") return jsonUsesTsx(parse(content), false);
517
+ if (CODE_EXTENSIONS.has(extension)) return TSX_CODE_REFERENCE.test(content);
518
+ return TSX_TEXT_REFERENCE.test(content) || extension === "" && TSX_CODE_REFERENCE.test(content);
519
+ }
520
+ /**
521
+ * Whether a JSON value holds a string that runs or loads tsx: a tsx command with an
522
+ * argument, a loader flag such as `--import=tsx` or `["--import", "tsx"]`, or a subpath
523
+ * such as `tsx/cjs`. A bare `tsx` counts only in `commands` (scripts, lint-staged tasks,
524
+ * and `imports` aliases) and as a loader setting such as Mocha's `"import": "tsx"`;
525
+ * elsewhere it is often a file extension, as in `moduleFileExtensions`.
526
+ */
527
+ function jsonUsesTsx(value, commands) {
528
+ if (typeof value === "string") return commands && TSX_COMMAND.test(value) || TSX_CONFIG_VALUE.test(value);
529
+ if (Array.isArray(value)) return value.some((item, index) => jsonUsesTsx(item, commands) || typeof item === "string" && LOADER_OPTIONS.has(item) && TSX_LOADER.test(String(value[index + 1])));
530
+ return isPlainRecord(value) && Object.entries(value).some(([key, item]) => jsonUsesTsx(item, commands) || LOADER_SETTINGS.has(key) && [item].flat().some((loader) => TSX_LOADER.test(String(loader))));
531
+ }
532
+ /**
533
+ * Move tsx scripts to `vpx`: rewrite package.json scripts, lint-staged tasks, and
534
+ * `#!/usr/bin/env tsx` shebangs, then remove the tsx devDependency from each package
535
+ * where nothing uses tsx anymore.
536
+ */
537
+ function migrateTsxToVpx(rootDir, packages, report) {
538
+ let changed = false;
539
+ const migrated = [];
540
+ for (const dir of packageDirs(rootDir, packages)) {
541
+ const packageJsonPath = path.join(dir, "package.json");
542
+ const pkg = readPackageJsonIfExists(packageJsonPath);
543
+ if (!pkg) continue;
544
+ if (ownsTsxAtRuntime(pkg)) {
545
+ if (packageCommands(pkg).some((c) => TSX_COMMAND.test(c))) warnMigration(`Kept tsx in ${displayRelative(packageJsonPath)}: it is listed in dependencies, optionalDependencies, or peerDependencies there, and \`vpx\` comes from the vite-plus devDependency.`, report);
546
+ continue;
547
+ }
548
+ migrated.push(dir);
549
+ editJsonFile(packageJsonPath, (json) => {
550
+ let edited = false;
551
+ for (const field of ["scripts", "lint-staged"]) {
552
+ const record = json[field];
553
+ if (isPlainRecord(record) && rewriteCommandRecord(record, `${displayRelative(packageJsonPath)} ${field}`, dir, report)) edited = true;
554
+ }
555
+ changed ||= edited;
556
+ return edited ? json : void 0;
557
+ });
558
+ for (const file of lintStagedJsonFiles(dir)) editJsonFile(file, (config) => {
559
+ const edited = rewriteCommandRecord(config, displayRelative(file), dir, report);
560
+ changed ||= edited;
561
+ return edited ? config : void 0;
562
+ });
563
+ if (rewriteTsxShebangs(dir, report)) changed = true;
564
+ }
565
+ let dependencyRemoved = false;
566
+ for (const dir of migrated) {
567
+ const packageJsonPath = path.join(dir, "package.json");
568
+ if (readPackageJsonIfExists(packageJsonPath)?.devDependencies?.tsx === void 0) continue;
569
+ const use = findTsxUse(dir);
570
+ if (use) {
571
+ warnMigration(`Kept the tsx devDependency in ${displayRelative(packageJsonPath)}: ${use}.`, report);
572
+ continue;
573
+ }
574
+ editJsonFile(packageJsonPath, (json) => {
575
+ delete json.devDependencies.tsx;
576
+ return json;
577
+ });
578
+ dependencyRemoved = true;
579
+ }
580
+ if (report && changed) report.tsxMigrated = true;
581
+ return {
582
+ changed: changed || dependencyRemoved,
583
+ dependencyRemoved
584
+ };
585
+ }
586
+ async function confirmTsxMigration(interactive) {
587
+ if (interactive) {
588
+ const confirmed = await confirm({
589
+ message: "Migrate tsx scripts to vpx?\n " + styleText("gray", "vpx is Vite+'s built-in TypeScript script runner: `tsx <file>` becomes `vpx <file>`, `tsx watch` becomes `vpx --watch`, and the tsx dependency is removed once nothing uses it."),
590
+ initialValue: true
591
+ });
592
+ if (isCancel(confirmed)) cancelAndExit();
593
+ return confirmed;
594
+ }
595
+ log$1.info("tsx scripts detected. Auto-migrating to vpx...");
596
+ return true;
597
+ }
598
+ // src/migration/vitest-v5/commands.ts
599
+ /** Read test commands without evaluating shell state or directory changes. */
68
600
  function literalTestCommands(command) {
69
- const commands = [];
70
- let quote = "";
71
- let start = 0;
72
- const append = (end) => {
73
- const tokens = literalArgv(command.slice(start, end));
74
- if (!tokens?.length) return false;
601
+ const commands = parseLiteralCommands(command, true);
602
+ if (!commands) return;
603
+ const result = [];
604
+ for (const tokens of commands) {
75
605
  const values = tokens.map(({ value }) => value);
76
606
  while (/^[A-Za-z_]\w*=/.test(values[0] ?? "")) values.shift();
77
- if (!values.length || [
78
- "cd",
79
- "pushd",
80
- "popd",
81
- ".",
82
- "source",
83
- "eval",
84
- "export",
85
- "set",
86
- "unset"
87
- ].includes(values[0])) return false;
88
- commands.push(values);
89
- return true;
90
- };
91
- for (let index = 0; index < command.length; index++) {
92
- const char = command[index];
93
- if (char === "\\") return;
94
- if (quote) {
95
- if (char === quote) quote = "";
96
- } else if (char === "\"" || char === "'") quote = char;
97
- else if (char === "#") return;
98
- else if (char === ";" || command.slice(index, index + 2) === "&&" || command.slice(index, index + 2) === "||") {
99
- if (!append(index)) return;
100
- if (char !== ";") index++;
101
- start = index + 1;
102
- }
607
+ if (!values.length || SHELL_STATE_COMMANDS.includes(values[0])) return;
608
+ result.push(values);
103
609
  }
104
- return !quote && append(command.length) ? commands : void 0;
610
+ return result;
105
611
  }
106
612
  /** Locate the runner's arguments after a supported package-manager wrapper. */
107
613
  function vitestCommandArgsStart(values) {
108
- let start = 0;
109
- if ([
110
- "pnpm",
111
- "npm",
112
- "yarn",
113
- "bun",
114
- "npx",
115
- "bunx"
116
- ].includes(values[0])) {
117
- start = 1;
118
- if (values[start] === "exec" || values[start] === "x") start++;
119
- if (values[start] === "--") start++;
120
- }
614
+ const start = getCommandStartIndex(values);
121
615
  if (values[start] === "vitest") return start + 1;
122
616
  if (values[start] === "vp" && values[start + 1] === "test") return start + 2;
123
617
  return -1;
124
618
  }
125
619
  function migrateBenchmarkOutput(command) {
126
- const argv = literalArgv(command);
620
+ const argv = parseLiteralCommands(command, false)?.[0];
127
621
  if (!argv) return command;
128
622
  const values = argv.map(({ value }) => value);
129
623
  const start = vitestCommandArgsStart(values);
@@ -182,7 +676,7 @@ function migrateVitestV5Command(file, command, preserveV4, line = 1, reviewV4 =
182
676
  findings
183
677
  };
184
678
  command = migrateBenchmarkOutput(command);
185
- const argv = literalArgv(command);
679
+ const argv = parseLiteralCommands(command, false)?.[0];
186
680
  const values = argv?.map(({ value }) => value);
187
681
  const runner = values ? vitestCommandArgsStart(values) : -1;
188
682
  let removedFlag = /(?:^|\s)(?:--compare|--outputJson)(?:\s|=|$)/.test(command);
@@ -230,8 +724,7 @@ function migrateVitestV5Command(file, command, preserveV4, line = 1, reviewV4 =
230
724
  findings
231
725
  };
232
726
  }
233
- //#endregion
234
- //#region src/migration/vitest-v5/ast.ts
727
+ // src/migration/vitest-v5/ast.ts
235
728
  function isNode(value) {
236
729
  return typeof value === "object" && value !== null && "type" in value && "start" in value;
237
730
  }
@@ -505,7 +998,7 @@ var SourceEditor = class {
505
998
  }
506
999
  }
507
1000
  const propertyIndent = multilinePrefix && object.properties.length ? indent : `${objectIndent} `;
508
- const entries = [...additions.values()].map((property) => printAddedProperty(property, propertyIndent, newline));
1001
+ const entries = Array.from(additions.values(), (property) => printAddedProperty(property, propertyIndent, newline));
509
1002
  const suffix = multiline && !multilinePrefix ? `${newline}${object.properties.length ? propertyIndent : objectIndent}` : "";
510
1003
  this.edit(start, multiline && !multilinePrefix && /^\s*$/.test(prefix) ? first : start, multiline ? `${newline}${propertyIndent}${entries.join(`,${newline}${propertyIndent}`)}${object.properties.length ? "," : ""}${suffix}` : ` ${entries.join(", ")}${object.properties.length ? "," : ""} `);
511
1004
  }
@@ -538,8 +1031,7 @@ var SourceEditor = class {
538
1031
  };
539
1032
  }
540
1033
  };
541
- //#endregion
542
- //#region src/migration/vitest-v5/benchmark-config.ts
1034
+ // src/migration/vitest-v5/benchmark-config.ts
543
1035
  const REPORTERS = /* @__PURE__ */ new Set([
544
1036
  "default",
545
1037
  "verbose",
@@ -607,8 +1099,7 @@ function migrateBenchmarkConfig(editor, test) {
607
1099
  oldJson
608
1100
  ]) if (prop) editor.remove(benchmark, prop);
609
1101
  }
610
- //#endregion
611
- //#region src/migration/vitest-v5/compatibility.ts
1102
+ // src/migration/vitest-v5/compatibility.ts
612
1103
  const settings = {
613
1104
  clearMocks: {
614
1105
  value: "false",
@@ -656,8 +1147,7 @@ function compatibilityProperty(key) {
656
1147
  comment: `// Vitest v4 compatibility: ${reason}\n// ${adoption}\n// ${documentationUrl$1("/guide/vitest-v5#remove-unneeded-compatibility-settings")}\n// https://vitest.dev/guide/migration/#${section}`
657
1148
  };
658
1149
  }
659
- //#endregion
660
- //#region src/migration/vitest-v5/config-object.ts
1150
+ // src/migration/vitest-v5/config-object.ts
661
1151
  const CONFIG_KEYS = /* @__PURE__ */ new Set([
662
1152
  "root",
663
1153
  "test",
@@ -703,8 +1193,7 @@ function resolveConfigObject(editor, node) {
703
1193
  }
704
1194
  return node;
705
1195
  }
706
- //#endregion
707
- //#region src/migration/vitest-v5/config.ts
1196
+ // src/migration/vitest-v5/config.ts
708
1197
  function isTrue(node) {
709
1198
  return isBoolean(node) && node.value;
710
1199
  }
@@ -1001,8 +1490,7 @@ function migrateConfig(file, source, options, mergedConfig, benchmarkOnly = fals
1001
1490
  });
1002
1491
  return editor.finish();
1003
1492
  }
1004
- //#endregion
1005
- //#region src/migration/vitest-v5/documentation.ts
1493
+ // src/migration/vitest-v5/documentation.ts
1006
1494
  const guide = documentationUrl$1("/guide/vitest-v5");
1007
1495
  const upstream = "https://vitest.dev/guide/migration/";
1008
1496
  const sections = {
@@ -1054,8 +1542,7 @@ function vitestV5Documentation(code) {
1054
1542
  if (sections[code]) return `${upstream}#${sections[code]}`;
1055
1543
  return `${guide}#${localSections[code] ?? "resolve-migration-findings"}`;
1056
1544
  }
1057
- //#endregion
1058
- //#region src/migration/vitest-v5/lockfile.ts
1545
+ // src/migration/vitest-v5/lockfile.ts
1059
1546
  var import_dist = require_dist();
1060
1547
  function record$1(value) {
1061
1548
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
@@ -1143,8 +1630,7 @@ function lockedVitestVersion(root, directory, manager, spec) {
1143
1630
  }
1144
1631
  } catch {}
1145
1632
  }
1146
- //#endregion
1147
- //#region src/migration/vitest-v5/storybook.ts
1633
+ // src/migration/vitest-v5/storybook.ts
1148
1634
  const STORYBOOK_PLUGIN = /* @__PURE__ */ new Set(["@storybook/addon-vitest/vitest-plugin"]);
1149
1635
  const NODE_PATH = /* @__PURE__ */ new Set(["node:path", "path"]);
1150
1636
  const NODE_URL = /* @__PURE__ */ new Set(["node:url", "url"]);
@@ -1268,8 +1754,7 @@ function resolveStorybookScope(editor, object, sources, file, cwd) {
1268
1754
  return unknown(`cannot parse ${label}/main`);
1269
1755
  }
1270
1756
  }
1271
- //#endregion
1272
- //#region src/migration/vitest-v5/scopes.ts
1757
+ // src/migration/vitest-v5/scopes.ts
1273
1758
  const DEFAULT_TEST_INCLUDE = ["**/*.{test,spec}.?(c|m)[jt]s?(x)"];
1274
1759
  const DEFAULT_TEST_EXCLUDE = ["**/node_modules/**", "**/.git/**"];
1275
1760
  const DEFAULT_BENCHMARK_INCLUDE = ["**/*.{bench,benchmark}.?(c|m)[jt]s?(x)"];
@@ -1659,7 +2144,7 @@ function resolveVitestV5TestModes(sources, configFiles, preserveV4, entries) {
1659
2144
  if (!found) unknown();
1660
2145
  }
1661
2146
  return {
1662
- modes: new Map([...sources.keys()].map((file) => {
2147
+ modes: new Map(Array.from(sources.keys(), (file) => {
1663
2148
  const matching = [];
1664
2149
  let benchmarkFile = false;
1665
2150
  const hasInSourceTests = sources.get(file)?.includes("import.meta.vitest");
@@ -1696,8 +2181,7 @@ function resolveVitestV5TestModes(sources, configFiles, preserveV4, entries) {
1696
2181
  findings
1697
2182
  };
1698
2183
  }
1699
- //#endregion
1700
- //#region src/migration/vitest-v5/benchmarks.ts
2184
+ // src/migration/vitest-v5/benchmarks.ts
1701
2185
  /** Keep the workload as a separate callback: making it the test callback
1702
2186
  * would measure nothing, and inlining it into a new closure can change scope. */
1703
2187
  function migrateBenchmarks(editor, globals = false) {
@@ -1840,8 +2324,7 @@ function migrateBenchmarks(editor, globals = false) {
1840
2324
  calls
1841
2325
  };
1842
2326
  }
1843
- //#endregion
1844
- //#region src/migration/vitest-v5/source.ts
2327
+ // src/migration/vitest-v5/source.ts
1845
2328
  const RUNNER_SYMBOLS = {
1846
2329
  test: "test",
1847
2330
  it: "it",
@@ -2074,6 +2557,45 @@ function looksLikeConstructorMock(editor, node) {
2074
2557
  }
2075
2558
  return false;
2076
2559
  }
2560
+ function unwrapTypeExpression(node) {
2561
+ while (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression" || node.type === "TSTypeAssertion") node = node.expression;
2562
+ return node;
2563
+ }
2564
+ /** Recognize a local promise consumed by a later statement without crossing
2565
+ * control flow or function boundaries. Ambiguous paths still need review. */
2566
+ function isAssertionBindingHandled(editor, node) {
2567
+ let initializer = node;
2568
+ let declarator = editor.parent(initializer);
2569
+ while (declarator && unwrapTypeExpression(declarator) === node) {
2570
+ initializer = declarator;
2571
+ declarator = editor.parent(initializer);
2572
+ }
2573
+ if (declarator?.type !== "VariableDeclarator" || declarator.init !== initializer || declarator.id.type !== "Identifier") return false;
2574
+ const binding = editor.binding(declarator.id);
2575
+ const declaration = editor.parent(declarator);
2576
+ const block = declaration && editor.parent(declaration);
2577
+ const fn = editor.functionParent(node);
2578
+ if (!binding?.constant || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || block?.type !== "BlockStatement" || !fn || fn.generator) return false;
2579
+ for (let ancestor = editor.parent(block); ancestor && ancestor !== fn; ancestor = editor.parent(ancestor)) if (ancestor.type === "TryStatement") return false;
2580
+ for (const statement of block.body.slice(block.body.indexOf(declaration) + 1)) switch (statement.type) {
2581
+ case "ReturnStatement": {
2582
+ if (!statement.argument) return false;
2583
+ let expression = unwrapTypeExpression(statement.argument);
2584
+ if (expression.type === "AwaitExpression") expression = unwrapTypeExpression(expression.argument);
2585
+ return editor.binding(expression) === binding;
2586
+ }
2587
+ case "ExpressionStatement": {
2588
+ const expression = unwrapTypeExpression(statement.expression);
2589
+ if (expression.type === "AwaitExpression" && editor.binding(unwrapTypeExpression(expression.argument)) === binding) return true;
2590
+ break;
2591
+ }
2592
+ case "VariableDeclaration":
2593
+ case "FunctionDeclaration":
2594
+ case "EmptyStatement": break;
2595
+ default: return false;
2596
+ }
2597
+ return false;
2598
+ }
2077
2599
  function chain(node, isApi) {
2078
2600
  const members = [];
2079
2601
  const calls = [];
@@ -2268,7 +2790,7 @@ function rewriteSource(file, source, options) {
2268
2790
  if (asynchronous && parent?.type === "ExpressionStatement") {
2269
2791
  if (canAwait(node)) editor.edit(node.start, node.start, "await ");
2270
2792
  else editor.report(node, "unawaited-assertion", "Await or return this asynchronous assertion in an async-compatible function.");
2271
- } else if (asynchronous && parent?.type !== "MemberExpression" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement") editor.report(node, "unawaited-assertion", "Check that the result of this asynchronous assertion is awaited or returned.");
2793
+ } else if (asynchronous && parent?.type !== "MemberExpression" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement" && !isAssertionBindingHandled(editor, node)) editor.report(node, "unawaited-assertion", "Check that the result of this asynchronous assertion is awaited or returned.");
2272
2794
  }
2273
2795
  if (importedName(editor, node.callee, RENDER_SOURCES) === "render" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement") {
2274
2796
  if (canAwait(node)) {
@@ -2352,8 +2874,7 @@ function rewriteSource(file, source, options) {
2352
2874
  if (benchmark && result.content === source) editor.report(benchmark, "benchmark-api", "The benchmark rewrite was discarded. Migrate the remaining legacy bench API manually before upgrading to Vitest v5.", "block");
2353
2875
  return result;
2354
2876
  }
2355
- //#endregion
2356
- //#region src/migration/vitest-v5/webdriverio.ts
2877
+ // src/migration/vitest-v5/webdriverio.ts
2357
2878
  const MIN_VERSION = "5.0.0";
2358
2879
  const DEFAULT_SPEC = "^5.0.0";
2359
2880
  const REGISTRY_ALIAS = `npm:${WEBDRIVERIO_PROVIDER}@`;
@@ -2573,8 +3094,7 @@ function migrateWebdriverioDependencies(workspace, sources, inputs) {
2573
3094
  overrideMap(path.join(workspace.rootDir, "pnpm-workspace.yaml"), ["overrides"]);
2574
3095
  return findings;
2575
3096
  }
2576
- //#endregion
2577
- //#region src/migration/migrator/vitest-v5.ts
3097
+ // src/migration/migrator/vitest-v5.ts
2578
3098
  const SKIP_DIRS = /* @__PURE__ */ new Set([
2579
3099
  "node_modules",
2580
3100
  ".git",
@@ -2905,7 +3425,7 @@ function planVitestV5Migration(workspace, originalProjects) {
2905
3425
  const findings = [];
2906
3426
  const directories = [workspace.rootDir, ...(workspace.packages ?? []).map((pkg) => path.resolve(workspace.rootDir, pkg.path))];
2907
3427
  const projects = [];
2908
- const projectSources = new Map([...new Set(directories)].map((directory) => [directory, new Map(filesInProject(directory).map((file) => [file, fs.readFileSync(file, "utf8")]))]));
3428
+ const projectSources = new Map(Array.from(new Set(directories), (directory) => [directory, new Map(filesInProject(directory).map((file) => [file, fs.readFileSync(file, "utf8")]))]));
2909
3429
  const allSources = /* @__PURE__ */ new Map();
2910
3430
  for (const sources of projectSources.values()) for (const [file, source] of sources) allSources.set(file, source);
2911
3431
  const rewrittenSources = new Map(allSources);
@@ -3032,7 +3552,7 @@ function currentProjectConfigs(plan) {
3032
3552
  const filesByProject = new Map(plan.projects.map((project) => [project.directory, filesInProject(project.directory)]));
3033
3553
  const sources = new Map([...filesByProject.values()].flat().map((file) => [file, fs.readFileSync(file, "utf8")]));
3034
3554
  const configs = findVitestV5ConfigFiles(sources, findVitestV5ConfigEntries(sources, filesByProject.keys()).flatMap((entry) => entry.file ? [entry.file] : []));
3035
- return new Map([...filesByProject].map(([directory, files]) => [directory, files.filter((file) => configs.has(file))]));
3555
+ return new Map(Array.from(filesByProject, ([directory, files]) => [directory, files.filter((file) => configs.has(file))]));
3036
3556
  }
3037
3557
  /** Run after other migration steps create or merge configs. Keep the source
3038
3558
  * version and deferred reviews in memory until the final report; no state file. */
@@ -3080,8 +3600,7 @@ function finishVitestV5Migration(plan) {
3080
3600
  const after = planVitestV5Migration(workspaceFromPlan(plan), new Map(plan.projects.map((project) => [project.directory, completedProjects.get(project.directory) ?? project])));
3081
3601
  return [...findings, ...after.findings];
3082
3602
  }
3083
- //#endregion
3084
- //#region src/migration/compat/runner.ts
3603
+ // src/migration/compat/runner.ts
3085
3604
  /**
3086
3605
  * Resolve the isolated compat worker emitted at `migration/compat/worker.js`.
3087
3606
  *
@@ -3137,8 +3656,7 @@ async function checkRolldownCompatibility(rootDir, report) {
3137
3656
  for (const warning of compatibilityResult?.warnings ?? []) addMigrationWarning(report, warning);
3138
3657
  } catch {}
3139
3658
  }
3140
- //#endregion
3141
- //#region src/migration/format.ts
3659
+ // src/migration/format.ts
3142
3660
  const FORMAT_FAILURE_MESSAGE = "Automatic formatting failed. Run `vp fmt` manually after migration.";
3143
3661
  const MAX_FORMAT_ARG_BYTES = process.platform === "win32" ? 24e3 : 1e5;
3144
3662
  function chunkPathsByArgLength(paths) {
@@ -3264,8 +3782,29 @@ async function formatMigratedProject(projectRoot, interactive, report, options =
3264
3782
  addMigrationWarning(report, FORMAT_FAILURE_MESSAGE);
3265
3783
  return false;
3266
3784
  }
3267
- //#endregion
3268
- //#region src/migration/npm-reinstall.ts
3785
+ // src/migration/install-result.ts
3786
+ /**
3787
+ * Reconcile a CommandRunSummary from `runViteInstall` with the migration's
3788
+ * duration counter and exit-code state. `runViteInstall` returns
3789
+ * `{ status: 'failed', exitCode }` without throwing; treating that as a success
3790
+ * (incrementing duration unconditionally) would let the migration claim
3791
+ * "Dependencies installed" while node_modules is desynced from the just-mutated
3792
+ * package.json. This helper centralizes the right handling: credit duration on
3793
+ * success, warn + flip exitCode on failure, stay silent on skip.
3794
+ */
3795
+ function handleInstallResult(installSummary, rootDir, report, options) {
3796
+ if (installSummary.status === "installed") return installSummary.durationMs;
3797
+ if (installSummary.status === "failed") {
3798
+ const exitCode = installSummary.exitCode ?? 1;
3799
+ const message = `Dependency installation failed (exit code ${exitCode}). Run \`vp install\` manually in ${rootDir} to resync node_modules.`;
3800
+ warnMsg(message);
3801
+ report.warnings.push(message);
3802
+ if (options?.propagateExitCode !== false) process.exitCode = exitCode;
3803
+ return 0;
3804
+ }
3805
+ return 0;
3806
+ }
3807
+ // src/migration/npm-reinstall.ts
3269
3808
  const VITE_PLUS_CORE_PACKAGE = "@voidzero-dev/vite-plus-core";
3270
3809
  function isViteInstallPath(packagePath) {
3271
3810
  return packagePath === "node_modules/vite" || packagePath.endsWith("/node_modules/vite");
@@ -3354,18 +3893,20 @@ function prepareNpmViteAliasReinstall(rootDir, projectPaths = [rootDir]) {
3354
3893
  }
3355
3894
  };
3356
3895
  }
3357
- //#endregion
3358
- //#region src/migration/setup-plan.ts
3896
+ // src/migration/setup-plan.ts
3359
3897
  async function collectGitHooksDecision(rootDir, packageManager, options, packages = []) {
3360
- let shouldSetupHooks = await promptGitHooks(options);
3361
- if (shouldSetupHooks) {
3362
- const reason = preflightGitHooksSetup(rootDir, packageManager, packages);
3363
- if (reason) {
3364
- log$1.warn(`⚠ ${reason}`);
3365
- shouldSetupHooks = false;
3366
- }
3898
+ const hasExistingHooks = options.hooks === void 0 && hasExistingViteHooksPolicy(rootDir);
3899
+ if (hasExistingHooks && isViteHooksSetupComplete(rootDir)) {
3900
+ log(`${muted("•")} Git hooks already configured, skipping`);
3901
+ return false;
3367
3902
  }
3368
- return shouldSetupHooks;
3903
+ if (!hasExistingHooks && !await promptGitHooks(options)) return false;
3904
+ const reason = preflightGitHooksSetup(rootDir, packageManager, packages);
3905
+ if (reason) {
3906
+ log$1.warn(`⚠ ${reason}`);
3907
+ return false;
3908
+ }
3909
+ return true;
3369
3910
  }
3370
3911
  async function collectAgentInstructionPlan(rootDir, options) {
3371
3912
  const existingAgentTargetPaths = options.agent !== void 0 || !options.interactive ? void 0 : detectExistingAgentTargetPaths(rootDir);
@@ -3469,13 +4010,9 @@ async function collectMigrationSetupPlan(rootDir, packageManager, options, packa
3469
4010
  ...eslintPlan
3470
4011
  };
3471
4012
  }
3472
- //#endregion
3473
- //#region src/migration/bin.ts
4013
+ // src/migration/bin.ts
3474
4014
  async function confirmNodeVersionFileMigration(interactive, detection) {
3475
- const message = {
3476
- "package.json": "Migrate Volta node version (package.json) to .node-version?",
3477
- ".nvmrc": "Migrate .nvmrc to .node-version?"
3478
- }[detection.file];
4015
+ const message = { "package.json": "Migrate Volta node version (package.json) to .node-version?" }[detection.file];
3479
4016
  if (interactive) {
3480
4017
  const confirmed = await confirm({
3481
4018
  message,
@@ -3609,7 +4146,7 @@ function hasExistingVitePlusMigrationCandidates(workspaceInfo, options) {
3609
4146
  const eslintProject = detectEslintProject(workspaceInfo.rootDir, workspaceInfo.packages);
3610
4147
  const prettierProject = detectPrettierProject(workspaceInfo.rootDir, workspaceInfo.packages);
3611
4148
  const tsupProject = detectTsupProject(workspaceInfo.rootDir, workspaceInfo.packages);
3612
- return hasExplicitExistingVitePlusSetupRequest(options) || detectLegacyGitHooksMigrationCandidate(workspaceInfo.rootDir) || hasBaseUrlInWorkspace(workspaceInfo) || eslintProject.hasDependency || prettierProject.hasDependency || tsupProject.hasDependency || detectNodeVersionManagerFile(workspaceInfo.rootDir) !== void 0 || getFrameworkShimCandidates(workspaceInfo.rootDir, workspaceInfo.packages).length > 0;
4149
+ return hasExplicitExistingVitePlusSetupRequest(options) || detectLegacyGitHooksMigrationCandidate(workspaceInfo.rootDir) || hasBaseUrlInWorkspace(workspaceInfo) || eslintProject.hasDependency || prettierProject.hasDependency || tsupProject.hasDependency || detectTsxProject(workspaceInfo.rootDir, workspaceInfo.packages) || detectNodeVersionManagerFile(workspaceInfo.rootDir) !== void 0 || getFrameworkShimCandidates(workspaceInfo.rootDir, workspaceInfo.packages).length > 0;
3613
4150
  }
3614
4151
  function getExistingVitePlusSetupOptions(options, useFullMigrationDefaults = false) {
3615
4152
  if (useFullMigrationDefaults) return options;
@@ -3629,6 +4166,7 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
3629
4166
  if (prettierProject.hasDependency && prettierProject.configFile) migratePrettier = await confirmPrettierMigration(options.interactive);
3630
4167
  else if (prettierProject.hasDependency) warnPackageLevelPrettier();
3631
4168
  const { migrateTsup, tsupConfigFile } = await collectTsupMigrationDecision(rootDir, options, packages);
4169
+ const migrateTsx = detectTsxProject(rootDir, packages) && await confirmTsxMigration(options.interactive);
3632
4170
  const fixBaseUrl = hasBaseUrlInWorkspace({
3633
4171
  rootDir,
3634
4172
  packages
@@ -3646,33 +4184,13 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
3646
4184
  prettierConfigFile: prettierProject.configFile,
3647
4185
  migrateTsup,
3648
4186
  tsupConfigFile,
4187
+ migrateTsx,
3649
4188
  fixBaseUrl,
3650
4189
  migrateNodeVersionFile,
3651
4190
  nodeVersionDetection,
3652
4191
  frameworkShimFrameworks
3653
4192
  };
3654
4193
  }
3655
- /**
3656
- * Reconcile a CommandRunSummary from `runViteInstall` with the migration's
3657
- * duration counter and exit-code state. `runViteInstall` returns
3658
- * `{ status: 'failed', exitCode }` without throwing; treating that as a success
3659
- * (incrementing duration unconditionally) would let the migration claim
3660
- * "Dependencies installed" while node_modules is desynced from the just-mutated
3661
- * package.json. This helper centralizes the right handling: credit duration on
3662
- * success, warn + flip exitCode on failure, stay silent on skip.
3663
- */
3664
- function handleInstallResult(installSummary, rootDir, report, options) {
3665
- if (installSummary.status === "installed") return installSummary.durationMs;
3666
- if (installSummary.status === "failed") {
3667
- const exitCode = installSummary.exitCode ?? 1;
3668
- const message = `Dependency installation failed (exit code ${exitCode}). Run \`vp install\` manually in ${rootDir} to resync node_modules.`;
3669
- warnMsg(message);
3670
- report.warnings.push(message);
3671
- if (options?.propagateExitCode !== false) process.exitCode = exitCode;
3672
- return 0;
3673
- }
3674
- return 0;
3675
- }
3676
4194
  const FULL_MIGRATION_HINT = `${styleText("gray", "•")} Skipped editor, hooks, and lint setup. Run \`vp migrate --full\` to apply them.`;
3677
4195
  function showMigrationSummary(options) {
3678
4196
  const { projectRoot, packageManager, packageManagerVersion, installDurationMs, finalInstallOk, report, updatedExistingVitePlus, suggestFullMigration } = options;
@@ -3702,6 +4220,7 @@ function showMigrationSummary(options) {
3702
4220
  if (report.eslintMigrated) log(`${styleText("gray", "•")} ESLint rules migrated to Oxlint`);
3703
4221
  if (report.prettierMigrated) log(`${styleText("gray", "•")} Prettier migrated to Oxfmt`);
3704
4222
  if (report.tsupMigrated) log(`${styleText("gray", "•")} tsup config migrated to tsdown (\`vp pack\`)`);
4223
+ if (report.tsxMigrated) log(`${styleText("gray", "•")} tsx scripts migrated to \`vpx\``);
3705
4224
  if (report.nodeVersionFileMigrated) log(`${styleText("gray", "•")} Node version manager file migrated to .node-version`);
3706
4225
  const setupVpFileCount = report.setupVpVersionUpdatedFileCount;
3707
4226
  if (setupVpFileCount > 0) {
@@ -3855,6 +4374,10 @@ async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vi
3855
4374
  cancelAndExit("Complete the tsup migration manually, then re-run `vp migrate`.", 1);
3856
4375
  }
3857
4376
  }
4377
+ if (plan.migrateTsx) {
4378
+ updateMigrationProgress("Migrating tsx scripts");
4379
+ migrateTsxToVpx(workspaceInfo.rootDir, workspaceInfo.packages, report);
4380
+ }
3858
4381
  vitestV5Plan = applyRefreshedVitestV5Migration(vitestV5Plan);
3859
4382
  const skipStagedMigration = shouldSkipStagedMigrationForHooks(workspaceInfo.rootDir, plan.shouldSetupHooks, plan.packageManager, workspaceInfo.packages);
3860
4383
  updateMigrationProgress("Rewriting configs");
@@ -3987,6 +4510,7 @@ async function main() {
3987
4510
  updateMigrationProgress("Rewriting toolchain imports across the workspace");
3988
4511
  const coreMigrationResult = finalizeCoreMigrationForExistingVitePlus(workspaceInfoOptional, true, report, pendingCoreMigration);
3989
4512
  if (coreMigrationResult.dependencies || coreMigrationResult.scripts || coreMigrationResult.tsconfigTypes || coreMigrationResult.imports || coreMigrationResult.tsdownConfig || coreMigrationResult.taskCacheConfig) didMigrate = true;
4513
+ if (retryPreservedOxlintConfigs(workspaceInfoOptional.rootDir, true, report, workspaceInfoOptional.packages)) didMigrate = true;
3990
4514
  clearMigrationProgress();
3991
4515
  const fullSetup = options.full === true;
3992
4516
  const skippedSetupCandidates = !fullSetup && !hasExplicitExistingVitePlusSetupRequest(options) && hasExistingVitePlusMigrationCandidates(workspaceInfoOptional, options);
@@ -4077,6 +4601,15 @@ async function main() {
4077
4601
  tsupMigrated = true;
4078
4602
  }
4079
4603
  }
4604
+ if (fullSetup && detectTsxProject(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages)) {
4605
+ if (options.interactive) clearMigrationProgress();
4606
+ if (await confirmTsxMigration(options.interactive)) {
4607
+ updateMigrationProgress("Migrating tsx scripts");
4608
+ const tsxResult = migrateTsxToVpx(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages, report);
4609
+ didMigrate ||= tsxResult.changed;
4610
+ needsInstall ||= tsxResult.dependencyRemoved;
4611
+ }
4612
+ }
4080
4613
  if (fullSetup) {
4081
4614
  const nodeVersionDetection = detectNodeVersionManagerFile(workspaceInfoOptional.rootDir);
4082
4615
  if (nodeVersionDetection) {
@@ -4102,7 +4635,10 @@ async function main() {
4102
4635
  ].includes(path.basename(file)));
4103
4636
  if (eslintMigrated || prettierMigrated || tsupMigrated) {
4104
4637
  updateMigrationProgress("Rewriting configs");
4105
- mergeViteConfigFiles(workspaceInfoOptional.rootDir, true, report, workspaceInfoOptional.packages);
4638
+ const rootDir = workspaceInfoOptional.rootDir;
4639
+ const oxlintConfigs = workspaceInfoOptional.isMonorepo ? prepareOxlintConfigs([rootDir, ...workspaceInfoOptional.packages.map((pkg) => path.join(rootDir, pkg.path))]) : void 0;
4640
+ mergeViteConfigFiles(rootDir, true, report, workspaceInfoOptional.packages, rootDir, oxlintConfigs);
4641
+ if (oxlintConfigs) removeMigratedOxlintConfigs(oxlintConfigs);
4106
4642
  if (tsupMigrated) {
4107
4643
  mergeTsdownConfigFile(workspaceInfoOptional.rootDir, true, report);
4108
4644
  for (const pkg of workspaceInfoOptional.packages ?? []) mergeTsdownConfigFile(path.join(workspaceInfoOptional.rootDir, pkg.path), true, report);
@@ -4208,5 +4744,4 @@ main().catch((err) => {
4208
4744
  console.error(err);
4209
4745
  process.exit(1);
4210
4746
  });
4211
- //#endregion
4212
4747
  export {};