vite-plus 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding/index.cjs +116 -55
- package/binding/index.d.cts +31 -0
- package/dist/{agent-D8oxxhVR.js → agent-CowvWq3j.js} +2 -2
- package/dist/bin.js +30 -8
- package/dist/config/bin.js +5 -5
- package/dist/{constants-DRUnJ_Dz.js → constants-D5f7inVN.js} +6 -2
- package/dist/create/bin.js +115 -65
- package/dist/{define-config-Cv2P6NT2.cjs → define-config-BGCfAiIo.cjs} +35 -3
- package/dist/{define-config-rPhYIFSn.d.ts → define-config-BrvHRi-G.d.ts} +30 -2
- package/dist/{define-config-ChwJcu5e.js → define-config-XKYhw0xH.js} +30 -5
- package/dist/define-config.cjs +2 -1
- package/dist/define-config.d.ts +2 -2
- package/dist/define-config.js +1 -1
- package/dist/{editor-ClL78D8u.js → editor-BVaNnIey.js} +1266 -884
- package/dist/hooks/bin.js +2 -2
- package/dist/{hooks-xRcJPc-O.js → hooks-w_e2whkQ.js} +40 -21
- package/dist/index.cjs +4 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lint-plugins.cjs +1 -1
- package/dist/migration/bin.js +1302 -311
- package/dist/migration/compat/worker.js +1 -1
- package/dist/{oxlint-plugin-config-BVpqYdcC.js → oxlint-plugin-config-5hVqz4kj.js} +1 -1
- package/dist/oxlint-plugin.js +29 -4
- package/dist/pack-bin.js +2 -2
- package/dist/{package-DzsomcUD.js → package-DLUCo6L_.js} +1 -1
- package/dist/{prompts-GL1vj0wk.js → prompts-DLpJGhfK.js} +3 -3
- package/dist/register.d.ts +1 -0
- package/dist/register.js +12 -0
- package/dist/{report-DLHO30yZ.js → report-BEdpIQvC.js} +2 -0
- package/dist/{resolve-vite-config-Dn5896A4.js → resolve-vite-config-D-JtjGRa.js} +2 -2
- package/dist/{resolve-vite-config-QO6cC0SP.js → resolve-vite-config-D7IUZYKq.js} +1 -1
- package/dist/script-esm-hooks.d.ts +5 -2
- package/dist/script-esm-hooks.js +5 -2
- package/dist/script-preload.cjs +24 -1
- package/dist/script-register.js +7 -2
- package/dist/script-watch.js +135 -41
- package/dist/staged/bin.js +2 -2
- package/dist/sync-versions/bin.js +1 -1
- package/dist/toolchain.js +14 -14
- package/dist/toolchain.json +14 -14
- package/dist/{tsconfig-LE_mAwe_.js → tsconfig-D3DiCiW6.js} +2 -2
- package/dist/{tsconfig-Do6MAG_f.js → tsconfig-DLERIAbF.js} +1 -1
- package/dist/version.js +2 -2
- package/dist/versions.js +7 -7
- package/docs/config/check.md +2 -0
- package/docs/config/fmt.md +1 -1
- package/docs/config/lint.md +1 -1
- package/docs/config/run.md +6 -2
- package/docs/config/staged.md +1 -1
- package/docs/guide/check.md +5 -1
- package/docs/guide/ci.md +2 -2
- package/docs/guide/create.md +1 -1
- package/docs/guide/env.md +1 -1
- package/docs/guide/global-cli.md +13 -9
- package/docs/guide/homebrew.md +127 -0
- package/docs/guide/ide-integration.md +1 -3
- package/docs/guide/implode.md +5 -3
- package/docs/guide/index.md +4 -4
- package/docs/guide/local-cli.md +22 -1
- package/docs/guide/migrate-rules.md +81 -22
- package/docs/guide/migrate.md +9 -9
- package/docs/guide/package-management/add.md +84 -0
- package/docs/guide/package-management/approve-builds.md +48 -0
- package/docs/guide/package-management/audit.md +39 -0
- package/docs/guide/package-management/cache.md +53 -0
- package/docs/guide/package-management/ci.md +28 -0
- package/docs/guide/package-management/config.md +119 -0
- package/docs/guide/package-management/dedupe.md +32 -0
- package/docs/guide/package-management/deprecate.md +39 -0
- package/docs/guide/package-management/dist-tag.md +61 -0
- package/docs/guide/package-management/dlx.md +40 -0
- package/docs/guide/package-management/fund.md +32 -0
- package/docs/guide/package-management/global-packages.md +32 -0
- package/docs/guide/package-management/index.md +78 -0
- package/docs/guide/package-management/info.md +35 -0
- package/docs/guide/package-management/install.md +89 -0
- package/docs/guide/package-management/licenses.md +42 -0
- package/docs/guide/package-management/link.md +29 -0
- package/docs/guide/package-management/list.md +60 -0
- package/docs/guide/package-management/login.md +34 -0
- package/docs/guide/package-management/logout.md +34 -0
- package/docs/guide/package-management/outdated.md +60 -0
- package/docs/guide/package-management/owner.md +79 -0
- package/docs/guide/package-management/pack.md +40 -0
- package/docs/guide/package-management/package-manager-selection.md +60 -0
- package/docs/guide/package-management/patch-commit.md +27 -0
- package/docs/guide/package-management/patch.md +29 -0
- package/docs/guide/package-management/ping.md +32 -0
- package/docs/guide/package-management/pkg.md +88 -0
- package/docs/guide/package-management/prune.md +34 -0
- package/docs/guide/package-management/publish.md +61 -0
- package/docs/guide/package-management/rebuild.md +31 -0
- package/docs/guide/package-management/remove.md +47 -0
- package/docs/guide/package-management/search.md +38 -0
- package/docs/guide/package-management/stage.md +175 -0
- package/docs/guide/package-management/team.md +140 -0
- package/docs/guide/package-management/token.md +87 -0
- package/docs/guide/package-management/unlink.md +33 -0
- package/docs/guide/package-management/update.md +62 -0
- package/docs/guide/package-management/version.md +36 -0
- package/docs/guide/package-management/whoami.md +30 -0
- package/docs/guide/package-management/why.md +55 -0
- package/docs/guide/run.md +23 -1
- package/docs/guide/test.md +1 -1
- package/docs/guide/troubleshooting.md +1 -1
- package/docs/guide/upgrade.md +12 -6
- package/docs/guide/vitest-v5.md +2 -2
- package/docs/guide/vpx.md +45 -5
- package/package.json +40 -34
- package/docs/guide/package-management.md +0 -269
package/dist/migration/bin.js
CHANGED
|
@@ -1,52 +1,36 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
1
|
+
import { C as confirm, D as outro, M as isCancel, O as select, S as normalize$1, T as log, 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-DLpJGhfK.js";
|
|
9
2
|
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-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
3
|
+
import { i as selectAgentTargetPaths, l as displayRelative, n as detectAgentConflicts, r as detectExistingAgentTargetPaths, s as writeAgentInstructions } from "../agent-CowvWq3j.js";
|
|
4
|
+
import { t as unwrapCliParseOutcome } from "../cli-parse-aeUeFDlC.js";
|
|
5
|
+
import { _ as engines, a as SETUP_VP_VERSION, d as VITE_PLUS_OVERRIDE_PACKAGES, f as VITE_PLUS_VERSION, m as isForceOverrideMode } from "../constants-D5f7inVN.js";
|
|
6
|
+
import { A as detectVitePlusBootstrapPending, C as removeMigratedOxlintConfigs, Ct as readPackageJsonIfExists, 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 isPlainRecord, T as injectLintTypeCheckDefaults, U as minimatch, W as findGitRoot, X as confirmTsupMigration, Y as hasFrameworkShim, Z as detectTsupProject, _ as hasExistingViteHooksPolicy, _t as SHELL_STATE_COMMANDS, at as warnPackageLevelPrettier, b as preflightGitHooksSetup, bt as checkVitestVersion, ct as detectIncompatibleEslintIntegration, d as finalizeCoreMigrationForExistingVitePlus, dt as warnIncompatibleEslintIntegration, et as warnMissingTsupConfig, f as detectNodeVersionManagerFile, ft as warnLegacyEslintConfig, g as detectLegacyGitHooksMigrationCandidate, gt as RETENTION_SCAN_SKIP_DIRS, ht as LINT_STAGED_JSON_CONFIG_FILES, i as selectEditor, j as ensureVitePlusBootstrap, k as collectToolchainVersionChanges, l as rewriteStandaloneProject, lt as migrateEslintToOxlint, m as migrateSetupVpVersion, mt as detectMigrationWarnings, 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 WEBDRIVERIO_PROVIDER, wt as warnMigration, x as shouldSkipStagedMigrationForHooks, xt as getCommandStartIndex, y as isViteHooksSetupComplete, yt as checkViteVersion } from "../editor-BVaNnIey.js";
|
|
7
|
+
import { a as hasVitePlusDependency, i as hasPackageManagerDeclaration, n as detectPackageMetadata, o as readNearestPackageJson } from "../package-DLUCo6L_.js";
|
|
8
|
+
import { a as muted, c as warnMsg, i as log$1, o as printHeader, r as formatDuration, t as accent } from "../terminal-BcuPUy-j.js";
|
|
9
|
+
import { a as hasBaseUrlInTsconfig, i as fixBaseUrlInTsconfig, t as confirmBaseUrlFix } from "../tsconfig-D3DiCiW6.js";
|
|
13
10
|
import { i as runCommandSilently } from "../command-rY79K5NI.js";
|
|
11
|
+
import { n as addMigrationWarning, r as createMigrationReport } from "../report-BEdpIQvC.js";
|
|
12
|
+
import { t as ROLLDOWN_COMPAT_RESULT_PREFIX } from "../protocol-CkNmY-Mr.js";
|
|
13
|
+
import { a as writeJsonFile, c as modify, i as readJsonFile, l as parse, n as editJsonFile, o as applyEdits$1, r as isJsonFile, s as findNodeAtLocation, u as parseTree } from "../json-kl84kDyH.js";
|
|
14
|
+
import { _ as isGreaterThanOrEqual, p as compare } from "../comparison-CmVirWIW-UlEf2maz.js";
|
|
14
15
|
import { t as require_dist } from "../dist-Bs0SObNC.js";
|
|
15
|
-
import {
|
|
16
|
+
import { t as documentationUrl$1 } from "../documentation-cv_y0Auv.js";
|
|
17
|
+
import { t as vitest_v5_entry_points_default } from "../vitest-v5-entry-points-NyJsFeux.js";
|
|
16
18
|
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";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import { analyzeMigrationSource, isDirectoryGitignored, parseLiteralCommands, parseMigrateArgs } from "../../binding/index.js";
|
|
21
|
+
import { ResolverFactory, analyzeMigrationSource, isDirectoryGitignored, parseLiteralCommands, parseMigrateArgs, parseSync } from "../../binding/index.js";
|
|
22
22
|
import { styleText } from "node:util";
|
|
23
23
|
import fs, { existsSync } from "node:fs";
|
|
24
24
|
import os from "node:os";
|
|
25
25
|
// src/migration/migrator/tsx.ts
|
|
26
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
27
|
const TSX_LOADER = /^tsx(?:\/(?:esm|cjs))?$/;
|
|
35
|
-
const LOADER_SETTINGS = /* @__PURE__ */ new Set([
|
|
36
|
-
"import",
|
|
37
|
-
"loader",
|
|
38
|
-
"require"
|
|
39
|
-
]);
|
|
40
28
|
const KEEP_EVAL = "`vpx` does not run TypeScript passed to --eval or --print";
|
|
41
29
|
const KEEP_CHECK = "`vpx` does not transform TypeScript for -c or --check";
|
|
42
|
-
const KEEP_REPL = "`vpx` does not start a TypeScript REPL";
|
|
30
|
+
const KEEP_REPL$1 = "`vpx` does not start a TypeScript REPL";
|
|
43
31
|
const KEEP_TEST_GLOBS = "`--test` without files finds `.ts` tests with tsx globs; list the test files for `vpx --test`";
|
|
44
32
|
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
33
|
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
34
|
const KEEP_OPTIONS = /* @__PURE__ */ new Map([
|
|
51
35
|
["-e", KEEP_EVAL],
|
|
52
36
|
["--eval", KEEP_EVAL],
|
|
@@ -55,12 +39,11 @@ const KEEP_OPTIONS = /* @__PURE__ */ new Map([
|
|
|
55
39
|
["-pe", KEEP_EVAL],
|
|
56
40
|
["-c", KEEP_CHECK],
|
|
57
41
|
["--check", KEEP_CHECK],
|
|
58
|
-
["-i", KEEP_REPL],
|
|
59
|
-
["--interactive", KEEP_REPL],
|
|
42
|
+
["-i", KEEP_REPL$1],
|
|
43
|
+
["--interactive", KEEP_REPL$1],
|
|
60
44
|
["--watch-path", KEEP_WATCH_PATH]
|
|
61
45
|
]);
|
|
62
46
|
const TSX_ENV_SHEBANG = /^(#!\s*\S*\benv\s+(?:-S\s+)?)(tsx(?=[ \t\r\n]|$)[^\r\n]*)/;
|
|
63
|
-
const SHEBANG_ENTRY = "./shebang.ts";
|
|
64
47
|
const TSX_SHEBANG = /^#![^\n]*[\s/]tsx(?=\s|$)/;
|
|
65
48
|
const TSX_LOADER_FLAG = /(?:(?:--import|--loader|--experimental-loader|--require|-r)[= \t]+|(?<![\w-])(?:import|loader|experimental-loader|require)=)['"]?tsx(?:\/(?:esm|cjs))?(?![\w./-])/;
|
|
66
49
|
const TSX_CODE_REFERENCE = new RegExp([
|
|
@@ -70,12 +53,209 @@ const TSX_CODE_REFERENCE = new RegExp([
|
|
|
70
53
|
TSX_LOADER_FLAG.source,
|
|
71
54
|
/['"`](?:--import|--loader|--experimental-loader|--require|-r)['"`]\s*,\s*['"`]tsx(?:\/(?:esm|cjs))?['"`]/.source
|
|
72
55
|
].join("|"));
|
|
56
|
+
const JSDOC_TYPE_TAG = /(?:^|\s)@(arg|argument|augments|enum|extends|implements|import|param|prop|property|return|returns|satisfies|template|this|type|typedef)\b\s*/g;
|
|
57
|
+
const JSDOC_PARAMETER_NAME = /^[^\s{[=]+(?:\[\])?(?:\.[^\s{[=]+)*/;
|
|
58
|
+
const JSDOC_TEMPLATE_PARAMETER_NAME = /^(?:(?:in|out|const)\s+)*[$\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*/u;
|
|
59
|
+
const TSX_IMPORT_TYPE = /\bimport\s*\(\s*(['"])tsx(?:\/[^'"]*)?\1\s*(?:,|\))/;
|
|
60
|
+
const TSX_JSDOC_IMPORT = /^(?:[^\s{},]+\s*,\s*)?(?:\{[^}]*\}|\*\s+as\s+[^\s{},]+|[^\s{},]+)\s+from\s*(['"])tsx(?:\/[^'"]*)?\1/;
|
|
73
61
|
const TSX_TEXT_REFERENCE = new RegExp([/(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?(?:\/(?:esm|cjs))?['"]?(?:[ \t]+\S|[ \t]*$)/.source, TSX_LOADER_FLAG.source].join("|"), "m");
|
|
74
62
|
const TSX_CONFIG_VALUE = new RegExp([
|
|
75
63
|
/(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?[ \t]+\S/.source,
|
|
76
64
|
TSX_LOADER_FLAG.source,
|
|
77
65
|
/^tsx\//.source
|
|
78
66
|
].join("|"), "i");
|
|
67
|
+
const ESM_SYNTAX = /(?:^|;)\s*(?:import(?![\w$])\s*(?![\w$]+\s*=)[\w*{'"]|export(?![\w$])\s*[\w*{])|\bimport\.meta\b/m;
|
|
68
|
+
const COMMONJS_GLOBALS = [
|
|
69
|
+
["__dirname", /(?<![.\w$])__dirname\b/],
|
|
70
|
+
["__filename", /(?<![.\w$])__filename\b/],
|
|
71
|
+
["require", /(?<![.\w$])require\s*(?:\(|\.\s*[\w$])/],
|
|
72
|
+
["exports", /(?<![.\w$'"`])exports(?![\w$'"`]|\s*:)/],
|
|
73
|
+
["module", /(?<![.\w$])module\s*(?:\.\s*[\w$]|\[)/]
|
|
74
|
+
];
|
|
75
|
+
const EXPORT_ASSIGNMENT = /(?:^|;)\s*export\s*=/m;
|
|
76
|
+
const RELATIVE_REQUIRE = /(?<![.\w$])require\s*\(\s*['"`](\.{1,2}\/[^'"`]+?)\.(js|cjs|mjs|jsx)['"`]/g;
|
|
77
|
+
const TYPESCRIPT_EXTENSIONS = {
|
|
78
|
+
js: ["ts", "tsx"],
|
|
79
|
+
cjs: ["cts"],
|
|
80
|
+
mjs: ["mts"],
|
|
81
|
+
jsx: ["tsx"]
|
|
82
|
+
};
|
|
83
|
+
const TSX_RUNNER = {
|
|
84
|
+
name: "tsx",
|
|
85
|
+
dependencies: ["tsx"],
|
|
86
|
+
commandPattern: TSX_COMMAND,
|
|
87
|
+
loaderPattern: TSX_LOADER,
|
|
88
|
+
isRegister: (option, value) => option === "--import" && value === "tsx",
|
|
89
|
+
envShebang: TSX_ENV_SHEBANG,
|
|
90
|
+
shebangPattern: TSX_SHEBANG,
|
|
91
|
+
codeReference: TSX_CODE_REFERENCE,
|
|
92
|
+
codeUsesRunner: codeUsesTsx,
|
|
93
|
+
textReference: TSX_TEXT_REFERENCE,
|
|
94
|
+
configValue: TSX_CONFIG_VALUE,
|
|
95
|
+
keepOptions: KEEP_OPTIONS,
|
|
96
|
+
dropOptions: /* @__PURE__ */ new Set(["--no-cache"]),
|
|
97
|
+
optionsWithValue: /* @__PURE__ */ new Set([...WATCH_OPTIONS_WITH_VALUE, "--tsconfig"]),
|
|
98
|
+
testWithoutFilesReason: KEEP_TEST_GLOBS,
|
|
99
|
+
envKeepReason,
|
|
100
|
+
scriptKeepReason: (cwd, script) => typescriptScriptKeepReason(cwd, script, "tsx")
|
|
101
|
+
};
|
|
102
|
+
function detectTsxProject(rootDir, packages) {
|
|
103
|
+
return detectScriptRunnerProject(TSX_RUNNER, rootDir, packages);
|
|
104
|
+
}
|
|
105
|
+
function migrateTsxToVpx(rootDir, packages, report) {
|
|
106
|
+
const result = migrateScriptRunnerToVpx(TSX_RUNNER, rootDir, packages, report);
|
|
107
|
+
if (report && result.scriptsChanged) report.tsxMigrated = true;
|
|
108
|
+
return {
|
|
109
|
+
changed: result.changed,
|
|
110
|
+
dependencyRemoved: result.dependencyRemoved
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Why environment assignments keep tsx for the command they apply to, or undefined.
|
|
115
|
+
* Names match in any case, as on Windows.
|
|
116
|
+
*/
|
|
117
|
+
function envKeepReason(assignments) {
|
|
118
|
+
if (assignments.some((value) => /^NODE_OPTIONS=/i.test(value) && TSX_COMMAND.test(value.slice(13)))) return KEEP_NODE_OPTIONS;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Why `vpx` would fail on a script that tsx runs, judged from its source. tsx resolves
|
|
122
|
+
* `require('./a.js')` to `a.ts`, and compiles ES module syntax to CommonJS in a `.cts`
|
|
123
|
+
* file and in a package without `"type": "module"`. `vpx` resolves `require()` as
|
|
124
|
+
* Node.js does, rejects ES module syntax in a `.cts` file, and runs the other files as ES
|
|
125
|
+
* modules, so `__dirname`, `__filename`, and `require` are not defined.
|
|
126
|
+
*/
|
|
127
|
+
function typescriptScriptKeepReason(cwd, script, runner) {
|
|
128
|
+
if (cwd === void 0 || /[*?[{]/.test(script) || !SCRIPT_EXTENSIONS.some((extension) => script.endsWith(extension))) return;
|
|
129
|
+
const file = script.startsWith("~/") ? path.join(os.homedir(), script.slice(2)) : path.resolve(cwd, script);
|
|
130
|
+
let source;
|
|
131
|
+
try {
|
|
132
|
+
source = fs.readFileSync(file, "utf8");
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return error.code === "ENOENT" ? `\`${script}\` does not exist yet, so vp migrate cannot check it` : void 0;
|
|
135
|
+
}
|
|
136
|
+
if (runner === "tsx") for (const [, base, extension] of source.matchAll(RELATIVE_REQUIRE)) {
|
|
137
|
+
const resolve = (ext) => path.resolve(path.dirname(file), `${base}.${ext}`);
|
|
138
|
+
const typescript = fs.existsSync(resolve(extension)) ? void 0 : TYPESCRIPT_EXTENSIONS[extension].find((ext) => fs.existsSync(resolve(ext)));
|
|
139
|
+
if (typescript) return `\`${script}\` requires \`${base}.${extension}\`, which only ${runner} resolves to \`${base}.${typescript}\`; write the TypeScript extension`;
|
|
140
|
+
}
|
|
141
|
+
const code = stripComments(source);
|
|
142
|
+
if (!/\.(?:c?ts|tsx|jsx?)$/.test(script) || !(ESM_SYNTAX.test(source) || ESM_SYNTAX.test(code))) return;
|
|
143
|
+
if (script.endsWith(".cts")) return `\`${script}\` uses import or export, which \`vpx\` does not compile to CommonJS; rename it to .mts`;
|
|
144
|
+
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`;
|
|
145
|
+
}
|
|
146
|
+
/** `source` without comments; a string that only looks like one, as in `'http://x'`, stays. */
|
|
147
|
+
function stripComments(source) {
|
|
148
|
+
return source.replaceAll(/("(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`(?:\\.|[^`\\])*`)|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (_, string) => string ?? "");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Whether `code`, without comments, uses a CommonJS global it does not declare or import
|
|
152
|
+
* at module level, as in `const require = createRequire(import.meta.url)` or `import
|
|
153
|
+
* module from 'node:module'`. A declaration inside a function, which an unindented line
|
|
154
|
+
* does not start, covers only that function.
|
|
155
|
+
*/
|
|
156
|
+
function usesCommonJsGlobals(code) {
|
|
157
|
+
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));
|
|
158
|
+
}
|
|
159
|
+
/** Whether a JSDoc type annotation or import tag needs tsx's declarations. */
|
|
160
|
+
function jsdocUsesTsx(value) {
|
|
161
|
+
if (!value.startsWith("*")) return false;
|
|
162
|
+
const text = value.replaceAll(/^\s*\* ?/gm, "").replaceAll(/```[\s\S]*?(?:```|$)|~~~[\s\S]*?(?:~~~|$)/g, "");
|
|
163
|
+
for (const match of text.matchAll(JSDOC_TYPE_TAG)) {
|
|
164
|
+
const [tag, name] = match;
|
|
165
|
+
if (jsdocTagUsesTsx(name, text.slice(match.index + tag.length))) return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
function jsdocTagUsesTsx(name, body) {
|
|
170
|
+
if (name === "import") return TSX_JSDOC_IMPORT.test(body);
|
|
171
|
+
if (/^(?:arg|argument|param|prop|property)$/.test(name) && !body.startsWith("{")) {
|
|
172
|
+
const end = body.startsWith("[") ? jsdocDelimitedEnd(body) : JSDOC_PARAMETER_NAME.exec(body)?.[0].length;
|
|
173
|
+
if (end === void 0) return false;
|
|
174
|
+
body = body.slice(end).trimStart();
|
|
175
|
+
}
|
|
176
|
+
if (body.startsWith("{")) {
|
|
177
|
+
const end = jsdocDelimitedEnd(body);
|
|
178
|
+
if (end === void 0) return false;
|
|
179
|
+
if (TSX_IMPORT_TYPE.test(body.slice(0, end))) return true;
|
|
180
|
+
body = body.slice(end).trimStart();
|
|
181
|
+
} else if (/^(?:type|this|enum|augments|extends|implements)$/.test(name)) return jsdocUnbracedTypeUsesTsx(body);
|
|
182
|
+
if (name !== "template") return false;
|
|
183
|
+
while (body) {
|
|
184
|
+
const hasDefault = body.startsWith("[");
|
|
185
|
+
const end = hasDefault ? jsdocDelimitedEnd(body) : JSDOC_TEMPLATE_PARAMETER_NAME.exec(body)?.[0].length;
|
|
186
|
+
if (end === void 0) return false;
|
|
187
|
+
if (hasDefault && TSX_IMPORT_TYPE.test(body.slice(0, end))) return true;
|
|
188
|
+
body = body.slice(end).trimStart();
|
|
189
|
+
if (!body.startsWith(",")) return false;
|
|
190
|
+
body = body.slice(1).trimStart();
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
/** End of a braced type or bracketed name/default, excluding trailing prose. */
|
|
195
|
+
function jsdocDelimitedEnd(body) {
|
|
196
|
+
const open = body[0];
|
|
197
|
+
const close = open === "{" ? "}" : "]";
|
|
198
|
+
let depth = 0;
|
|
199
|
+
for (const token of body.matchAll(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|[{}[\]]/g)) if (token[0] === open) depth++;
|
|
200
|
+
else if (token[0] === close && --depth === 0) return token.index + 1;
|
|
201
|
+
}
|
|
202
|
+
/** Find the complete unbraced type before a tag's description or the next tag. */
|
|
203
|
+
function jsdocUnbracedTypeUsesTsx(body) {
|
|
204
|
+
if (!TSX_IMPORT_TYPE.test(body)) return false;
|
|
205
|
+
const boundaries = Array.from(body.matchAll(/\s+|$/g)).toReversed();
|
|
206
|
+
for (const boundary of boundaries) {
|
|
207
|
+
const type = body.slice(0, boundary.index);
|
|
208
|
+
if (parseSync("jsdoc.ts", `type T = (${type});`).errors.length > 0) continue;
|
|
209
|
+
if (TSX_IMPORT_TYPE.test(type)) return true;
|
|
210
|
+
const rest = body.slice(boundary.index).trimStart();
|
|
211
|
+
return /^(?:[|&<.([?!:]|extends\b|is\b)/.test(rest) && TSX_IMPORT_TYPE.test(rest);
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
/** Whether source code or JSDoc types still need tsx. */
|
|
216
|
+
function codeUsesTsx(content, file) {
|
|
217
|
+
if (!content.includes("tsx")) return false;
|
|
218
|
+
if (!SCRIPT_EXTENSIONS.includes(path.extname(file))) return TSX_CODE_REFERENCE.test(content);
|
|
219
|
+
const parsed = parseSync(file, content);
|
|
220
|
+
if (parsed.errors.length > 0) return TSX_CODE_REFERENCE.test(content);
|
|
221
|
+
const isJavaScript = /\.(?:[cm]?js|jsx)$/.test(file);
|
|
222
|
+
let codeWithoutComments = "";
|
|
223
|
+
let offset = 0;
|
|
224
|
+
for (const comment of parsed.comments) {
|
|
225
|
+
if (isJavaScript && comment.type === "Block" && jsdocUsesTsx(comment.value)) return true;
|
|
226
|
+
codeWithoutComments += `${content.slice(offset, comment.start)} `;
|
|
227
|
+
offset = comment.end;
|
|
228
|
+
}
|
|
229
|
+
return TSX_CODE_REFERENCE.test(codeWithoutComments + content.slice(offset));
|
|
230
|
+
}
|
|
231
|
+
async function confirmTsxMigration(interactive) {
|
|
232
|
+
if (interactive) {
|
|
233
|
+
const confirmed = await confirm({
|
|
234
|
+
message: "Migrate tsx scripts to Vite+?\n " + styleText("gray", "`tsx <file>` becomes `vpx <file>`, `tsx watch` becomes `vpx --watch`, and `node --import tsx` uses `vite-plus/register`. The tsx dependency is removed once nothing uses it."),
|
|
235
|
+
initialValue: true
|
|
236
|
+
});
|
|
237
|
+
if (isCancel(confirmed)) cancelAndExit();
|
|
238
|
+
return confirmed;
|
|
239
|
+
}
|
|
240
|
+
log.info("tsx scripts detected. Auto-migrating to Vite+...");
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
// src/migration/migrator/script-runner.ts
|
|
244
|
+
const LOADER_OPTIONS = /* @__PURE__ */ new Set([
|
|
245
|
+
"--import",
|
|
246
|
+
"--loader",
|
|
247
|
+
"--experimental-loader",
|
|
248
|
+
"--require",
|
|
249
|
+
"-r"
|
|
250
|
+
]);
|
|
251
|
+
const LOADER_SETTINGS = /* @__PURE__ */ new Set([
|
|
252
|
+
"import",
|
|
253
|
+
"loader",
|
|
254
|
+
"require"
|
|
255
|
+
]);
|
|
256
|
+
const KEEP_REPL = "`vpx` does not start a TypeScript REPL";
|
|
257
|
+
const KEEP_SHELL = "the command uses shell syntax that vp migrate does not rewrite, such as pipes, variables, or subshells";
|
|
258
|
+
const KEEP_WATCH_TEST = "`vpx --watch` does not run `--test`";
|
|
79
259
|
const IGNORED_FIELDS = /* @__PURE__ */ new Set([
|
|
80
260
|
"bundleDependencies",
|
|
81
261
|
"bundledDependencies",
|
|
@@ -129,22 +309,6 @@ const USE_SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
129
309
|
".vite",
|
|
130
310
|
"coverage"
|
|
131
311
|
]);
|
|
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
312
|
const shellCommand = (previous) => /^-[a-z]*c$/.test(previous);
|
|
149
313
|
const COMMAND_LAUNCHERS = /* @__PURE__ */ new Map([
|
|
150
314
|
["bash", shellCommand],
|
|
@@ -154,15 +318,11 @@ const COMMAND_LAUNCHERS = /* @__PURE__ */ new Map([
|
|
|
154
318
|
["zsh", shellCommand]
|
|
155
319
|
]);
|
|
156
320
|
/**
|
|
157
|
-
* Rewrite
|
|
158
|
-
*
|
|
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.
|
|
321
|
+
* Rewrite supported runner and Node.js loader invocations while preserving shell
|
|
322
|
+
* syntax and arguments. Unsupported forms remain, with their reasons in `kept`.
|
|
163
323
|
*/
|
|
164
|
-
function
|
|
165
|
-
if (!
|
|
324
|
+
function rewriteScriptRunnerCommand(runner, command, cwd, environment = [], checkingScripts = /* @__PURE__ */ new Set()) {
|
|
325
|
+
if (!runner.commandPattern.test(command) && !runner.envKeepReason(environment)) return {
|
|
166
326
|
command,
|
|
167
327
|
kept: []
|
|
168
328
|
};
|
|
@@ -174,8 +334,10 @@ function rewriteTsxCommand(command, cwd) {
|
|
|
174
334
|
const rewrite = {
|
|
175
335
|
command,
|
|
176
336
|
cwd,
|
|
337
|
+
environment,
|
|
177
338
|
edits: [],
|
|
178
|
-
kept: []
|
|
339
|
+
kept: [],
|
|
340
|
+
checkingScripts
|
|
179
341
|
};
|
|
180
342
|
for (const tokens of segments) {
|
|
181
343
|
const name = tokens[0].value;
|
|
@@ -183,17 +345,16 @@ function rewriteTsxCommand(command, cwd) {
|
|
|
183
345
|
rewrite.cwd = void 0;
|
|
184
346
|
continue;
|
|
185
347
|
}
|
|
186
|
-
rewriteSegment(rewrite, tokens);
|
|
348
|
+
rewriteSegment(runner, rewrite, tokens);
|
|
187
349
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (!rewrite.kept.length && TSX_COMMAND.test(rewritten)) rewrite.kept.push(KEEP_OTHER);
|
|
350
|
+
const rewritten = applyEdits(command, rewrite.edits);
|
|
351
|
+
if (!rewrite.kept.length && runner.commandPattern.test(rewritten)) rewrite.kept.push(`vp migrate does not rewrite this use of ${runner.name}`);
|
|
191
352
|
return {
|
|
192
353
|
command: rewritten,
|
|
193
354
|
kept: rewrite.kept
|
|
194
355
|
};
|
|
195
356
|
}
|
|
196
|
-
function rewriteSegment(rewrite, tokens) {
|
|
357
|
+
function rewriteSegment(runner, rewrite, tokens) {
|
|
197
358
|
const values = tokens.map(({ value }) => value);
|
|
198
359
|
let first = 0;
|
|
199
360
|
const skipAssignments = () => {
|
|
@@ -205,24 +366,160 @@ function rewriteSegment(rewrite, tokens) {
|
|
|
205
366
|
first++;
|
|
206
367
|
skipAssignments();
|
|
207
368
|
}
|
|
369
|
+
let unknownEnvironment = false;
|
|
370
|
+
while (values[first] === "env") {
|
|
371
|
+
first++;
|
|
372
|
+
if (values[first] === "--") first++;
|
|
373
|
+
if (values[first]?.startsWith("-")) {
|
|
374
|
+
unknownEnvironment = true;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
skipAssignments();
|
|
378
|
+
}
|
|
208
379
|
if (first >= values.length) return;
|
|
209
|
-
const
|
|
380
|
+
const assignments = [...rewrite.environment, ...values.slice(0, first)];
|
|
381
|
+
const envReason = runner.envKeepReason(assignments);
|
|
382
|
+
const environment = scanEnvironmentOptions(runner, assignments);
|
|
383
|
+
if (unknownEnvironment) environment.keepReason ??= inheritedOptionsKeepReason(runner);
|
|
384
|
+
environment.keepReason ??= runner.registerEnvKeepReason?.(assignments);
|
|
385
|
+
environment.keepReason ??= environment.preloads.map((script) => runner.scriptKeepReason?.(rewrite.cwd, script)).find((reason) => reason !== void 0);
|
|
386
|
+
const editStart = rewrite.edits.length;
|
|
387
|
+
const keptStart = rewrite.kept.length;
|
|
210
388
|
const start = first + getCommandStartIndex(values.slice(first));
|
|
389
|
+
if ([
|
|
390
|
+
"env",
|
|
391
|
+
"cross-env",
|
|
392
|
+
"cross-env-shell"
|
|
393
|
+
].includes(values[start])) environment.keepReason ??= inheritedOptionsKeepReason(runner);
|
|
211
394
|
const launcher = COMMAND_LAUNCHERS.get(values[start]);
|
|
212
|
-
if (values[start] ===
|
|
213
|
-
else if (values[start] === "node") rewriteNodeLoader(rewrite, tokens, first, start, envReason);
|
|
214
|
-
else if (shell
|
|
215
|
-
|
|
216
|
-
|
|
395
|
+
if (values[start] === runner.name || runner.aliases?.includes(values[start])) rewriteRunner(runner, rewrite, tokens, first, start, envReason ?? environment.keepReason, assignments);
|
|
396
|
+
else if (values[start] === "node") rewriteNodeLoader(runner, rewrite, tokens, first, start, envReason, assignments, environment);
|
|
397
|
+
else if (shell || launcher) {
|
|
398
|
+
const commands = shell ? tokens.slice(start) : tokens.slice(start + 1).filter((_, index) => launcher(values[start + index]));
|
|
399
|
+
let checked = commands.length > 0;
|
|
400
|
+
for (const token of commands) if (!rewriteQuotedCommand(runner, rewrite, token, assignments)) checked = false;
|
|
401
|
+
if (!checked && environment.replacements.length && runner.registerEnvRequiresNode) rewrite.kept.push(`vp migrate only replaces ${runner.name} in NODE_OPTIONS when every launcher target is a quoted literal node command`);
|
|
402
|
+
} else if (values.slice(start).some((value) => runner.commandPattern.test(value))) rewrite.kept.push(`vp migrate does not rewrite this use of ${runner.name}`);
|
|
403
|
+
else if (environment.replacements.length && runner.registerEnvRequiresNode) rewrite.kept.push(`vp migrate only replaces ${runner.name} in NODE_OPTIONS for a literal node command`);
|
|
404
|
+
if (!environment.replacements.length) return;
|
|
405
|
+
const taskStart = [
|
|
406
|
+
"npm",
|
|
407
|
+
"pnpm",
|
|
408
|
+
"yarn",
|
|
409
|
+
"bun"
|
|
410
|
+
].includes(values[start]) ? start : first;
|
|
411
|
+
const reason = environment.keepReason ?? packageTaskKeepReason(runner, rewrite, values.slice(taskStart), assignments);
|
|
412
|
+
if (reason && rewrite.kept.length === keptStart) rewrite.kept.push(reason);
|
|
413
|
+
if (rewrite.kept.length !== keptStart) {
|
|
414
|
+
rewrite.edits.length = editStart;
|
|
415
|
+
return;
|
|
217
416
|
}
|
|
417
|
+
for (const replacement of environment.replacements) {
|
|
418
|
+
const index = replacement.index - rewrite.environment.length;
|
|
419
|
+
if (index >= 0) rewrite.edits.push(literalEdit(rewrite.command, tokens[index], replacement.value));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function inheritedOptionsKeepReason(runner) {
|
|
423
|
+
return `\`NODE_OPTIONS\` loads ${runner.name} into a child command that vp migrate cannot check`;
|
|
424
|
+
}
|
|
425
|
+
/** Check package scripts that inherit a preload without changing their definitions here. */
|
|
426
|
+
function taskKeepReason(runner, rewrite, name, inherited, lifecycle) {
|
|
427
|
+
if (!name || !rewrite.cwd) return inheritedOptionsKeepReason(runner);
|
|
428
|
+
const scripts = readNearestPackageJson(rewrite.cwd)?.scripts;
|
|
429
|
+
if (!isPlainRecord(scripts) || typeof scripts[name] !== "string") return inheritedOptionsKeepReason(runner);
|
|
430
|
+
const names = lifecycle ? [
|
|
431
|
+
`pre${name}`,
|
|
432
|
+
name,
|
|
433
|
+
`post${name}`
|
|
434
|
+
] : [name];
|
|
435
|
+
for (const task of names) {
|
|
436
|
+
const command = scripts[task];
|
|
437
|
+
if (typeof command !== "string") continue;
|
|
438
|
+
const key = `${rewrite.cwd}\0${task}`;
|
|
439
|
+
if (rewrite.checkingScripts.has(key)) return inheritedOptionsKeepReason(runner);
|
|
440
|
+
const checking = /* @__PURE__ */ new Set([...rewrite.checkingScripts, key]);
|
|
441
|
+
const migrated = rewriteScriptRunnerCommand(runner, command, rewrite.cwd, [], checking);
|
|
442
|
+
const reason = migrated.kept[0] ?? rewriteScriptRunnerCommand(runner, migrated.command, rewrite.cwd, inherited, checking).kept[0];
|
|
443
|
+
if (reason) return `script \`${task}\` needs ${runner.name}: ${reason}`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function packageTaskKeepReason(runner, rewrite, values, inherited) {
|
|
447
|
+
const [manager, command] = values;
|
|
448
|
+
if (![
|
|
449
|
+
"npm",
|
|
450
|
+
"pnpm",
|
|
451
|
+
"yarn",
|
|
452
|
+
"bun"
|
|
453
|
+
].includes(manager)) return;
|
|
454
|
+
if ([
|
|
455
|
+
"exec",
|
|
456
|
+
"x",
|
|
457
|
+
"dlx"
|
|
458
|
+
].includes(command)) return;
|
|
459
|
+
const explicitRun = command === "run" || command === "run-script";
|
|
460
|
+
const name = explicitRun ? values[2] : command;
|
|
461
|
+
const expectedLength = explicitRun ? 3 : 2;
|
|
462
|
+
if (values.length !== expectedLength || name?.startsWith("-")) return inheritedOptionsKeepReason(runner);
|
|
463
|
+
return taskKeepReason(runner, rewrite, name, inherited, true);
|
|
464
|
+
}
|
|
465
|
+
function applyEdits(command, pending) {
|
|
466
|
+
let rewritten = command;
|
|
467
|
+
const edits = [];
|
|
468
|
+
for (const edit of pending.toSorted((a, b) => b.start - a.start || b.end - a.end)) {
|
|
469
|
+
const previous = edits.at(-1);
|
|
470
|
+
if (previous?.text === "" && edit.text === "" && edit.end >= previous.start) {
|
|
471
|
+
previous.start = Math.min(previous.start, edit.start);
|
|
472
|
+
previous.end = Math.max(previous.end, edit.end);
|
|
473
|
+
} else edits.push({ ...edit });
|
|
474
|
+
}
|
|
475
|
+
for (const edit of edits) rewritten = rewritten.slice(0, edit.start) + edit.text + rewritten.slice(edit.end);
|
|
476
|
+
return rewritten;
|
|
477
|
+
}
|
|
478
|
+
/** Preserve the surrounding quote style when replacing one complete shell token. */
|
|
479
|
+
function literalEdit(command, token, value) {
|
|
480
|
+
const raw = command.slice(token.start, token.end);
|
|
481
|
+
const prefix = /^([A-Za-z_]\w*=)/.exec(raw)?.[1] ?? "";
|
|
482
|
+
const quote = raw[prefix.length];
|
|
483
|
+
const preserveQuote = prefix !== "" || raw.endsWith(quote);
|
|
484
|
+
let content = value.slice(prefix.length);
|
|
485
|
+
if (quote === "'" && preserveQuote) content = `'${content.replaceAll("'", "'\\''")}'`;
|
|
486
|
+
else if (quote === "\"" && preserveQuote || !/^[\w./=:-]+$/.test(content)) content = `"${content.replaceAll(/[\\"$`]/g, "\\$&")}"`;
|
|
487
|
+
return {
|
|
488
|
+
start: token.start,
|
|
489
|
+
end: token.end,
|
|
490
|
+
text: `${prefix}${content}`
|
|
491
|
+
};
|
|
218
492
|
}
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
493
|
+
/** NODE_OPTIONS uses double quotes and spaces, not shell single quotes or tab separators. */
|
|
494
|
+
function scanEnvironmentOptions(runner, assignments) {
|
|
495
|
+
const result = {
|
|
496
|
+
replacements: [],
|
|
497
|
+
preloads: [],
|
|
498
|
+
args: []
|
|
499
|
+
};
|
|
500
|
+
for (const [index, assignment] of assignments.entries()) {
|
|
501
|
+
if (!/^NODE_OPTIONS=/i.test(assignment)) continue;
|
|
502
|
+
const value = assignment.slice(13);
|
|
503
|
+
const segments = value.includes("'") || value.replaceAll(/"[^"]*"/g, "").includes(" ") ? void 0 : parseLiteralCommands(value, false);
|
|
504
|
+
if (!segments || segments.length !== 1) {
|
|
505
|
+
result.args = void 0;
|
|
506
|
+
if (runner.commandPattern.test(value)) result.keepReason = runner.envKeepReason([assignment]) ?? `NODE_OPTIONS loads ${runner.name} hooks that vp migrate cannot replace`;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
result.args?.push(...segments[0].map(({ value }) => value));
|
|
510
|
+
const scanned = scanOptions(runner, segments[0], 0, false);
|
|
511
|
+
result.preloads.push(...scanned.preloads);
|
|
512
|
+
result.keepReason ??= scanned.registerKeepReason;
|
|
513
|
+
if (scanned.loaders !== scanned.imports.length || scanned.entryIndex !== segments[0].length) {
|
|
514
|
+
if (runner.commandPattern.test(value)) result.keepReason = runner.envKeepReason([assignment]) ?? `NODE_OPTIONS loads ${runner.name} hooks that vp migrate cannot replace`;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (scanned.imports.length) result.replacements.push({
|
|
518
|
+
index,
|
|
519
|
+
value: `${assignment.slice(0, 13)}${applyEdits(value, importEdits(value, scanned))}`
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
return result;
|
|
226
523
|
}
|
|
227
524
|
/**
|
|
228
525
|
* An option's name without `=value`. Node.js reads `_` as `-` in long option names, as
|
|
@@ -235,75 +532,42 @@ function optionName(arg) {
|
|
|
235
532
|
/** Remove `tokens[index]` with the space before it. */
|
|
236
533
|
function removal(tokens, index) {
|
|
237
534
|
return {
|
|
238
|
-
start: tokens[index - 1].
|
|
535
|
+
start: tokens[index - 1]?.end ?? tokens[index].start,
|
|
239
536
|
end: tokens[index].end,
|
|
240
537
|
text: ""
|
|
241
538
|
};
|
|
242
539
|
}
|
|
243
540
|
/**
|
|
244
|
-
* Why `vpx`
|
|
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
|
|
541
|
+
* Why `vpx` cannot run an invocation: `positionals` are the entry
|
|
284
542
|
* and its arguments, or with `--test`, the test files; `preloads` are local `--require`
|
|
285
543
|
* and `--import` modules, which load first.
|
|
286
544
|
*/
|
|
287
|
-
function runKeepReason(cwd, test, positionals, preloads) {
|
|
545
|
+
function runKeepReason(runner, cwd, test, positionals, preloads) {
|
|
288
546
|
const [entry] = positionals;
|
|
289
|
-
if (entry === void 0) return test ?
|
|
547
|
+
if (entry === void 0) return test ? runner.testWithoutFilesReason : KEEP_REPL;
|
|
290
548
|
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
549
|
const scripts = test ? positionals.filter((value) => !value.startsWith("-")) : [entry];
|
|
292
550
|
for (const script of [...preloads, ...scripts]) {
|
|
293
|
-
const reason = scriptKeepReason(cwd, script);
|
|
551
|
+
const reason = runner.scriptKeepReason?.(cwd, script);
|
|
294
552
|
if (reason) return reason;
|
|
295
553
|
}
|
|
296
554
|
}
|
|
297
|
-
function
|
|
555
|
+
function rewriteRunner(runner, rewrite, tokens, first, start, keepReason, environment) {
|
|
298
556
|
if (keepReason) {
|
|
299
557
|
rewrite.kept.push(keepReason);
|
|
300
558
|
return;
|
|
301
559
|
}
|
|
560
|
+
const prepared = prepareRun(runner, rewrite, tokens, start, environment, false);
|
|
561
|
+
if (prepared.keepReason) {
|
|
562
|
+
rewrite.kept.push(prepared.keepReason);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
tokens = prepared.tokens;
|
|
302
566
|
let index = start + 1;
|
|
303
|
-
const watch = tokens[index]?.value === "watch";
|
|
567
|
+
const watch = runner.watchSubcommand !== false && tokens[index]?.value === "watch";
|
|
304
568
|
if (watch) index++;
|
|
305
|
-
const scanned = scanOptions(tokens, index, true);
|
|
306
|
-
const reason = runReason(rewrite.cwd, tokens, scanned, watch);
|
|
569
|
+
const scanned = scanOptions(runner, tokens, index, true);
|
|
570
|
+
const reason = runReason(runner, rewrite.cwd, tokens, scanned, watch);
|
|
307
571
|
if (reason) {
|
|
308
572
|
rewrite.kept.push(reason);
|
|
309
573
|
return;
|
|
@@ -312,15 +576,39 @@ function rewriteTsx(rewrite, tokens, first, start, keepReason) {
|
|
|
312
576
|
start: tokens[first].start,
|
|
313
577
|
end: tokens[index - 1].end,
|
|
314
578
|
text: watch ? "vpx --watch" : "vpx"
|
|
315
|
-
}, ...scanned.removals);
|
|
316
|
-
}
|
|
317
|
-
function rewriteNodeLoader(rewrite, tokens, first, start, keepReason) {
|
|
318
|
-
const
|
|
579
|
+
}, ...prepared.edits, ...scanned.removals);
|
|
580
|
+
}
|
|
581
|
+
function rewriteNodeLoader(runner, rewrite, tokens, first, start, keepReason, environment, environmentOptions) {
|
|
582
|
+
const original = scanOptions(runner, tokens, start + 1, false);
|
|
583
|
+
if (runner.isRegister && original.loaders === original.imports.length && (original.imports.length || environmentOptions.replacements.length)) {
|
|
584
|
+
const prepared = prepareRun(runner, rewrite, tokens, start, environment, true, environmentOptions);
|
|
585
|
+
const scanned = scanOptions(runner, prepared.tokens, start + 1, false);
|
|
586
|
+
const scripts = scanned.inlineCode ? [] : prepared.tokens.slice(scanned.entryIndex).map(({ value }) => value);
|
|
587
|
+
let reason = environmentOptions.keepReason ?? prepared.keepReason ?? scanned.registerKeepReason;
|
|
588
|
+
if (reason === void 0 && environmentOptions.replacements.length && scanned.runScript !== void 0) reason = taskKeepReason(runner, rewrite, scanned.entryIndex < prepared.tokens.length ? void 0 : scanned.runScript, environment, false);
|
|
589
|
+
reason ??= [...scanned.preloads, ...scanned.test ? scripts : scripts.slice(0, 1)].map((script) => runner.scriptKeepReason?.(rewrite.cwd, script)).find((reason) => reason !== void 0);
|
|
590
|
+
if (reason) rewrite.kept.push(reason);
|
|
591
|
+
else rewrite.edits.push(...prepared.edits, ...importEdits(rewrite.command, scanned));
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (!original.loaders) {
|
|
595
|
+
const environmentLoader = environment.some((assignment) => /^NODE_OPTIONS=/i.test(assignment) && runner.commandPattern.test(assignment.slice(13)));
|
|
596
|
+
const reason = original.runnerLoader ? original.keepReason : environmentLoader ? keepReason : void 0;
|
|
597
|
+
if (reason) rewrite.kept.push(reason);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const prepared = prepareRun(runner, rewrite, tokens, start, environment, true);
|
|
601
|
+
if (prepared.keepReason) {
|
|
602
|
+
rewrite.kept.push(prepared.keepReason);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
tokens = prepared.tokens;
|
|
606
|
+
const scanned = scanOptions(runner, tokens, start + 1, false);
|
|
319
607
|
if (!scanned.loaders) {
|
|
320
|
-
if (keepReason
|
|
608
|
+
if (keepReason) rewrite.kept.push(keepReason);
|
|
321
609
|
return;
|
|
322
610
|
}
|
|
323
|
-
const reason = keepReason ?? runReason(rewrite.cwd, tokens, scanned, false);
|
|
611
|
+
const reason = keepReason ?? runReason(runner, rewrite.cwd, tokens, scanned, false);
|
|
324
612
|
if (reason) {
|
|
325
613
|
rewrite.kept.push(reason);
|
|
326
614
|
return;
|
|
@@ -329,14 +617,85 @@ function rewriteNodeLoader(rewrite, tokens, first, start, keepReason) {
|
|
|
329
617
|
start: tokens[first].start,
|
|
330
618
|
end: tokens[start].end,
|
|
331
619
|
text: "vpx"
|
|
332
|
-
}, ...scanned.removals);
|
|
620
|
+
}, ...prepared.edits, ...scanned.removals);
|
|
621
|
+
}
|
|
622
|
+
/** Preserve original source ranges while normalizing runner-specific options. */
|
|
623
|
+
function prepareRun(runner, rewrite, tokens, start, environment, nodeLoader, publicRegister) {
|
|
624
|
+
const prepared = runner.prepareRun?.(tokens.slice(start + 1).map(({ value }) => value), {
|
|
625
|
+
cwd: rewrite.cwd,
|
|
626
|
+
environment,
|
|
627
|
+
nodeLoader,
|
|
628
|
+
executable: tokens[start].value,
|
|
629
|
+
publicRegister: publicRegister !== void 0,
|
|
630
|
+
nodeOptionsArgs: publicRegister?.args
|
|
631
|
+
});
|
|
632
|
+
const edits = [];
|
|
633
|
+
const added = prepared?.prependArgs?.map(quoteArgument) ?? [];
|
|
634
|
+
if (added.some((value) => value === void 0) || prepared?.edits?.some((edit) => edit.value !== void 0 && quoteArgument(edit.value) === void 0)) return {
|
|
635
|
+
tokens,
|
|
636
|
+
edits,
|
|
637
|
+
keepReason: "a rewritten argument needs shell escaping that vp migrate does not support"
|
|
638
|
+
};
|
|
639
|
+
const changes = new Map(prepared?.edits?.map((edit) => [start + 1 + edit.index, edit]));
|
|
640
|
+
const normalized = tokens.flatMap((token, index) => {
|
|
641
|
+
const change = changes.get(index);
|
|
642
|
+
if (!change) return [token];
|
|
643
|
+
if (change.value === void 0) {
|
|
644
|
+
edits.push(removal(tokens, index));
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
const text = quoteArgument(change.value);
|
|
648
|
+
edits.push({
|
|
649
|
+
start: token.start,
|
|
650
|
+
end: token.end,
|
|
651
|
+
text
|
|
652
|
+
});
|
|
653
|
+
return [{
|
|
654
|
+
...token,
|
|
655
|
+
value: change.value
|
|
656
|
+
}];
|
|
657
|
+
});
|
|
658
|
+
if (prepared?.prependArgs?.length) {
|
|
659
|
+
const position = tokens[start].end;
|
|
660
|
+
edits.push({
|
|
661
|
+
start: position,
|
|
662
|
+
end: position,
|
|
663
|
+
text: ` ${added.join(" ")}`
|
|
664
|
+
});
|
|
665
|
+
normalized.splice(start + 1, 0, ...prepared.prependArgs.map((value) => ({
|
|
666
|
+
...tokens[start],
|
|
667
|
+
value,
|
|
668
|
+
start: position,
|
|
669
|
+
end: position
|
|
670
|
+
})));
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
tokens: normalized,
|
|
674
|
+
edits,
|
|
675
|
+
keepReason: prepared?.keepReason
|
|
676
|
+
};
|
|
333
677
|
}
|
|
334
|
-
|
|
335
|
-
|
|
678
|
+
function quoteArgument(value) {
|
|
679
|
+
if (/^[\w./:=@+-]+$/.test(value)) return value;
|
|
680
|
+
return /^[\w./:=@+ -]+$/.test(value) ? `"${value}"` : void 0;
|
|
681
|
+
}
|
|
682
|
+
function importEdits(command, scanned) {
|
|
683
|
+
const edits = [];
|
|
684
|
+
for (const { token, value, option } of scanned.imports) {
|
|
685
|
+
if (option) edits.push(literalEdit(command, option, "--import"));
|
|
686
|
+
edits.push(literalEdit(command, token, value));
|
|
687
|
+
}
|
|
688
|
+
return edits;
|
|
689
|
+
}
|
|
690
|
+
/** Read options from the executable to the entry file. */
|
|
691
|
+
function scanOptions(runner, tokens, index, directRunner) {
|
|
336
692
|
const scanned = {
|
|
337
693
|
entryIndex: tokens.length,
|
|
338
694
|
removals: [],
|
|
339
695
|
loaders: 0,
|
|
696
|
+
runnerLoader: false,
|
|
697
|
+
imports: [],
|
|
698
|
+
inlineCode: false,
|
|
340
699
|
preloads: [],
|
|
341
700
|
test: false,
|
|
342
701
|
watch: false
|
|
@@ -349,52 +708,82 @@ function scanOptions(tokens, index, tsx) {
|
|
|
349
708
|
}
|
|
350
709
|
const name = optionName(value);
|
|
351
710
|
const inline = value.includes("=");
|
|
352
|
-
|
|
711
|
+
const argument = inline ? value.slice(value.indexOf("=") + 1) : tokens[index + 1]?.value;
|
|
712
|
+
if (directRunner && runner.dropOptions?.has(name)) {
|
|
353
713
|
scanned.removals.push(removal(tokens, index));
|
|
354
714
|
continue;
|
|
355
715
|
}
|
|
356
716
|
if (LOADER_OPTIONS.has(name)) {
|
|
357
|
-
const loader =
|
|
358
|
-
if (loader !== void 0 &&
|
|
717
|
+
const loader = argument;
|
|
718
|
+
if (loader !== void 0 && runner.loaderPattern.test(loader)) {
|
|
719
|
+
scanned.runnerLoader = true;
|
|
720
|
+
if (runner.isLoader && !runner.isLoader(name, loader)) {
|
|
721
|
+
scanned.keepReason ??= `vp migrate does not rewrite ${name} ${loader}`;
|
|
722
|
+
scanned.registerKeepReason ??= scanned.keepReason;
|
|
723
|
+
if (!inline) index++;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
359
726
|
scanned.removals.push({
|
|
360
727
|
...removal(tokens, index),
|
|
361
728
|
end: tokens[inline ? index : index + 1].end
|
|
362
729
|
});
|
|
363
730
|
scanned.loaders++;
|
|
364
|
-
|
|
731
|
+
if (runner.isRegister?.(name, loader)) scanned.imports.push({
|
|
732
|
+
token: tokens[inline ? index : index + 1],
|
|
733
|
+
value: inline ? "--import=vite-plus/register" : "vite-plus/register",
|
|
734
|
+
option: !inline && name !== "--import" ? tokens[index] : void 0
|
|
735
|
+
});
|
|
736
|
+
} else if (loader !== void 0) {
|
|
737
|
+
const reason = runner.registerPreloadKeepReason?.(name, loader);
|
|
738
|
+
scanned.registerKeepReason ??= reason;
|
|
739
|
+
if (directRunner) scanned.keepReason ??= reason;
|
|
740
|
+
if (loader.startsWith(".") || path.isAbsolute(loader)) scanned.preloads.push(loader);
|
|
741
|
+
}
|
|
365
742
|
if (!inline) index++;
|
|
366
743
|
continue;
|
|
367
744
|
}
|
|
368
|
-
scanned.keepReason ??=
|
|
745
|
+
scanned.keepReason ??= runner.keepOptions.get(name);
|
|
746
|
+
scanned.registerKeepReason ??= runner.registerKeepOptions?.get(name);
|
|
747
|
+
if (runner.registerInlineCodeKeepReason) {
|
|
748
|
+
const code = [
|
|
749
|
+
"-e",
|
|
750
|
+
"-p",
|
|
751
|
+
"-pe",
|
|
752
|
+
"--eval",
|
|
753
|
+
"--print"
|
|
754
|
+
].includes(name) ? argument : /^-(?:pe|ep|e|p)(.+)$/.exec(value)?.[1];
|
|
755
|
+
if (code !== void 0) scanned.registerKeepReason ??= runner.registerInlineCodeKeepReason(code);
|
|
756
|
+
}
|
|
369
757
|
scanned.test ||= name === "--test";
|
|
370
758
|
scanned.watch ||= name === "--watch";
|
|
371
|
-
|
|
759
|
+
scanned.inlineCode ||= [
|
|
760
|
+
"-e",
|
|
761
|
+
"-p",
|
|
762
|
+
"-pe",
|
|
763
|
+
"--eval",
|
|
764
|
+
"--print",
|
|
765
|
+
"--run"
|
|
766
|
+
].includes(name);
|
|
767
|
+
if (name === "--run") scanned.runScript = argument ?? "";
|
|
768
|
+
if (!inline && (NODE_OPTIONS_WITH_VALUE.has(name) || directRunner && runner.optionsWithValue?.has(name))) index++;
|
|
372
769
|
}
|
|
373
770
|
return scanned;
|
|
374
771
|
}
|
|
375
772
|
/** Why `vpx` cannot run what the scanned options and the scripts after them run. */
|
|
376
|
-
function runReason(cwd, tokens, scanned, watch) {
|
|
773
|
+
function runReason(runner, cwd, tokens, scanned, watch) {
|
|
377
774
|
if (scanned.keepReason) return scanned.keepReason;
|
|
378
775
|
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);
|
|
776
|
+
return runKeepReason(runner, cwd, scanned.test, tokens.slice(scanned.entryIndex).map(({ value }) => value), scanned.preloads);
|
|
380
777
|
}
|
|
381
|
-
/**
|
|
382
|
-
|
|
383
|
-
* tsx for it, as when the launcher passes on `TSX_TSCONFIG_PATH`.
|
|
384
|
-
*/
|
|
385
|
-
function rewriteQuotedCommand(rewrite, token, inherited) {
|
|
778
|
+
/** Rewrite a quoted launcher command with the environment it inherits. */
|
|
779
|
+
function rewriteQuotedCommand(runner, rewrite, token, environment) {
|
|
386
780
|
const raw = rewrite.command.slice(token.start, token.end);
|
|
387
781
|
const quote = raw[0];
|
|
388
|
-
if (quote !== "\"" && quote !== "'" || raw.length < 2 || !raw.endsWith(quote)) return;
|
|
389
|
-
const inner =
|
|
782
|
+
if (quote !== "\"" && quote !== "'" || raw.length < 2 || !raw.endsWith(quote)) return false;
|
|
783
|
+
const inner = rewriteScriptRunnerCommand(runner, token.value, rewrite.cwd, environment, rewrite.checkingScripts);
|
|
390
784
|
rewrite.kept.push(...inner.kept);
|
|
391
|
-
if (inner.command
|
|
392
|
-
|
|
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
|
-
});
|
|
785
|
+
if (inner.command !== token.value) rewrite.edits.push(literalEdit(rewrite.command, token, inner.command));
|
|
786
|
+
return token.value.trim() !== "";
|
|
398
787
|
}
|
|
399
788
|
/** The commands in a scripts or lint-staged record. */
|
|
400
789
|
function recordCommands(record) {
|
|
@@ -409,38 +798,37 @@ function lintStagedJsonFiles(dir) {
|
|
|
409
798
|
return LINT_STAGED_JSON_CONFIG_FILES.map((name) => path.join(dir, name)).filter((file) => fs.existsSync(file) && isJsonFile(file));
|
|
410
799
|
}
|
|
411
800
|
/**
|
|
412
|
-
* A package that lists
|
|
413
|
-
* production scripts can need
|
|
801
|
+
* A package that lists a runner outside devDependencies keeps it: published code or
|
|
802
|
+
* production scripts can need it, and `vpx` comes from the vite-plus devDependency.
|
|
414
803
|
*/
|
|
415
|
-
function
|
|
416
|
-
return pkg.dependencies?.
|
|
804
|
+
function ownsRunnerAtRuntime(runner, pkg) {
|
|
805
|
+
return runner.dependencies.some((dependency) => pkg.dependencies?.[dependency] !== void 0 || pkg.optionalDependencies?.[dependency] !== void 0 || pkg.peerDependencies?.[dependency] !== void 0);
|
|
417
806
|
}
|
|
418
807
|
function packageDirs(rootDir, packages) {
|
|
419
808
|
return [rootDir, ...(packages ?? []).map((pkg) => path.join(rootDir, pkg.path))];
|
|
420
809
|
}
|
|
421
810
|
/**
|
|
422
|
-
*
|
|
423
|
-
* `vpx` replaces.
|
|
811
|
+
* Detect supported invocations, or retained uses when the profile requests warnings.
|
|
424
812
|
*/
|
|
425
|
-
function
|
|
813
|
+
function detectScriptRunnerProject(runner, rootDir, packages) {
|
|
426
814
|
return packageDirs(rootDir, packages).some((dir) => {
|
|
427
815
|
const pkg = readPackageJsonIfExists(path.join(dir, "package.json"));
|
|
428
|
-
if (!pkg ||
|
|
429
|
-
return [...packageCommands(pkg), ...lintStagedJsonFiles(dir).flatMap((file) => recordCommands(readJsonFile(file)))].some((command) =>
|
|
430
|
-
const result =
|
|
816
|
+
if (!pkg || !runner.detectKeptUses && ownsRunnerAtRuntime(runner, pkg)) return false;
|
|
817
|
+
return [...packageCommands(pkg), ...lintStagedJsonFiles(dir).flatMap((file) => recordCommands(readJsonFile(file)))].some((command) => runner.detectKeptUses ? runner.commandPattern.test(command) : rewriteScriptRunnerCommand(runner, command, dir).command !== command) || runner.detectKeptUses && findRunnerUse(runner, dir, ownsRunnerAtRuntime(runner, pkg) || runner.dependencies.some((dependency) => pkg.devDependencies?.[dependency] !== void 0)) !== void 0 || sourceTreeMatches(dir, (content, file) => {
|
|
818
|
+
const result = rewriteRunnerShebang(runner, content, file, dir);
|
|
431
819
|
return result !== void 0 && "content" in result;
|
|
432
|
-
});
|
|
820
|
+
}, { includeExtensionless: runner.includeExtensionlessShebangs });
|
|
433
821
|
});
|
|
434
822
|
}
|
|
435
823
|
/** Rewrite each command of a scripts or lint-staged record; returns whether one changed. */
|
|
436
|
-
function rewriteCommandRecord(record, label, cwd, report) {
|
|
824
|
+
function rewriteCommandRecord(runner, record, label, cwd, report) {
|
|
437
825
|
let edited = false;
|
|
438
826
|
for (const [key, value] of Object.entries(record)) {
|
|
439
827
|
const commands = Array.isArray(value) ? value : [value];
|
|
440
828
|
const rewritten = commands.map((command) => {
|
|
441
829
|
if (typeof command !== "string") return command;
|
|
442
|
-
const result =
|
|
443
|
-
for (const reason of result.kept) warnMigration(`Kept
|
|
830
|
+
const result = rewriteScriptRunnerCommand(runner, command, cwd);
|
|
831
|
+
for (const reason of result.kept) warnMigration(`Kept ${runner.name} in ${label} "${key}": ${reason}.`, report);
|
|
444
832
|
return result.command;
|
|
445
833
|
});
|
|
446
834
|
if (rewritten.some((command, index) => command !== commands[index])) {
|
|
@@ -451,98 +839,144 @@ function rewriteCommandRecord(record, label, cwd, report) {
|
|
|
451
839
|
return edited;
|
|
452
840
|
}
|
|
453
841
|
/**
|
|
454
|
-
* The
|
|
455
|
-
* the file has no `#!/usr/bin/env tsx` shebang.
|
|
842
|
+
* The migrated runner or Node.js preload shebang, or its retention reason.
|
|
456
843
|
*/
|
|
457
|
-
function
|
|
458
|
-
const match =
|
|
844
|
+
function rewriteRunnerShebang(runner, content, file, packageDir) {
|
|
845
|
+
const match = runner.envShebang.exec(content);
|
|
459
846
|
if (!match) return;
|
|
460
847
|
const [line, prefix, command] = match;
|
|
461
|
-
const
|
|
462
|
-
|
|
848
|
+
const entry = quoteArgument(`./${path.basename(file)}`);
|
|
849
|
+
if (entry === void 0) return { reason: "the script path needs shell escaping that vp migrate does not support" };
|
|
850
|
+
const shebangRunner = runner.prepareRun ? {
|
|
851
|
+
...runner,
|
|
852
|
+
prepareRun: (args, context) => runner.prepareRun(args, {
|
|
853
|
+
...context,
|
|
854
|
+
cwd: path.dirname(file),
|
|
855
|
+
shebang: true
|
|
856
|
+
})
|
|
857
|
+
} : runner;
|
|
858
|
+
const original = `${command} ${entry}`;
|
|
859
|
+
const result = rewriteScriptRunnerCommand(shebangRunner, original);
|
|
860
|
+
if (result.command === original && !result.kept.length) return;
|
|
861
|
+
const tokens = parseLiteralCommands(command, true)?.[0];
|
|
862
|
+
const executable = tokens?.[0]?.value;
|
|
863
|
+
const directRunner = executable === runner.name || executable !== void 0 && runner.aliases?.includes(executable) === true;
|
|
864
|
+
const optionsStart = directRunner && runner.watchSubcommand !== false && tokens?.[1]?.value === "watch" ? 2 : 1;
|
|
865
|
+
const preloads = tokens && (executable === "node" || directRunner) ? scanOptions(runner, tokens, optionsStart, directRunner).preloads : [];
|
|
866
|
+
const reason = result.kept[0] ?? preloads.map((script) => runner.scriptKeepReason?.(packageDir, script)).find((reason) => reason !== void 0) ?? runner.scriptKeepReason?.(path.dirname(file), `./${path.basename(file)}`);
|
|
463
867
|
if (reason) return { reason };
|
|
464
|
-
|
|
868
|
+
const rewritten = result.command.slice(0, -` ${entry}`.length);
|
|
869
|
+
if (!path.extname(file) && /^vpx(?:\s|$)/.test(rewritten)) return;
|
|
870
|
+
return { content: `${prefix}${rewritten}${content.slice(line.length)}` };
|
|
465
871
|
}
|
|
466
|
-
/**
|
|
467
|
-
function
|
|
872
|
+
/** Rewrite matching runner and Node.js preload shebangs in a package's scripts. */
|
|
873
|
+
function rewriteRunnerShebangs(runner, dir, report) {
|
|
468
874
|
let changed = false;
|
|
469
875
|
sourceTreeMatches(dir, (content, file) => {
|
|
470
|
-
const result =
|
|
471
|
-
if (result && "reason" in result) warnMigration(`Kept
|
|
876
|
+
const result = rewriteRunnerShebang(runner, content, file, dir);
|
|
877
|
+
if (result && "reason" in result) warnMigration(`Kept ${runner.name} in the shebang of ${displayRelative(file)}: ${result.reason}.`, report);
|
|
472
878
|
else if (result) {
|
|
473
879
|
fs.writeFileSync(file, result.content);
|
|
474
880
|
changed = true;
|
|
475
881
|
}
|
|
476
882
|
return false;
|
|
477
|
-
});
|
|
883
|
+
}, { includeExtensionless: runner.includeExtensionlessShebangs });
|
|
478
884
|
return changed;
|
|
479
885
|
}
|
|
480
886
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
* still load it.
|
|
887
|
+
* Find a remaining use, including nested packages and build output that can resolve
|
|
888
|
+
* the package's runner dependencies.
|
|
484
889
|
*/
|
|
485
|
-
function
|
|
890
|
+
function findRunnerUse(runner, dir, retainUnverified = true) {
|
|
486
891
|
let found;
|
|
487
892
|
sourceTreeMatches(dir, (content, file) => {
|
|
488
893
|
if (LOCKFILES.has(path.basename(file))) return false;
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
return
|
|
894
|
+
if (fileUsesRunner(runner, content, file)) found = `${displayRelative(file)} still uses ${runner.name}`;
|
|
895
|
+
else if (path.basename(file) !== "package.json") found = environmentFileUse(runner, content, void 0, retainUnverified);
|
|
896
|
+
return found !== void 0;
|
|
492
897
|
}, {
|
|
493
898
|
crossPackageBoundaries: true,
|
|
494
899
|
extensions: REFERENCE_EXTENSIONS,
|
|
900
|
+
fileNamePattern: runner.referenceFileNamePattern,
|
|
495
901
|
includeExtensionless: true,
|
|
496
902
|
includePackageReferences: true,
|
|
497
903
|
matchesPackage: (packageDir, pkg) => {
|
|
498
|
-
const field = Object.entries(pkg).find(([key, value]) => !IGNORED_FIELDS.has(key) &&
|
|
904
|
+
const field = Object.entries(pkg).find(([key, value]) => !IGNORED_FIELDS.has(key) && jsonUsesRunner(runner, value, [
|
|
499
905
|
"imports",
|
|
500
906
|
"lint-staged",
|
|
501
907
|
"scripts"
|
|
502
908
|
].includes(key)))?.[0];
|
|
503
|
-
if (field !== void 0) found = `\`${field}\` in ${displayRelative(path.join(packageDir, "package.json"))} still uses
|
|
504
|
-
|
|
909
|
+
if (field !== void 0) found = `\`${field}\` in ${displayRelative(path.join(packageDir, "package.json"))} still uses ${runner.name}`;
|
|
910
|
+
else {
|
|
911
|
+
found = packageCommands(pkg).map((command) => environmentFileUse(runner, command, packageDir, retainUnverified)).find((reason) => reason !== void 0);
|
|
912
|
+
found ??= environmentFileUse(runner, JSON.stringify(Object.fromEntries(Object.entries(pkg).filter(([key]) => !IGNORED_FIELDS.has(key) && key !== "scripts" && key !== "lint-staged"))), void 0, retainUnverified);
|
|
913
|
+
}
|
|
914
|
+
return found !== void 0;
|
|
505
915
|
},
|
|
506
916
|
skipDirs: USE_SCAN_SKIP_DIRS
|
|
507
917
|
});
|
|
508
918
|
return found;
|
|
509
919
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
920
|
+
const ENV_FILE_OPTION = /--env[-_]file(?:[-_]if[-_]exists)?(?=$|[=\s'"])/;
|
|
921
|
+
/** Explicit Node.js environment inputs need checking regardless of their extension. */
|
|
922
|
+
function environmentFileUse(runner, command, cwd, retainUnverified = true) {
|
|
923
|
+
if (!ENV_FILE_OPTION.test(command)) return;
|
|
924
|
+
const unchecked = retainUnverified ? `an explicit Node.js env file could load ${runner.name}, but vp migrate cannot check it` : void 0;
|
|
925
|
+
const segments = parseLiteralCommands(command, true);
|
|
926
|
+
if (!cwd || !segments || /%\w+%/.test(command)) return unchecked;
|
|
927
|
+
for (const tokens of segments) {
|
|
928
|
+
const values = tokens.map(({ value }) => value);
|
|
929
|
+
if (SHELL_STATE_COMMANDS.includes(values[0]) || values[0] === "chdir") cwd = void 0;
|
|
930
|
+
if (!values.some((value) => ENV_FILE_OPTION.test(value))) continue;
|
|
931
|
+
const start = getCommandStartIndex(values);
|
|
932
|
+
if (!cwd || values[start] !== "node") return unchecked;
|
|
933
|
+
for (let index = start + 1; index < values.length; index++) {
|
|
934
|
+
const value = values[index];
|
|
935
|
+
if (value === "--" || !value.startsWith("-")) break;
|
|
936
|
+
const name = optionName(value);
|
|
937
|
+
const inline = value.includes("=");
|
|
938
|
+
const takesArgument = NODE_OPTIONS_WITH_VALUE.has(name);
|
|
939
|
+
const argument = inline ? value.slice(value.indexOf("=") + 1) : values[index + 1];
|
|
940
|
+
if (name === "--env-file" || name === "--env-file-if-exists") {
|
|
941
|
+
if (!argument) return unchecked;
|
|
942
|
+
const file = path.resolve(cwd, argument);
|
|
943
|
+
try {
|
|
944
|
+
if (runner.textReference.test(fs.readFileSync(file, "utf8"))) return `${displayRelative(file)} still uses ${runner.name}`;
|
|
945
|
+
} catch {
|
|
946
|
+
if (retainUnverified) return `the env file ${displayRelative(file)} could load ${runner.name}, but vp migrate cannot read it`;
|
|
947
|
+
}
|
|
948
|
+
} else if (ENV_FILE_OPTION.test(value) || takesArgument && argument && ENV_FILE_OPTION.test(argument)) return unchecked;
|
|
949
|
+
if (!inline && takesArgument) index++;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/** Whether a file still runs, imports, or loads the runner. */
|
|
954
|
+
function fileUsesRunner(runner, content, file) {
|
|
955
|
+
if (runner.shebangPattern.test(content)) return true;
|
|
513
956
|
const name = path.basename(file);
|
|
514
|
-
if (
|
|
957
|
+
if (runner.referenceFileNamePattern?.test(name)) return runner.textReference.test(content);
|
|
958
|
+
if (LINT_STAGED_JSON_CONFIG_FILES.some((config) => config === name) && jsonUsesRunner(runner, parse(content), true)) return true;
|
|
515
959
|
const extension = path.extname(file);
|
|
516
|
-
if (extension === ".json" || extension === ".jsonc") return
|
|
517
|
-
if (CODE_EXTENSIONS.has(extension)) return
|
|
518
|
-
return
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
*/
|
|
527
|
-
function
|
|
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) {
|
|
960
|
+
if (extension === ".json" || extension === ".jsonc") return jsonUsesRunner(runner, parse(content), false);
|
|
961
|
+
if (CODE_EXTENSIONS.has(extension)) return runner.codeUsesRunner?.(content, file) ?? runner.codeReference.test(content);
|
|
962
|
+
return runner.textReference.test(content) || extension === "" && runner.codeReference.test(content);
|
|
963
|
+
}
|
|
964
|
+
/** Recognize command strings and loader references in structured configuration. */
|
|
965
|
+
function jsonUsesRunner(runner, value, commands) {
|
|
966
|
+
if (typeof value === "string") return commands && runner.commandPattern.test(value) || runner.configValue.test(value);
|
|
967
|
+
if (Array.isArray(value)) return value.some((item, index) => jsonUsesRunner(runner, item, commands) || typeof item === "string" && LOADER_OPTIONS.has(item) && runner.loaderPattern.test(String(value[index + 1])));
|
|
968
|
+
return isPlainRecord(value) && Object.entries(value).some(([key, item]) => jsonUsesRunner(runner, item, commands) || LOADER_SETTINGS.has(key) && [item].flat().some((loader) => runner.loaderPattern.test(String(loader))));
|
|
969
|
+
}
|
|
970
|
+
/** Rewrite known command surfaces, then remove dependencies with no remaining uses. */
|
|
971
|
+
function migrateScriptRunnerToVpx(runner, rootDir, packages, report) {
|
|
538
972
|
let changed = false;
|
|
539
973
|
const migrated = [];
|
|
540
974
|
for (const dir of packageDirs(rootDir, packages)) {
|
|
541
975
|
const packageJsonPath = path.join(dir, "package.json");
|
|
542
976
|
const pkg = readPackageJsonIfExists(packageJsonPath);
|
|
543
977
|
if (!pkg) continue;
|
|
544
|
-
if (
|
|
545
|
-
if (packageCommands(pkg).some((c) =>
|
|
978
|
+
if (ownsRunnerAtRuntime(runner, pkg)) {
|
|
979
|
+
if (packageCommands(pkg).some((c) => runner.commandPattern.test(c))) warnMigration(`Kept ${runner.name} in ${displayRelative(packageJsonPath)}: it is listed in dependencies, optionalDependencies, or peerDependencies there, and \`vpx\` comes from the vite-plus devDependency.`, report);
|
|
546
980
|
continue;
|
|
547
981
|
}
|
|
548
982
|
migrated.push(dir);
|
|
@@ -550,51 +984,538 @@ function migrateTsxToVpx(rootDir, packages, report) {
|
|
|
550
984
|
let edited = false;
|
|
551
985
|
for (const field of ["scripts", "lint-staged"]) {
|
|
552
986
|
const record = json[field];
|
|
553
|
-
if (isPlainRecord(record) && rewriteCommandRecord(record, `${displayRelative(packageJsonPath)} ${field}`, dir, report)) edited = true;
|
|
987
|
+
if (isPlainRecord(record) && rewriteCommandRecord(runner, record, `${displayRelative(packageJsonPath)} ${field}`, dir, report)) edited = true;
|
|
554
988
|
}
|
|
555
989
|
changed ||= edited;
|
|
556
990
|
return edited ? json : void 0;
|
|
557
991
|
});
|
|
558
992
|
for (const file of lintStagedJsonFiles(dir)) editJsonFile(file, (config) => {
|
|
559
|
-
const edited = rewriteCommandRecord(config, displayRelative(file), dir, report);
|
|
993
|
+
const edited = rewriteCommandRecord(runner, config, displayRelative(file), dir, report);
|
|
560
994
|
changed ||= edited;
|
|
561
995
|
return edited ? config : void 0;
|
|
562
996
|
});
|
|
563
|
-
if (
|
|
997
|
+
if (rewriteRunnerShebangs(runner, dir, report)) changed = true;
|
|
564
998
|
}
|
|
565
999
|
let dependencyRemoved = false;
|
|
566
1000
|
for (const dir of migrated) {
|
|
567
1001
|
const packageJsonPath = path.join(dir, "package.json");
|
|
568
|
-
|
|
569
|
-
|
|
1002
|
+
const devDependencies = readPackageJsonIfExists(packageJsonPath)?.devDependencies;
|
|
1003
|
+
if (!runner.dependencies.some((dependency) => devDependencies?.[dependency] !== void 0)) continue;
|
|
1004
|
+
const use = findRunnerUse(runner, dir);
|
|
570
1005
|
if (use) {
|
|
571
|
-
warnMigration(`Kept the
|
|
1006
|
+
warnMigration(`Kept the ${runner.dependencies.join(" / ")} devDependency in ${displayRelative(packageJsonPath)}: ${use}.`, report);
|
|
572
1007
|
continue;
|
|
573
1008
|
}
|
|
574
1009
|
editJsonFile(packageJsonPath, (json) => {
|
|
575
|
-
delete json.devDependencies
|
|
1010
|
+
for (const dependency of runner.dependencies) delete json.devDependencies[dependency];
|
|
576
1011
|
return json;
|
|
577
1012
|
});
|
|
578
1013
|
dependencyRemoved = true;
|
|
579
1014
|
}
|
|
580
|
-
if (report && changed) report.tsxMigrated = true;
|
|
581
1015
|
return {
|
|
582
1016
|
changed: changed || dependencyRemoved,
|
|
583
|
-
dependencyRemoved
|
|
1017
|
+
dependencyRemoved,
|
|
1018
|
+
scriptsChanged: changed
|
|
584
1019
|
};
|
|
585
1020
|
}
|
|
586
|
-
|
|
1021
|
+
// src/migration/migrator/oxnode.ts
|
|
1022
|
+
const OXNODE_COMMAND = /(?:^|[\s;&|("'=/\\`])oxnode(?:\.(?:cmd|exe|ps1))?(?=$|[@\s;&|)"'`])|@oxc-node\/core(?:\/[^\s'"`]+)?/i;
|
|
1023
|
+
const OXNODE_LOADER = /^@oxc-node\/core(?:\/.*)?$/;
|
|
1024
|
+
const OXNODE_PACKAGE_REFERENCE = /@oxc-node\/(?:cli|core)(?=$|[\s/'"`])/;
|
|
1025
|
+
const OXNODE_CODE_REFERENCE = new RegExp([OXNODE_COMMAND.source, OXNODE_PACKAGE_REFERENCE.source].join("|"), "i");
|
|
1026
|
+
const OXNODE_TEXT_REFERENCE = new RegExp([OXNODE_COMMAND.source, OXNODE_PACKAGE_REFERENCE.source].join("|"), "im");
|
|
1027
|
+
const KEEP_NODE_WATCH = "oxnode --watch starts the Node.js watcher, while `vpx --watch` uses its own watcher; use oxnode watch to migrate it";
|
|
1028
|
+
const KEEP_TRANSFORM_DEPS = "`OXC_TRANSFORM_ALL` changes dependency transforms, while the Vite+ loader always transforms only TypeScript dependencies";
|
|
1029
|
+
const REGISTER_KEEP_OPTIONS = /* @__PURE__ */ new Map([
|
|
1030
|
+
["--env-file", "an env file can configure oxc-node hooks or dependency transforms; migrate it manually"],
|
|
1031
|
+
["--env-file-if-exists", "an env file can configure oxc-node hooks or dependency transforms; migrate it manually"],
|
|
1032
|
+
["--config-file", "a Node.js config file can load additional hooks; migrate it manually"],
|
|
1033
|
+
["--experimental-config-file", "a Node.js config file can load additional hooks; migrate it manually"],
|
|
1034
|
+
["--experimental-default-config-file", "a Node.js config file can load additional hooks; migrate it manually"]
|
|
1035
|
+
]);
|
|
1036
|
+
function isOxnodeRegister(option, value) {
|
|
1037
|
+
return option === "--import" && /^@oxc-node\/core\/register(?:\.mjs)?$/.test(value);
|
|
1038
|
+
}
|
|
1039
|
+
function oxnodeEnvironmentReason(assignments) {
|
|
1040
|
+
for (const assignment of assignments) {
|
|
1041
|
+
const separator = assignment.indexOf("=");
|
|
1042
|
+
const name = assignment.slice(0, separator).toUpperCase();
|
|
1043
|
+
const value = assignment.slice(separator + 1);
|
|
1044
|
+
if (name === "OXC_TRANSFORM_ALL" && value !== "typescript") return KEEP_TRANSFORM_DEPS;
|
|
1045
|
+
if ((name === "VP_SCRIPT_TSCONFIG" || name === "TSX_TSCONFIG_PATH") && value) return `\`${name}\` would select a different configuration for the Vite+ loader`;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const OXNODE_RUNNER = {
|
|
1049
|
+
name: "oxnode",
|
|
1050
|
+
dependencies: ["@oxc-node/cli", "@oxc-node/core"],
|
|
1051
|
+
commandPattern: OXNODE_COMMAND,
|
|
1052
|
+
loaderPattern: OXNODE_LOADER,
|
|
1053
|
+
isLoader: isOxnodeRegister,
|
|
1054
|
+
isRegister: isOxnodeRegister,
|
|
1055
|
+
registerEnvKeepReason: oxnodeEnvironmentReason,
|
|
1056
|
+
registerKeepOptions: REGISTER_KEEP_OPTIONS,
|
|
1057
|
+
registerPreloadKeepReason(option, value) {
|
|
1058
|
+
if (option === "--loader" || option === "--experimental-loader" || option === "--import" && value === "vite-plus/register") return;
|
|
1059
|
+
return "an additional startup preload can configure oxc-node hooks or dependency transforms; migrate it manually";
|
|
1060
|
+
},
|
|
1061
|
+
registerInlineCodeKeepReason(code) {
|
|
1062
|
+
if (OXNODE_PACKAGE_REFERENCE.test(code)) return "inline code uses a standalone oxc-node API or loader that does not configure the bundled Vite+ loader";
|
|
1063
|
+
},
|
|
1064
|
+
envShebang: /^(#!\s*\S*\benv\s+(?:-S\s+)?)((?:oxnode|node)(?=[ \t\r\n]|$)[^\r\n]*)/,
|
|
1065
|
+
includeExtensionlessShebangs: true,
|
|
1066
|
+
shebangPattern: /^#![^\n]*[\s/]oxnode(?=\s|$)/,
|
|
1067
|
+
codeReference: OXNODE_CODE_REFERENCE,
|
|
1068
|
+
textReference: OXNODE_TEXT_REFERENCE,
|
|
1069
|
+
configValue: OXNODE_TEXT_REFERENCE,
|
|
1070
|
+
referenceFileNamePattern: /^\.env(?:\.|$)/,
|
|
1071
|
+
keepOptions: new Map([
|
|
1072
|
+
...REGISTER_KEEP_OPTIONS,
|
|
1073
|
+
["-e", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1074
|
+
["--eval", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1075
|
+
["-p", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1076
|
+
["-pe", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1077
|
+
["--print", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1078
|
+
["-c", "`vpx -c` selects package shell mode instead of Node.js syntax checking"],
|
|
1079
|
+
["--check", "`vpx` does not transform TypeScript for --check"],
|
|
1080
|
+
["-i", "`vpx` does not start a TypeScript REPL"],
|
|
1081
|
+
["--interactive", "`vpx` does not start a TypeScript REPL"],
|
|
1082
|
+
["--watch", KEEP_NODE_WATCH],
|
|
1083
|
+
["--watch-path", KEEP_NODE_WATCH],
|
|
1084
|
+
["--node-help", "`vpx` does not support oxnode --node-help"],
|
|
1085
|
+
["-h", "oxnode and `vpx` have different help output"],
|
|
1086
|
+
["--help", "oxnode and `vpx` have different help output"],
|
|
1087
|
+
["-v", "oxnode and `vpx` have different versions"],
|
|
1088
|
+
["--version", "oxnode and `vpx` have different versions"]
|
|
1089
|
+
]),
|
|
1090
|
+
optionsWithValue: /* @__PURE__ */ new Set([...WATCH_OPTIONS_WITH_VALUE, "--tsconfig"]),
|
|
1091
|
+
testWithoutFilesReason: "list the test files explicitly before migrating to `vpx --test`",
|
|
1092
|
+
envKeepReason(assignments) {
|
|
1093
|
+
const reason = oxnodeEnvironmentReason(assignments);
|
|
1094
|
+
if (reason) return reason;
|
|
1095
|
+
for (const assignment of assignments) {
|
|
1096
|
+
const separator = assignment.indexOf("=");
|
|
1097
|
+
const name = assignment.slice(0, separator).toUpperCase();
|
|
1098
|
+
const value = assignment.slice(separator + 1);
|
|
1099
|
+
if (name === "NODE_OPTIONS" && /@oxc-node\/core/.test(value)) return "`NODE_OPTIONS` loads oxc-node hooks that would compete with those of `vpx`; remove it first";
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
scriptKeepReason(cwd, script) {
|
|
1103
|
+
if (/[*?[{]/.test(script)) return;
|
|
1104
|
+
let file;
|
|
1105
|
+
if (script.startsWith("~/")) file = path.join(os.homedir(), script.slice(2));
|
|
1106
|
+
else if (path.isAbsolute(script)) file = script;
|
|
1107
|
+
else if (cwd !== void 0) file = path.resolve(cwd, script);
|
|
1108
|
+
else return;
|
|
1109
|
+
try {
|
|
1110
|
+
const source = fs.readFileSync(file, "utf8");
|
|
1111
|
+
if (OXNODE_PACKAGE_REFERENCE.test(source.replace(/^#![^\r\n]*/, ""))) return `\`${script}\` uses a standalone oxc-node API or loader that does not configure the bundled \`vpx\` loader`;
|
|
1112
|
+
} catch {}
|
|
1113
|
+
},
|
|
1114
|
+
detectKeptUses: true
|
|
1115
|
+
};
|
|
1116
|
+
function detectOxnodeProject(rootDir, packages) {
|
|
1117
|
+
return detectScriptRunnerProject(OXNODE_RUNNER, rootDir, packages);
|
|
1118
|
+
}
|
|
1119
|
+
function migrateOxnodeToVpx(rootDir, packages, report) {
|
|
1120
|
+
const result = migrateScriptRunnerToVpx(OXNODE_RUNNER, rootDir, packages, report);
|
|
1121
|
+
if (report && result.scriptsChanged) report.oxnodeMigrated = true;
|
|
1122
|
+
return {
|
|
1123
|
+
changed: result.changed,
|
|
1124
|
+
dependencyRemoved: result.dependencyRemoved
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
async function confirmOxnodeMigration(interactive) {
|
|
1128
|
+
if (interactive) {
|
|
1129
|
+
const confirmed = await confirm({
|
|
1130
|
+
message: "Migrate oxnode scripts to Vite+?\n " + styleText("gray", "`oxnode <file>` becomes `vpx <file>`, and `oxnode watch` becomes `vpx --watch`. Node.js imports use `vite-plus/register`. Standalone oxc-node dependencies are removed once nothing uses them."),
|
|
1131
|
+
initialValue: true
|
|
1132
|
+
});
|
|
1133
|
+
if (isCancel(confirmed)) cancelAndExit();
|
|
1134
|
+
return confirmed;
|
|
1135
|
+
}
|
|
1136
|
+
log.info("oxnode scripts detected. Auto-migrating to Vite+...");
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
// src/migration/migrator/ts-node.ts
|
|
1140
|
+
const TS_NODE_REFERENCE = /(?:^|[\s;&|("'=/\\`])ts-node(?:-(?:cwd|esm|script|transpile-only))?(?:\.(?:cmd|exe|ps1))?(?=$|[@/\s;&|)"'`])/i;
|
|
1141
|
+
const TS_NODE_LOADERS = /^ts-node(?:\/.*)?$/;
|
|
1142
|
+
/** Check module metadata only; migration must not execute a project's dependencies. */
|
|
1143
|
+
function incompatibleModuleSource(file, source) {
|
|
1144
|
+
file = fs.realpathSync(file);
|
|
1145
|
+
const { ast } = JSON.parse(analyzeMigrationSource(file, source));
|
|
1146
|
+
let requireResolver;
|
|
1147
|
+
let importResolver;
|
|
1148
|
+
for (const node of ast.node.body) {
|
|
1149
|
+
let specifier;
|
|
1150
|
+
let namedOnly = false;
|
|
1151
|
+
if (node.type === "ImportDeclaration" && node.importKind !== "type") {
|
|
1152
|
+
const bindings = node.specifiers.filter((binding) => binding.type !== "ImportSpecifier" || binding.importKind !== "type");
|
|
1153
|
+
if (!bindings.length) continue;
|
|
1154
|
+
specifier = node.source.value;
|
|
1155
|
+
namedOnly = bindings.every((binding) => binding.type === "ImportSpecifier" && (binding.imported.type === "Identifier" ? binding.imported.name : binding.imported.value) !== "default");
|
|
1156
|
+
} else if (node.type === "ExportNamedDeclaration" && node.source && node.exportKind !== "type") {
|
|
1157
|
+
const bindings = node.specifiers.filter((binding) => binding.exportKind !== "type");
|
|
1158
|
+
if (!bindings.length) continue;
|
|
1159
|
+
specifier = node.source.value;
|
|
1160
|
+
namedOnly = bindings.every((binding) => (binding.local.type === "Identifier" ? binding.local.name : binding.local.value) !== "default");
|
|
1161
|
+
} else if (node.type === "ExportAllDeclaration" && node.exportKind !== "type") specifier = node.source.value;
|
|
1162
|
+
else continue;
|
|
1163
|
+
requireResolver ??= new ResolverFactory({
|
|
1164
|
+
conditionNames: [
|
|
1165
|
+
"node",
|
|
1166
|
+
"require",
|
|
1167
|
+
"module-sync",
|
|
1168
|
+
"node-addons"
|
|
1169
|
+
],
|
|
1170
|
+
builtinModules: true,
|
|
1171
|
+
moduleType: true,
|
|
1172
|
+
nodePath: false
|
|
1173
|
+
});
|
|
1174
|
+
importResolver ??= requireResolver.cloneWithOptions({
|
|
1175
|
+
conditionNames: [
|
|
1176
|
+
"node",
|
|
1177
|
+
"import",
|
|
1178
|
+
"module-sync",
|
|
1179
|
+
"node-addons"
|
|
1180
|
+
],
|
|
1181
|
+
builtinModules: true,
|
|
1182
|
+
moduleType: true,
|
|
1183
|
+
nodePath: false
|
|
1184
|
+
});
|
|
1185
|
+
const required = requireResolver.sync(path.dirname(file), specifier);
|
|
1186
|
+
const imported = importResolver.sync(path.dirname(file), specifier);
|
|
1187
|
+
if (namedOnly && required.builtin && required.builtin.resolved === imported.builtin?.resolved) continue;
|
|
1188
|
+
if (required.path && required.path === imported.path && String(required.moduleType) === "module" && String(imported.moduleType) === "module") continue;
|
|
1189
|
+
return specifier;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function isTsNodeLoader(option, value) {
|
|
1193
|
+
return (option === "-r" || option === "--require" || option === "--import") && /^ts-node\/register(?:\/(?:transpile-only|type-check|files))?$/.test(value) || (option === "--loader" || option === "--experimental-loader") && /^ts-node\/esm(?:\/transpile-only)?(?:\.mjs)?$/.test(value);
|
|
1194
|
+
}
|
|
1195
|
+
const TS_NODE_RUNNER = {
|
|
1196
|
+
name: "ts-node",
|
|
1197
|
+
aliases: ["ts-node-esm", "ts-node-transpile-only"],
|
|
1198
|
+
watchSubcommand: false,
|
|
1199
|
+
dependencies: ["ts-node"],
|
|
1200
|
+
commandPattern: TS_NODE_REFERENCE,
|
|
1201
|
+
loaderPattern: TS_NODE_LOADERS,
|
|
1202
|
+
isLoader: isTsNodeLoader,
|
|
1203
|
+
isRegister: isTsNodeLoader,
|
|
1204
|
+
registerEnvRequiresNode: true,
|
|
1205
|
+
envShebang: /^(#!\s*\S*\benv\s+(?:-S\s+)?)(ts-node(?:-(?:esm|transpile-only))?(?=[ \t\r\n]|$)[^\r\n]*)/,
|
|
1206
|
+
shebangPattern: /^#![^\n]*[\s/]ts-node(?:-(?:cwd|esm|script|transpile-only))?(?=\s|$)/,
|
|
1207
|
+
codeReference: TS_NODE_REFERENCE,
|
|
1208
|
+
textReference: TS_NODE_REFERENCE,
|
|
1209
|
+
configValue: TS_NODE_REFERENCE,
|
|
1210
|
+
referenceFileNamePattern: /^\.env(?:\.|$)/,
|
|
1211
|
+
keepOptions: /* @__PURE__ */ new Map([
|
|
1212
|
+
["-e", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1213
|
+
["--eval", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1214
|
+
["-p", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1215
|
+
["--print", "`vpx` does not run TypeScript passed to --eval or --print"],
|
|
1216
|
+
["-c", "`vpx -c` selects package shell mode instead of Node.js syntax checking"],
|
|
1217
|
+
["--check", "`vpx` does not transform TypeScript for --check"],
|
|
1218
|
+
["-i", "`vpx` does not start a TypeScript REPL"],
|
|
1219
|
+
["--interactive", "`vpx` does not start a TypeScript REPL"],
|
|
1220
|
+
["--watch", "Node.js watch mode and `vpx --watch` use different watchers"],
|
|
1221
|
+
["--watch-path", "Node.js watch mode and `vpx --watch` use different watchers"],
|
|
1222
|
+
["-h", "ts-node and `vpx` have different help output"],
|
|
1223
|
+
["--help", "ts-node and `vpx` have different help output"],
|
|
1224
|
+
["-v", "ts-node and `vpx` have different versions"],
|
|
1225
|
+
["--version", "ts-node and `vpx` have different versions"]
|
|
1226
|
+
]),
|
|
1227
|
+
optionsWithValue: /* @__PURE__ */ new Set(["--tsconfig"]),
|
|
1228
|
+
testWithoutFilesReason: "list the test files explicitly before migrating to `vpx --test`",
|
|
1229
|
+
envKeepReason(assignments) {
|
|
1230
|
+
if (assignments.some((assignment) => /^NODE_OPTIONS=/i.test(assignment) && TS_NODE_REFERENCE.test(assignment.slice(13)))) return "`NODE_OPTIONS` loads ts-node hooks that would compete with those of `vpx`; remove it first";
|
|
1231
|
+
if (assignments.some((assignment) => /^NODE_OPTIONS=.+/i.test(assignment))) return "`NODE_OPTIONS` can change ts-node startup or load configuration preloads; review it before migrating";
|
|
1232
|
+
},
|
|
1233
|
+
prepareRun: (args, context) => prepareTsNodeRun(args, context),
|
|
1234
|
+
scriptKeepReason(cwd, script) {
|
|
1235
|
+
const reason = typescriptScriptKeepReason(cwd, script, "ts-node");
|
|
1236
|
+
if (reason) return reason;
|
|
1237
|
+
if (cwd !== void 0 && !/[*?[{]/.test(script)) try {
|
|
1238
|
+
const file = script.startsWith("~/") ? path.join(os.homedir(), script.slice(2)) : path.resolve(cwd, script);
|
|
1239
|
+
const source = fs.readFileSync(file, "utf8").replace(/^#![^\n]*\n?/, "");
|
|
1240
|
+
if (TS_NODE_REFERENCE.test(source)) return `\`${script}\` uses a standalone ts-node API or loader that does not configure the bundled \`vpx\` loader`;
|
|
1241
|
+
const incompatible = incompatibleModuleSource(file, source);
|
|
1242
|
+
if (incompatible !== void 0) return `\`${script}\` has runtime bindings from \`${incompatible}\` whose CommonJS interop or import/require resolution may change with \`vpx\`; migrate the bindings manually`;
|
|
1243
|
+
} catch {
|
|
1244
|
+
return `\`${script}\` cannot be read or parsed, so vp migrate cannot check its CommonJS interop`;
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
detectKeptUses: true
|
|
1248
|
+
};
|
|
1249
|
+
function detectTsNodeProject(rootDir, packages) {
|
|
1250
|
+
return detectScriptRunnerProject(TS_NODE_RUNNER, rootDir, packages);
|
|
1251
|
+
}
|
|
1252
|
+
function migrateTsNodeToVpx(rootDir, packages, report) {
|
|
1253
|
+
const result = migrateScriptRunnerToVpx(TS_NODE_RUNNER, rootDir, packages, report);
|
|
1254
|
+
if (report && result.scriptsChanged) report.tsNodeMigrated = true;
|
|
1255
|
+
return {
|
|
1256
|
+
changed: result.changed,
|
|
1257
|
+
dependencyRemoved: result.dependencyRemoved
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
async function confirmTsNodeMigration(interactive) {
|
|
587
1261
|
if (interactive) {
|
|
588
1262
|
const confirmed = await confirm({
|
|
589
|
-
message: "Migrate
|
|
1263
|
+
message: "Migrate ts-node scripts to Vite+?\n " + styleText("gray", "Commands with type-checking disabled become `vpx`. Supported Node.js preloads become `vite-plus/register`. Commands that type-check or use custom ts-node settings stay unchanged. The ts-node devDependency is removed once nothing uses it."),
|
|
590
1264
|
initialValue: true
|
|
591
1265
|
});
|
|
592
1266
|
if (isCancel(confirmed)) cancelAndExit();
|
|
593
1267
|
return confirmed;
|
|
594
1268
|
}
|
|
595
|
-
log
|
|
1269
|
+
log.info("ts-node scripts detected. Auto-migrating supported commands to Vite+...");
|
|
596
1270
|
return true;
|
|
597
1271
|
}
|
|
1272
|
+
// src/migration/migrator/ts-node-config.ts
|
|
1273
|
+
const KEEP_TYPE_CHECK = "this ts-node command type-checks before execution; `vpx` does not type-check. Keep a separate type-check command before changing this script";
|
|
1274
|
+
const KEEP_CWD = "the working directory is unknown, so the ts-node configuration cannot be checked";
|
|
1275
|
+
const TS_NODE_LOADER = /^ts-node\/(?:register(?:\/(?:transpile-only|type-check|files))?|esm(?:\/transpile-only)?(?:\.mjs)?)$/;
|
|
1276
|
+
const PRELOAD_OPTIONS = /* @__PURE__ */ new Set([
|
|
1277
|
+
"-r",
|
|
1278
|
+
"--require",
|
|
1279
|
+
"--import",
|
|
1280
|
+
"--loader",
|
|
1281
|
+
"--experimental-loader"
|
|
1282
|
+
]);
|
|
1283
|
+
const BOOLEAN_OPTIONS = /* @__PURE__ */ new Map([
|
|
1284
|
+
["-T", "transpileOnly"],
|
|
1285
|
+
["--transpileOnly", "transpileOnly"],
|
|
1286
|
+
["--transpile-only", "transpileOnly"],
|
|
1287
|
+
["--typeCheck", "typeCheck"],
|
|
1288
|
+
["--type-check", "typeCheck"],
|
|
1289
|
+
["--esm", "esm"],
|
|
1290
|
+
["--files", "files"]
|
|
1291
|
+
]);
|
|
1292
|
+
const CONFIG_OPTIONS = /* @__PURE__ */ new Set([
|
|
1293
|
+
"transpileOnly",
|
|
1294
|
+
"typeCheck",
|
|
1295
|
+
"esm",
|
|
1296
|
+
"files"
|
|
1297
|
+
]);
|
|
1298
|
+
function booleanEnvironment(value) {
|
|
1299
|
+
if (/^(?:y|yes|true|1)$/i.test(value)) return true;
|
|
1300
|
+
if (/^(?:n|no|false|0)$/i.test(value)) return false;
|
|
1301
|
+
}
|
|
1302
|
+
function nearestTsconfig(dir) {
|
|
1303
|
+
for (;;) {
|
|
1304
|
+
const file = path.join(dir, "tsconfig.json");
|
|
1305
|
+
if (fs.existsSync(file)) return file;
|
|
1306
|
+
const parent = path.dirname(dir);
|
|
1307
|
+
if (parent === dir) return;
|
|
1308
|
+
dir = parent;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function readTsNodeEnvironment(context) {
|
|
1312
|
+
const options = {};
|
|
1313
|
+
let project;
|
|
1314
|
+
for (const assignment of context.environment) {
|
|
1315
|
+
const separator = assignment.indexOf("=");
|
|
1316
|
+
if (separator < 0) continue;
|
|
1317
|
+
const originalName = assignment.slice(0, separator);
|
|
1318
|
+
const name = originalName.toUpperCase();
|
|
1319
|
+
const value = assignment.slice(separator + 1);
|
|
1320
|
+
if (originalName !== name && (name.startsWith("TS_NODE_") || context.publicRegister && name === "NODE_OPTIONS")) return { keepReason: `\`${originalName}\` uses platform-dependent environment variable casing` };
|
|
1321
|
+
if (name === "TS_NODE_PROJECT") project = value || void 0;
|
|
1322
|
+
else if (name === "TS_NODE_TRANSPILE_ONLY" || name === "TS_NODE_TYPE_CHECK") {
|
|
1323
|
+
const parsed = booleanEnvironment(value);
|
|
1324
|
+
if (parsed === void 0) return { keepReason: `\`${name}\` does not have a literal boolean value` };
|
|
1325
|
+
options[name === "TS_NODE_TRANSPILE_ONLY" ? "transpileOnly" : "typeCheck"] = parsed;
|
|
1326
|
+
} else if (name.startsWith("TS_NODE_")) return { keepReason: `\`${name}\` configures ts-node behavior that vp migrate does not translate` };
|
|
1327
|
+
else if ([
|
|
1328
|
+
"OXC_TSCONFIG_PATH",
|
|
1329
|
+
"TSX_TSCONFIG_PATH",
|
|
1330
|
+
"VP_SCRIPT_TSCONFIG"
|
|
1331
|
+
].includes(name) && value) return { keepReason: `\`${name}\` would select a different configuration for the Vite+ loader` };
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
options,
|
|
1335
|
+
project
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
/** Read only data; migration must not load ts-node or a project's compiler. */
|
|
1339
|
+
function readTsNodeOptions(file, ancestors = /* @__PURE__ */ new Set()) {
|
|
1340
|
+
const keepConfig = (reason) => ({ keepReason: `the ts-node configuration in \`${file}\` ${reason}` });
|
|
1341
|
+
if (ancestors.has(file)) return keepConfig("has circular inheritance");
|
|
1342
|
+
const chain = new Set(ancestors).add(file);
|
|
1343
|
+
let config;
|
|
1344
|
+
try {
|
|
1345
|
+
const errors = [];
|
|
1346
|
+
config = parse(fs.readFileSync(file, "utf8"), errors, { allowTrailingComma: true });
|
|
1347
|
+
if (errors.length || !isPlainRecord(config)) return keepConfig("cannot be parsed");
|
|
1348
|
+
} catch {
|
|
1349
|
+
return keepConfig("cannot be read");
|
|
1350
|
+
}
|
|
1351
|
+
let inherited = {};
|
|
1352
|
+
let compilerOptions = {};
|
|
1353
|
+
if (config.extends !== void 0) {
|
|
1354
|
+
if (typeof config.extends !== "string" || !config.extends.startsWith(".") && !path.isAbsolute(config.extends)) return keepConfig("uses inheritance that vp migrate cannot resolve");
|
|
1355
|
+
let base = path.resolve(path.dirname(file), config.extends);
|
|
1356
|
+
if (!fs.existsSync(base) && !base.endsWith(".json")) base += ".json";
|
|
1357
|
+
const result = readTsNodeOptions(base, chain);
|
|
1358
|
+
if (result.keepReason) return result;
|
|
1359
|
+
inherited = result.options ?? {};
|
|
1360
|
+
compilerOptions = result.compilerOptions ?? {};
|
|
1361
|
+
}
|
|
1362
|
+
if (config.compilerOptions !== void 0) {
|
|
1363
|
+
if (!isPlainRecord(config.compilerOptions)) return keepConfig("has an invalid `compilerOptions` object");
|
|
1364
|
+
compilerOptions = {
|
|
1365
|
+
...compilerOptions,
|
|
1366
|
+
...config.compilerOptions
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
const settings = config["ts-node"];
|
|
1370
|
+
if (settings === void 0) return {
|
|
1371
|
+
options: inherited,
|
|
1372
|
+
compilerOptions
|
|
1373
|
+
};
|
|
1374
|
+
if (!isPlainRecord(settings)) return keepConfig("has an invalid `ts-node` object");
|
|
1375
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
1376
|
+
if (!CONFIG_OPTIONS.has(key)) return keepConfig(`uses \`ts-node.${key}\`, which vp migrate does not translate`);
|
|
1377
|
+
if (typeof value !== "boolean") return keepConfig(`has a non-boolean \`ts-node.${key}\` value`);
|
|
1378
|
+
}
|
|
1379
|
+
return {
|
|
1380
|
+
options: {
|
|
1381
|
+
...inherited,
|
|
1382
|
+
...settings
|
|
1383
|
+
},
|
|
1384
|
+
compilerOptions
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Translate the supported ts-node options before the shared runner scans Node options.
|
|
1389
|
+
* ts-node merges environment < inherited config < CLI/API, then typeCheck overrides
|
|
1390
|
+
* transpileOnly. Keep all other compiler behavior for manual migration.
|
|
1391
|
+
*/
|
|
1392
|
+
function prepareTsNodeRun(args, context) {
|
|
1393
|
+
const { cwd, nodeLoader, executable } = context;
|
|
1394
|
+
if (!cwd) return { keepReason: KEEP_CWD };
|
|
1395
|
+
if (context.publicRegister) {
|
|
1396
|
+
if (context.nodeOptionsArgs === void 0 && context.environment.some((assignment) => /^NODE_OPTIONS=.+/i.test(assignment))) return { keepReason: "the literal NODE_OPTIONS arguments cannot be checked for ts-node settings" };
|
|
1397
|
+
args = [...context.nodeOptionsArgs ?? [], ...args];
|
|
1398
|
+
}
|
|
1399
|
+
const environment = readTsNodeEnvironment(context);
|
|
1400
|
+
if ("keepReason" in environment) return environment;
|
|
1401
|
+
const explicit = {};
|
|
1402
|
+
if (executable === "ts-node-transpile-only") explicit.transpileOnly = true;
|
|
1403
|
+
else if (executable !== "ts-node" && executable !== "ts-node-esm" && !nodeLoader) return { keepReason: `vp migrate does not translate the \`${executable}\` execution mode` };
|
|
1404
|
+
const edits = [];
|
|
1405
|
+
let project;
|
|
1406
|
+
let entry;
|
|
1407
|
+
let loaders = 0;
|
|
1408
|
+
for (let index = 0; index < args.length; index++) {
|
|
1409
|
+
const value = args[index];
|
|
1410
|
+
if (value === "--") {
|
|
1411
|
+
entry = args[index + 1];
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
if (!value.startsWith("-")) {
|
|
1415
|
+
entry = value;
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
const equals = value.indexOf("=");
|
|
1419
|
+
const originalOption = equals < 0 ? value : value.slice(0, equals);
|
|
1420
|
+
const name = nodeLoader && originalOption.startsWith("--") ? originalOption.replaceAll("_", "-") : originalOption;
|
|
1421
|
+
const inline = equals < 0 ? void 0 : value.slice(equals + 1);
|
|
1422
|
+
if (context.publicRegister && name === "--run") return { keepReason: "vp migrate cannot check the ts-node settings of a package script run by Node.js" };
|
|
1423
|
+
if (context.publicRegister && (name === "--eval" || name === "--print" || /^-[ep]/.test(name) || name === "-i" || name === "--interactive")) return { keepReason: "vp migrate does not translate ts-node inline code or REPL execution" };
|
|
1424
|
+
if (nodeLoader && (name === "--env-file" || name === "--env-file-if-exists")) return { keepReason: `\`${name}\` can configure ts-node through environment variables; vp migrate does not translate those settings` };
|
|
1425
|
+
if (nodeLoader && [
|
|
1426
|
+
"--config-file",
|
|
1427
|
+
"--experimental-config-file",
|
|
1428
|
+
"--experimental-default-config-file"
|
|
1429
|
+
].includes(name)) return { keepReason: `\`${name}\` can load preloads that configure ts-node; migrate it manually` };
|
|
1430
|
+
const booleanOption = BOOLEAN_OPTIONS.get(name);
|
|
1431
|
+
if (!nodeLoader && booleanOption) {
|
|
1432
|
+
if (inline !== void 0) return { keepReason: `ts-node treats \`${name}=value\` as an enabled flag; use an explicit configuration value before migrating` };
|
|
1433
|
+
explicit[booleanOption] = true;
|
|
1434
|
+
edits.push({ index });
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
if (!nodeLoader && (name === "-P" || name === "--project")) {
|
|
1438
|
+
if (project !== void 0) return { keepReason: "the command selects a ts-node project more than once" };
|
|
1439
|
+
const projectPath = inline ?? args[index + 1];
|
|
1440
|
+
if (!projectPath || projectPath.startsWith("-")) return { keepReason: `\`${name}\` does not name a tsconfig` };
|
|
1441
|
+
project = {
|
|
1442
|
+
path: projectPath,
|
|
1443
|
+
index,
|
|
1444
|
+
inline: inline !== void 0
|
|
1445
|
+
};
|
|
1446
|
+
if (!project.inline) index++;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (PRELOAD_OPTIONS.has(name)) {
|
|
1450
|
+
if (!nodeLoader && name !== "-r" && name !== "--require") return { keepReason: `\`${name}\` uses ts-node behavior that vp migrate does not translate` };
|
|
1451
|
+
const preload = inline ?? args[index + 1];
|
|
1452
|
+
if (!preload || preload.startsWith("-")) return { keepReason: `\`${name}\` does not name a preload` };
|
|
1453
|
+
if (TS_NODE_LOADER.test(preload)) {
|
|
1454
|
+
if (!nodeLoader || ++loaders > 1) return { keepReason: "the command registers ts-node more than once" };
|
|
1455
|
+
if (/\/transpile-only(?:\.mjs)?$/.test(preload)) explicit.transpileOnly = true;
|
|
1456
|
+
else if (preload.endsWith("/type-check")) explicit.typeCheck = true;
|
|
1457
|
+
} else return { keepReason: `the additional preload \`${preload}\` can configure ts-node before execution; migrate it manually` };
|
|
1458
|
+
if (inline === void 0) index++;
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (!nodeLoader) return { keepReason: `\`${name}\` uses ts-node behavior that vp migrate does not translate` };
|
|
1462
|
+
if (inline === void 0 && NODE_OPTIONS_WITH_VALUE.has(name)) index++;
|
|
1463
|
+
}
|
|
1464
|
+
if (!entry || entry === "-") return { keepReason: "vp migrate only replaces ts-node commands that name a script file" };
|
|
1465
|
+
const selectedProject = project?.path ?? environment.project;
|
|
1466
|
+
if (context.shebang && selectedProject !== void 0) return { keepReason: "the ts-node shebang selects a tsconfig relative to the caller; migrate it manually" };
|
|
1467
|
+
let configFile;
|
|
1468
|
+
if (selectedProject) {
|
|
1469
|
+
configFile = path.resolve(cwd, selectedProject);
|
|
1470
|
+
if (fs.statSync(configFile, { throwIfNoEntry: false })?.isDirectory()) {
|
|
1471
|
+
if (project === void 0) return { keepReason: "`TS_NODE_PROJECT` names a directory; `vpx` requires the tsconfig file path" };
|
|
1472
|
+
configFile = path.join(configFile, "tsconfig.json");
|
|
1473
|
+
project.path = path.join(project.path, "tsconfig.json");
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
let searchDir = cwd;
|
|
1477
|
+
if (!nodeLoader) try {
|
|
1478
|
+
searchDir = path.dirname(fs.realpathSync(path.resolve(cwd, entry)));
|
|
1479
|
+
} catch {
|
|
1480
|
+
return { keepReason: `\`${entry}\` cannot be resolved, so its ts-node configuration cannot be checked` };
|
|
1481
|
+
}
|
|
1482
|
+
configFile = nearestTsconfig(searchDir);
|
|
1483
|
+
if (context.publicRegister && !configFile) {
|
|
1484
|
+
let entryConfig;
|
|
1485
|
+
try {
|
|
1486
|
+
entryConfig = nearestTsconfig(path.dirname(fs.realpathSync(path.resolve(cwd, entry))));
|
|
1487
|
+
} catch {
|
|
1488
|
+
return { keepReason: `\`${entry}\` cannot be resolved, so its ts-node configuration cannot be checked` };
|
|
1489
|
+
}
|
|
1490
|
+
if (entryConfig) return { keepReason: "the Node.js preload would discover an entrypoint tsconfig that ts-node does not use; select the intended config with TS_NODE_PROJECT before migrating" };
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
const config = configFile ? readTsNodeOptions(configFile) : { options: {} };
|
|
1494
|
+
if (config.keepReason) return { keepReason: config.keepReason };
|
|
1495
|
+
if (context.shebang && configFile) return { keepReason: "the ts-node shebang needs a pinned tsconfig, whose path depends on the caller; migrate it manually" };
|
|
1496
|
+
const options = {
|
|
1497
|
+
...environment.options,
|
|
1498
|
+
...config.options,
|
|
1499
|
+
...explicit
|
|
1500
|
+
};
|
|
1501
|
+
if (options.transpileOnly !== true || options.typeCheck === true) return { keepReason: KEEP_TYPE_CHECK };
|
|
1502
|
+
if (configFile && config.compilerOptions?.target == null && typeof config.compilerOptions?.useDefineForClassFields !== "boolean") return { keepReason: "the ts-node config defaults to assignment class fields, while the Vite+ loader defaults to define semantics; set compilerOptions.useDefineForClassFields explicitly before migrating" };
|
|
1503
|
+
if (project !== void 0) {
|
|
1504
|
+
edits.push({
|
|
1505
|
+
index: project.index,
|
|
1506
|
+
value: `--tsconfig=${project.path.split(path.sep).join("/")}`
|
|
1507
|
+
});
|
|
1508
|
+
if (!project.inline) edits.push({ index: project.index + 1 });
|
|
1509
|
+
}
|
|
1510
|
+
if (configFile && selectedProject === void 0) {
|
|
1511
|
+
if (context.publicRegister) return { keepReason: "the Node.js preload needs the ts-node config pinned explicitly; set TS_NODE_PROJECT to its file path before migrating" };
|
|
1512
|
+
return {
|
|
1513
|
+
edits,
|
|
1514
|
+
prependArgs: [`--tsconfig=${path.relative(fs.realpathSync(cwd), fs.realpathSync(configFile)).split(path.sep).join("/")}`]
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return { edits };
|
|
1518
|
+
}
|
|
598
1519
|
// src/migration/vitest-v5/commands.ts
|
|
599
1520
|
/** Read test commands without evaluating shell state or directory changes. */
|
|
600
1521
|
function literalTestCommands(command) {
|
|
@@ -1547,6 +2468,44 @@ var import_dist = require_dist();
|
|
|
1547
2468
|
function record$1(value) {
|
|
1548
2469
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1549
2470
|
}
|
|
2471
|
+
/** Create one reader per preflight, with a single parser for each lockfile path. */
|
|
2472
|
+
function createLockfileReader() {
|
|
2473
|
+
const locks = /* @__PURE__ */ new Map();
|
|
2474
|
+
return (file, parse) => {
|
|
2475
|
+
if (!locks.has(file)) {
|
|
2476
|
+
locks.set(file, void 0);
|
|
2477
|
+
try {
|
|
2478
|
+
locks.set(file, parse(fs.readFileSync(file, "utf8").replace(/^\uFEFF/, "")));
|
|
2479
|
+
} catch {}
|
|
2480
|
+
}
|
|
2481
|
+
return locks.get(file);
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
function parseYarnLockfile(text) {
|
|
2485
|
+
if (/^__metadata:/m.test(text)) {
|
|
2486
|
+
const document = (0, import_dist.parseDocument)(text);
|
|
2487
|
+
if (document.errors.length) return;
|
|
2488
|
+
return {
|
|
2489
|
+
modern: true,
|
|
2490
|
+
entries: record$1(document.toJS()) ?? {}
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
const entries = [];
|
|
2494
|
+
for (const block of text.split(/\r?\n(?=[^\s#])/)) {
|
|
2495
|
+
const [header] = block.split(/\r?\n/);
|
|
2496
|
+
if (!header.endsWith(":")) continue;
|
|
2497
|
+
const document = (0, import_dist.parseDocument)(`[${header.slice(0, -1)}]`);
|
|
2498
|
+
const keys = document.errors.length ? void 0 : document.toJS();
|
|
2499
|
+
if (Array.isArray(keys)) entries.push({
|
|
2500
|
+
keys,
|
|
2501
|
+
version: /^ version "([^"]+)"\r?$/m.exec(block)?.[1]
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
return {
|
|
2505
|
+
modern: false,
|
|
2506
|
+
entries
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
1550
2509
|
function declaredVitest(pkg) {
|
|
1551
2510
|
const manifest = record$1(pkg);
|
|
1552
2511
|
for (const field of [
|
|
@@ -1562,7 +2521,7 @@ function declaredVitest(pkg) {
|
|
|
1562
2521
|
/** Read a direct upstream Vitest edge without installing or changing a lockfile.
|
|
1563
2522
|
* Unsupported protocols, ambiguous layouts and stale entries remain unknown.
|
|
1564
2523
|
* In particular, never infer a runner from an unrelated transitive package. */
|
|
1565
|
-
function lockedVitestVersion(root, directory, manager, spec) {
|
|
2524
|
+
function lockedVitestVersion(root, directory, manager, spec, readLockfile) {
|
|
1566
2525
|
const range = spec.replace(/^npm:vitest@/, "");
|
|
1567
2526
|
const validRange = normalizeRange(range);
|
|
1568
2527
|
if (!validRange && !/^[\w.-]+$/.test(range)) return;
|
|
@@ -1571,10 +2530,9 @@ function lockedVitestVersion(root, directory, manager, spec) {
|
|
|
1571
2530
|
return !validRange || satisfies(version, range, { includePrerelease: true }) ? version : void 0;
|
|
1572
2531
|
}
|
|
1573
2532
|
const relative = path.relative(root, directory).replaceAll("\\", "/");
|
|
1574
|
-
const read = (name) => fs.readFileSync(path.join(root, name), "utf8").replace(/^\uFEFF/, "");
|
|
1575
2533
|
try {
|
|
1576
2534
|
if (manager === PackageManager.npm) {
|
|
1577
|
-
const packages = record$1(
|
|
2535
|
+
const packages = record$1(readLockfile(path.join(root, fs.existsSync(path.join(root, "npm-shrinkwrap.json")) ? "npm-shrinkwrap.json" : "package-lock.json"), (source) => record$1(JSON.parse(source)))?.packages);
|
|
1578
2536
|
if (!packages) return;
|
|
1579
2537
|
if (declaredVitest(packages[relative]) !== spec) return;
|
|
1580
2538
|
let owner = relative;
|
|
@@ -1587,34 +2545,29 @@ function lockedVitestVersion(root, directory, manager, spec) {
|
|
|
1587
2545
|
}
|
|
1588
2546
|
}
|
|
1589
2547
|
if (manager === PackageManager.yarn) {
|
|
1590
|
-
const
|
|
1591
|
-
if (
|
|
1592
|
-
|
|
1593
|
-
if (document.errors.length) return;
|
|
2548
|
+
const lock = readLockfile(path.join(root, "yarn.lock"), parseYarnLockfile);
|
|
2549
|
+
if (!lock) return;
|
|
2550
|
+
if (lock.modern) {
|
|
1594
2551
|
const descriptors = /* @__PURE__ */ new Set([`vitest@${spec}`, `vitest@npm:${range}`]);
|
|
1595
|
-
const entries = Object.entries(
|
|
2552
|
+
const entries = Object.entries(lock.entries).filter(([keys]) => keys.split(/,\s+/).some((key) => descriptors.has(key)));
|
|
1596
2553
|
if (entries.length !== 1) return;
|
|
1597
2554
|
const entry = record$1(entries[0][1]);
|
|
1598
2555
|
return typeof entry?.resolution === "string" && entry.resolution.startsWith("vitest@npm:") ? valid(entry.resolution.slice(11)) : void 0;
|
|
1599
2556
|
}
|
|
1600
2557
|
const matches = [];
|
|
1601
|
-
for (const
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
const document = (0, import_dist.parseDocument)(`[${header.slice(0, -1)}]`);
|
|
1605
|
-
const keys = document.errors.length ? void 0 : document.toJS();
|
|
1606
|
-
if (Array.isArray(keys) && keys.includes(`vitest@${spec}`)) {
|
|
1607
|
-
const version = /^ version "([^"]+)"\r?$/m.exec(block)?.[1];
|
|
1608
|
-
if (!version) return;
|
|
1609
|
-
matches.push(version);
|
|
1610
|
-
}
|
|
2558
|
+
for (const { keys, version } of lock.entries) if (keys.includes(`vitest@${spec}`)) {
|
|
2559
|
+
if (!version) return;
|
|
2560
|
+
matches.push(version);
|
|
1611
2561
|
}
|
|
1612
2562
|
return matches.length === 1 ? valid(matches[0]) : void 0;
|
|
1613
2563
|
}
|
|
1614
2564
|
if (manager === PackageManager.bun) {
|
|
1615
|
-
const
|
|
1616
|
-
|
|
1617
|
-
|
|
2565
|
+
const lock = readLockfile(path.join(root, "bun.lock"), (source) => {
|
|
2566
|
+
const errors = [];
|
|
2567
|
+
const parsed = record$1(parse(source, errors, { allowTrailingComma: true }));
|
|
2568
|
+
return errors.length ? void 0 : parsed;
|
|
2569
|
+
});
|
|
2570
|
+
if (declaredVitest(record$1(lock?.workspaces)?.[relative]) !== spec) return;
|
|
1618
2571
|
const packages = record$1(lock?.packages);
|
|
1619
2572
|
const direct = packages?.vitest;
|
|
1620
2573
|
if (!Array.isArray(direct) || typeof direct[0] !== "string" || !direct[0].startsWith("vitest@")) return;
|
|
@@ -2916,7 +3869,7 @@ function migrateWebdriverioDependencies(workspace, sources, inputs) {
|
|
|
2916
3869
|
const set = (file, keys, value) => {
|
|
2917
3870
|
if (get(file, keys) === value) return;
|
|
2918
3871
|
const source = sources.get(file);
|
|
2919
|
-
if (file.endsWith(".json")) sources.set(file, applyEdits(source, modify(source, keys, value, { formattingOptions: {
|
|
3872
|
+
if (file.endsWith(".json")) sources.set(file, applyEdits$1(source, modify(source, keys, value, { formattingOptions: {
|
|
2920
3873
|
insertSpaces: true,
|
|
2921
3874
|
tabSize: 2
|
|
2922
3875
|
} })));
|
|
@@ -3182,17 +4135,16 @@ function finding(file, code, message, severity = "review", source = "", offset =
|
|
|
3182
4135
|
};
|
|
3183
4136
|
}
|
|
3184
4137
|
/** Follow this project's dependency edges, never an unrelated transitive runner. */
|
|
3185
|
-
function lockedSourceVersion(directory, root, pkg, manager, expectedRunnerPackage) {
|
|
4138
|
+
function lockedSourceVersion(directory, root, pkg, readLockfile, manager, expectedRunnerPackage) {
|
|
3186
4139
|
if (manager !== void 0 && manager !== PackageManager.pnpm) {
|
|
3187
4140
|
const spec = dependency(pkg, "vitest");
|
|
3188
|
-
return spec && !expectedRunnerPackage ? lockedVitestVersion(root, directory, manager, spec) : void 0;
|
|
4141
|
+
return spec && !expectedRunnerPackage ? lockedVitestVersion(root, directory, manager, spec, readLockfile) : void 0;
|
|
3189
4142
|
}
|
|
3190
|
-
const lockfile = path.join(root, "pnpm-lock.yaml");
|
|
3191
|
-
if (!fs.existsSync(lockfile)) return;
|
|
3192
4143
|
try {
|
|
3193
|
-
const
|
|
3194
|
-
|
|
3195
|
-
|
|
4144
|
+
const lock = readLockfile(path.join(root, "pnpm-lock.yaml"), (source) => {
|
|
4145
|
+
const document = (0, import_dist.parseDocument)(source);
|
|
4146
|
+
return document.errors.length ? void 0 : record(document.toJS());
|
|
4147
|
+
});
|
|
3196
4148
|
const importer = record(record(lock?.importers)?.[path.relative(root, directory).replaceAll("\\", "/") || "."]);
|
|
3197
4149
|
if (!importer) return;
|
|
3198
4150
|
const name = dependency(pkg, "vitest") ? "vitest" : "vite-plus";
|
|
@@ -3249,7 +4201,7 @@ function installedSourceVersion(directory, name, expectedPackage, expectedRange)
|
|
|
3249
4201
|
}
|
|
3250
4202
|
return dependency(pkg, LEGACY_RUNNER) ? installedSourceVersion(installed.path, LEGACY_RUNNER) : void 0;
|
|
3251
4203
|
}
|
|
3252
|
-
function sourceVersion(directory, root, pkg, manager) {
|
|
4204
|
+
function sourceVersion(directory, root, pkg, readLockfile, manager) {
|
|
3253
4205
|
const catalog = createCatalogDependencyResolver(root, manager ?? PackageManager.pnpm);
|
|
3254
4206
|
const vitePlus = dependency(pkg, "vite-plus");
|
|
3255
4207
|
const vitePlusRange = vitePlus?.startsWith("catalog:") ? catalog?.(vitePlus, "vite-plus") : vitePlus;
|
|
@@ -3266,7 +4218,7 @@ function sourceVersion(directory, root, pkg, manager) {
|
|
|
3266
4218
|
if (bundled) return bundled;
|
|
3267
4219
|
}
|
|
3268
4220
|
const expectedRunnerPackage = legacyAlias ? LEGACY_RUNNER : void 0;
|
|
3269
|
-
const locked = lockedSourceVersion(directory, root, pkg, manager, expectedRunnerPackage);
|
|
4221
|
+
const locked = lockedSourceVersion(directory, root, pkg, readLockfile, manager, expectedRunnerPackage);
|
|
3270
4222
|
if (locked && (!spec || legacyAlias || !normalizeRange(spec) || satisfies(locked, spec, { includePrerelease: true }))) return locked;
|
|
3271
4223
|
const installed = installedSourceVersion(directory, "vitest", expectedRunnerPackage);
|
|
3272
4224
|
if (installed && (!spec || legacyAlias || satisfies(installed, spec, { includePrerelease: true }))) return installed;
|
|
@@ -3276,7 +4228,7 @@ function sourceVersion(directory, root, pkg, manager) {
|
|
|
3276
4228
|
const bundled = installedSourceVersion(directory, "vite-plus", "vite-plus", vitePlusRange);
|
|
3277
4229
|
if (bundled) return bundled;
|
|
3278
4230
|
}
|
|
3279
|
-
if (directory !== root) return sourceVersion(root, root, readJson(path.join(root, "package.json")), manager);
|
|
4231
|
+
if (directory !== root) return sourceVersion(root, root, readJson(path.join(root, "package.json")), readLockfile, manager);
|
|
3280
4232
|
}
|
|
3281
4233
|
function checkNodeRange(file, value, label, findings, publicContract = false) {
|
|
3282
4234
|
if (!publicContract && [
|
|
@@ -3338,7 +4290,7 @@ function migrateNode(file, source, findings) {
|
|
|
3338
4290
|
"version"
|
|
3339
4291
|
]);
|
|
3340
4292
|
if (typeof pkg.volta?.node === "string" && ![".node-version", ".nvmrc"].some((name) => fs.existsSync(path.join(path.dirname(file), name)))) checkRuntime(pkg.volta.node, "volta.node", ["volta", "node"]);
|
|
3341
|
-
return applyEdits(source, edits);
|
|
4293
|
+
return applyEdits$1(source, edits);
|
|
3342
4294
|
}
|
|
3343
4295
|
return source;
|
|
3344
4296
|
}
|
|
@@ -3391,7 +4343,7 @@ function scanAndRewriteFile(file, source, project, testMode, mergedConfig) {
|
|
|
3391
4343
|
content: JSON.stringify(result.content)
|
|
3392
4344
|
});
|
|
3393
4345
|
}
|
|
3394
|
-
content = applyEdits(source, edits);
|
|
4346
|
+
content = applyEdits$1(source, edits);
|
|
3395
4347
|
} else if (/^tsconfig(?:\..+)?\.json$/.test(path.basename(file))) {
|
|
3396
4348
|
const types = record(record(parse(source))?.compilerOptions)?.types;
|
|
3397
4349
|
if (Array.isArray(types) && types.includes("@testing-library/jest-dom") && !types.includes("@testing-library/jest-dom/vitest")) findings.push(finding(file, "jest-dom-types", "If this config checks Vitest tests, load @testing-library/jest-dom/vitest in compilerOptions.types or an included TypeScript setup file. The root jest-dom type entry augments Jest, not Vitest v5."));
|
|
@@ -3422,6 +4374,7 @@ function scanAndRewriteFile(file, source, project, testMode, mergedConfig) {
|
|
|
3422
4374
|
/** Read-only preflight. Call before package-manager conversion, installs,
|
|
3423
4375
|
* catalog updates, or generic import rewrites can erase the source version. */
|
|
3424
4376
|
function planVitestV5Migration(workspace, originalProjects) {
|
|
4377
|
+
const readLockfile = createLockfileReader();
|
|
3425
4378
|
const findings = [];
|
|
3426
4379
|
const directories = [workspace.rootDir, ...(workspace.packages ?? []).map((pkg) => path.resolve(workspace.rootDir, pkg.path))];
|
|
3427
4380
|
const projects = [];
|
|
@@ -3440,7 +4393,7 @@ function planVitestV5Migration(workspace, originalProjects) {
|
|
|
3440
4393
|
const pkg = readJson(path.join(directory, "package.json"));
|
|
3441
4394
|
const configFiles = new Set([...allConfigs].filter((file) => sources.has(file)));
|
|
3442
4395
|
const original = originalProjects?.get(directory);
|
|
3443
|
-
const version = original ? original.sourceVersion : sourceVersion(directory, workspace.rootDir, pkg, workspace.packageManager);
|
|
4396
|
+
const version = original ? original.sourceVersion : sourceVersion(directory, workspace.rootDir, pkg, readLockfile, workspace.packageManager);
|
|
3444
4397
|
const active = original?.active ?? (!!dependency(pkg, "vitest") || Object.values(record(pkg.scripts) ?? {}).some((command) => typeof command === "string" && VITEST_COMMAND.test(command)) || [...sources].some(([file, source]) => CODE_FILE.test(file) && hasVitestV5SourceUsage(file, source) || configFiles.has(file) && /\btest\s*:/.test(source)));
|
|
3445
4398
|
const options = {
|
|
3446
4399
|
preserveV4: original?.options.preserveV4 ?? (!!version && getMajor(version) < 5),
|
|
@@ -3897,13 +4850,13 @@ function prepareNpmViteAliasReinstall(rootDir, projectPaths = [rootDir]) {
|
|
|
3897
4850
|
async function collectGitHooksDecision(rootDir, packageManager, options, packages = []) {
|
|
3898
4851
|
const hasExistingHooks = options.hooks === void 0 && hasExistingViteHooksPolicy(rootDir);
|
|
3899
4852
|
if (hasExistingHooks && isViteHooksSetupComplete(rootDir)) {
|
|
3900
|
-
log(`${muted("•")} Git hooks already configured, skipping`);
|
|
4853
|
+
log$1(`${muted("•")} Git hooks already configured, skipping`);
|
|
3901
4854
|
return false;
|
|
3902
4855
|
}
|
|
3903
4856
|
if (!hasExistingHooks && !await promptGitHooks(options)) return false;
|
|
3904
4857
|
const reason = preflightGitHooksSetup(rootDir, packageManager, packages);
|
|
3905
4858
|
if (reason) {
|
|
3906
|
-
log
|
|
4859
|
+
log.warn(`⚠ ${reason}`);
|
|
3907
4860
|
return false;
|
|
3908
4861
|
}
|
|
3909
4862
|
return true;
|
|
@@ -4042,7 +4995,7 @@ async function confirmYarnNodeModulesMode(rootDir, packageManager, packageManage
|
|
|
4042
4995
|
if (packageManager !== PackageManager.yarn) return false;
|
|
4043
4996
|
const pnp = detectYarnPnpMode(rootDir, packageManagerVersion);
|
|
4044
4997
|
if (!pnp) return false;
|
|
4045
|
-
log
|
|
4998
|
+
log.warn(`⚠ Vite+ does not currently support Yarn Plug'n'Play (PnP).`);
|
|
4046
4999
|
if (pnp.source === "environment") cancelAndExit("YARN_NODE_LINKER=pnp overrides project configuration. Set it to node-modules or unset it, then re-run `vp migrate`.", 1);
|
|
4047
5000
|
if (interactive) {
|
|
4048
5001
|
const confirmed = await confirm({
|
|
@@ -4146,7 +5099,7 @@ function hasExistingVitePlusMigrationCandidates(workspaceInfo, options) {
|
|
|
4146
5099
|
const eslintProject = detectEslintProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
4147
5100
|
const prettierProject = detectPrettierProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
4148
5101
|
const tsupProject = detectTsupProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
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;
|
|
5102
|
+
return hasExplicitExistingVitePlusSetupRequest(options) || detectLegacyGitHooksMigrationCandidate(workspaceInfo.rootDir) || hasBaseUrlInWorkspace(workspaceInfo) || eslintProject.hasDependency || prettierProject.hasDependency || tsupProject.hasDependency || detectTsxProject(workspaceInfo.rootDir, workspaceInfo.packages) || detectOxnodeProject(workspaceInfo.rootDir, workspaceInfo.packages) || detectTsNodeProject(workspaceInfo.rootDir, workspaceInfo.packages) || detectNodeVersionManagerFile(workspaceInfo.rootDir) !== void 0 || getFrameworkShimCandidates(workspaceInfo.rootDir, workspaceInfo.packages).length > 0;
|
|
4150
5103
|
}
|
|
4151
5104
|
function getExistingVitePlusSetupOptions(options, useFullMigrationDefaults = false) {
|
|
4152
5105
|
if (useFullMigrationDefaults) return options;
|
|
@@ -4167,6 +5120,8 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
|
|
|
4167
5120
|
else if (prettierProject.hasDependency) warnPackageLevelPrettier();
|
|
4168
5121
|
const { migrateTsup, tsupConfigFile } = await collectTsupMigrationDecision(rootDir, options, packages);
|
|
4169
5122
|
const migrateTsx = detectTsxProject(rootDir, packages) && await confirmTsxMigration(options.interactive);
|
|
5123
|
+
const migrateOxnode = detectOxnodeProject(rootDir, packages) && await confirmOxnodeMigration(options.interactive);
|
|
5124
|
+
const migrateTsNode = detectTsNodeProject(rootDir, packages) && await confirmTsNodeMigration(options.interactive);
|
|
4170
5125
|
const fixBaseUrl = hasBaseUrlInWorkspace({
|
|
4171
5126
|
rootDir,
|
|
4172
5127
|
packages
|
|
@@ -4185,6 +5140,8 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
|
|
|
4185
5140
|
migrateTsup,
|
|
4186
5141
|
tsupConfigFile,
|
|
4187
5142
|
migrateTsx,
|
|
5143
|
+
migrateOxnode,
|
|
5144
|
+
migrateTsNode,
|
|
4188
5145
|
fixBaseUrl,
|
|
4189
5146
|
migrateNodeVersionFile,
|
|
4190
5147
|
nodeVersionDetection,
|
|
@@ -4196,52 +5153,56 @@ function showMigrationSummary(options) {
|
|
|
4196
5153
|
const { projectRoot, packageManager, packageManagerVersion, installDurationMs, finalInstallOk, report, updatedExistingVitePlus, suggestFullMigration } = options;
|
|
4197
5154
|
const projectLabel = displayRelative(projectRoot) || ".";
|
|
4198
5155
|
const configUpdates = report.createdViteConfigCount + report.mergedConfigCount + report.mergedStagedConfigCount + report.inlinedLintStagedConfigCount + report.removedConfigCount + report.tsdownImportCount + report.wrappedPluginConfigCount + report.migratedTaskCacheConfigCount;
|
|
4199
|
-
log(`${styleText("magenta", "◇")} ${updatedExistingVitePlus ? "Updated" : "Migrated"} ${accent(projectLabel)} to Vite+ ${VITE_PLUS_VERSION}`);
|
|
4200
|
-
log(`${styleText("gray", "•")} Node ${process.versions.node} ${packageManager} ${packageManagerVersion}`);
|
|
5156
|
+
log$1(`${styleText("magenta", "◇")} ${updatedExistingVitePlus ? "Updated" : "Migrated"} ${accent(projectLabel)} to Vite+ ${VITE_PLUS_VERSION}`);
|
|
5157
|
+
log$1(`${styleText("gray", "•")} Node ${process.versions.node} ${packageManager} ${packageManagerVersion}`);
|
|
4201
5158
|
if (report.dependencyUpgrades.length > 0) {
|
|
4202
5159
|
const nameWidth = Math.max(...report.dependencyUpgrades.map((change) => change.name.length));
|
|
4203
5160
|
const fromWidth = Math.max(...report.dependencyUpgrades.map((change) => (change.from ?? "").length));
|
|
4204
|
-
log(`${styleText("gray", "•")} Dependencies:`);
|
|
5161
|
+
log$1(`${styleText("gray", "•")} Dependencies:`);
|
|
4205
5162
|
for (const change of report.dependencyUpgrades) {
|
|
4206
5163
|
const name = change.name.padEnd(nameWidth);
|
|
4207
5164
|
const from = muted((change.from ?? "").padEnd(fromWidth));
|
|
4208
5165
|
const to = styleText("green", change.to);
|
|
4209
|
-
log(` ${name} ${from} → ${to}`);
|
|
5166
|
+
log$1(` ${name} ${from} → ${to}`);
|
|
4210
5167
|
}
|
|
4211
5168
|
}
|
|
4212
|
-
if (finalInstallOk && installDurationMs > 0) log(`${styleText("green", "✓")} Dependencies installed in ${formatDuration(installDurationMs)}`);
|
|
5169
|
+
if (finalInstallOk && installDurationMs > 0) log$1(`${styleText("green", "✓")} Dependencies installed in ${formatDuration(installDurationMs)}`);
|
|
4213
5170
|
if (configUpdates > 0 || report.rewrittenImportFileCount > 0) {
|
|
4214
5171
|
const parts = [];
|
|
4215
5172
|
if (configUpdates > 0) parts.push(`${configUpdates} ${configUpdates === 1 ? "config update" : "config updates"} applied`);
|
|
4216
5173
|
if (report.rewrittenImportFileCount > 0) parts.push(`${report.rewrittenImportFileCount} ${report.rewrittenImportFileCount === 1 ? "file had" : "files had"} imports rewritten`);
|
|
4217
|
-
log(`${styleText("gray", "•")} ${parts.join(", ")}`);
|
|
4218
|
-
}
|
|
4219
|
-
if (report.preservedUpstreamVitestImportFileCount > 0) log(`${styleText("gray", "•")} Kept upstream \`vitest\` imports in ${report.preservedUpstreamVitestImportFileCount} ${report.preservedUpstreamVitestImportFileCount === 1 ? "file" : "files"} for @nuxt/test-utils compatibility`);
|
|
4220
|
-
if (report.eslintMigrated) log(`${styleText("gray", "•")} ESLint rules migrated to Oxlint`);
|
|
4221
|
-
if (report.prettierMigrated) log(`${styleText("gray", "•")} Prettier migrated to Oxfmt`);
|
|
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
|
|
4224
|
-
if (report.
|
|
5174
|
+
log$1(`${styleText("gray", "•")} ${parts.join(", ")}`);
|
|
5175
|
+
}
|
|
5176
|
+
if (report.preservedUpstreamVitestImportFileCount > 0) log$1(`${styleText("gray", "•")} Kept upstream \`vitest\` imports in ${report.preservedUpstreamVitestImportFileCount} ${report.preservedUpstreamVitestImportFileCount === 1 ? "file" : "files"} for @nuxt/test-utils compatibility`);
|
|
5177
|
+
if (report.eslintMigrated) log$1(`${styleText("gray", "•")} ESLint rules migrated to Oxlint`);
|
|
5178
|
+
if (report.prettierMigrated) log$1(`${styleText("gray", "•")} Prettier migrated to Oxfmt`);
|
|
5179
|
+
if (report.tsupMigrated) log$1(`${styleText("gray", "•")} tsup config migrated to tsdown (\`vp pack\`)`);
|
|
5180
|
+
if (report.tsxMigrated) log$1(`${styleText("gray", "•")} tsx scripts migrated to Vite+`);
|
|
5181
|
+
if (report.oxnodeMigrated) log$1(`${styleText("gray", "•")} oxnode scripts migrated to Vite+`);
|
|
5182
|
+
if (report.tsNodeMigrated) log$1(`${styleText("gray", "•")} ts-node scripts migrated to Vite+`);
|
|
5183
|
+
if (report.nodeVersionFileMigrated) log$1(`${styleText("gray", "•")} Node version manager file migrated to .node-version`);
|
|
4225
5184
|
const setupVpFileCount = report.setupVpVersionUpdatedFileCount;
|
|
4226
5185
|
if (setupVpFileCount > 0) {
|
|
4227
5186
|
const fileLabel = setupVpFileCount === 1 ? "file" : "files";
|
|
4228
|
-
log(`${styleText("gray", "•")} setup-vp updated to ${SETUP_VP_VERSION} in ${setupVpFileCount} GitHub Actions ${fileLabel}`);
|
|
4229
|
-
}
|
|
4230
|
-
if (report.wrappedPluginConfigCount > 0) log(`${styleText("gray", "•")} Inline Vite plugins wrapped with lazyPlugins for check/lint/fmt`);
|
|
4231
|
-
if (report.migratedTaskCacheConfigCount > 0) log(`${styleText("gray", "•")} Task cache settings moved under \`cache\``);
|
|
4232
|
-
if (report.gitHooksConfigured) log(`${styleText("gray", "•")} Git hooks configured`);
|
|
4233
|
-
if (report.frameworkShimAdded) log(`${styleText("gray", "•")} TypeScript shim added for framework component files`);
|
|
4234
|
-
if (report.packageManagerBootstrapConfigured) log(`${styleText("gray", "•")} Package manager settings configured`);
|
|
4235
|
-
if (suggestFullMigration) log(FULL_MIGRATION_HINT);
|
|
4236
|
-
|
|
4237
|
-
log(`${styleText("yellow", "!")} Warnings:`);
|
|
4238
|
-
for (const warning of report.warnings) log(` - ${warning}`);
|
|
4239
|
-
}
|
|
5187
|
+
log$1(`${styleText("gray", "•")} setup-vp updated to ${SETUP_VP_VERSION} in ${setupVpFileCount} GitHub Actions ${fileLabel}`);
|
|
5188
|
+
}
|
|
5189
|
+
if (report.wrappedPluginConfigCount > 0) log$1(`${styleText("gray", "•")} Inline Vite plugins wrapped with lazyPlugins for check/lint/fmt`);
|
|
5190
|
+
if (report.migratedTaskCacheConfigCount > 0) log$1(`${styleText("gray", "•")} Task cache settings moved under \`cache\``);
|
|
5191
|
+
if (report.gitHooksConfigured) log$1(`${styleText("gray", "•")} Git hooks configured`);
|
|
5192
|
+
if (report.frameworkShimAdded) log$1(`${styleText("gray", "•")} TypeScript shim added for framework component files`);
|
|
5193
|
+
if (report.packageManagerBootstrapConfigured) log$1(`${styleText("gray", "•")} Package manager settings configured`);
|
|
5194
|
+
if (suggestFullMigration) log$1(FULL_MIGRATION_HINT);
|
|
5195
|
+
showMigrationWarnings(report.warnings);
|
|
4240
5196
|
if (report.manualSteps.length > 0) {
|
|
4241
|
-
log(`${styleText("blue", "→")} Manual follow-up:`);
|
|
4242
|
-
for (const step of report.manualSteps) log(` - ${step}`);
|
|
5197
|
+
log$1(`${styleText("blue", "→")} Manual follow-up:`);
|
|
5198
|
+
for (const step of report.manualSteps) log$1(` - ${step}`);
|
|
4243
5199
|
}
|
|
4244
5200
|
}
|
|
5201
|
+
function showMigrationWarnings(warnings) {
|
|
5202
|
+
if (warnings.length === 0) return;
|
|
5203
|
+
log$1(`${styleText("yellow", "!")} Warnings:`);
|
|
5204
|
+
for (const warning of warnings) log$1(` - ${warning}`);
|
|
5205
|
+
}
|
|
4245
5206
|
async function downloadSupportedPackageManager(options) {
|
|
4246
5207
|
const { rootDir, packageManager, packageManagerVersion, interactive, updateMigrationProgress, failMigrationProgress } = options;
|
|
4247
5208
|
updateMigrationProgress("Preparing migration");
|
|
@@ -4251,11 +5212,11 @@ async function downloadSupportedPackageManager(options) {
|
|
|
4251
5212
|
await upgradeYarn(rootDir, interactive, true);
|
|
4252
5213
|
} else if (packageManager === PackageManager.pnpm && satisfies(downloadResult.version, "< 9.5.0")) {
|
|
4253
5214
|
failMigrationProgress("Migration failed");
|
|
4254
|
-
log
|
|
5215
|
+
log.error(`✘ pnpm@${downloadResult.version} is not supported by auto migration, please upgrade pnpm to >=9.5.0 first`);
|
|
4255
5216
|
cancelAndExit("Vite+ cannot automatically migrate this project yet.", 1);
|
|
4256
5217
|
} else if (packageManager === PackageManager.npm && satisfies(downloadResult.version, "< 8.3.0")) {
|
|
4257
5218
|
failMigrationProgress("Migration failed");
|
|
4258
|
-
log
|
|
5219
|
+
log.error(`✘ npm@${downloadResult.version} is not supported by auto migration, please upgrade npm to >=8.3.0 first`);
|
|
4259
5220
|
cancelAndExit("Vite+ cannot automatically migrate this project yet.", 1);
|
|
4260
5221
|
}
|
|
4261
5222
|
return downloadResult;
|
|
@@ -4272,7 +5233,7 @@ function completeVitestV5Migration(plan, report) {
|
|
|
4272
5233
|
function applyRefreshedVitestV5Migration(plan) {
|
|
4273
5234
|
const refreshed = refreshVitestV5Migration(plan);
|
|
4274
5235
|
if (refreshed.findings.some((finding) => finding.severity === "block")) {
|
|
4275
|
-
log
|
|
5236
|
+
log.warn(formatVitestV5Findings(refreshed));
|
|
4276
5237
|
cancelAndExit("Resolve the blocking Vitest v5 findings, then re-run `vp migrate`. Earlier setup steps may have changed project files; Vitest v5 edits were not applied.", 1);
|
|
4277
5238
|
}
|
|
4278
5239
|
applyVitestV5Migration(refreshed);
|
|
@@ -4280,6 +5241,7 @@ function applyRefreshedVitestV5Migration(plan) {
|
|
|
4280
5241
|
}
|
|
4281
5242
|
async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vitestV5Plan, preExistingChangedPaths) {
|
|
4282
5243
|
const report = createMigrationReport();
|
|
5244
|
+
report.warnings.push(...detectMigrationWarnings(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages));
|
|
4283
5245
|
const migrationProgress = interactive ? spinner({ indicator: "timer" }) : void 0;
|
|
4284
5246
|
let migrationProgressStarted = false;
|
|
4285
5247
|
const updateMigrationProgress = (message) => {
|
|
@@ -4320,7 +5282,7 @@ async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vi
|
|
|
4320
5282
|
if (plan.convertYarnPnp) {
|
|
4321
5283
|
updateMigrationProgress("Configuring Yarn node-modules mode");
|
|
4322
5284
|
report.packageManagerBootstrapConfigured = configureYarnNodeModulesMode(workspaceInfo.rootDir);
|
|
4323
|
-
if (report.packageManagerBootstrapConfigured) log
|
|
5285
|
+
if (report.packageManagerBootstrapConfigured) log.success("✔ Switched Yarn to node-modules mode");
|
|
4324
5286
|
}
|
|
4325
5287
|
if (plan.migrateNodeVersionFile && plan.nodeVersionDetection) {
|
|
4326
5288
|
updateMigrationProgress("Migrating node version file");
|
|
@@ -4378,6 +5340,14 @@ async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vi
|
|
|
4378
5340
|
updateMigrationProgress("Migrating tsx scripts");
|
|
4379
5341
|
migrateTsxToVpx(workspaceInfo.rootDir, workspaceInfo.packages, report);
|
|
4380
5342
|
}
|
|
5343
|
+
if (plan.migrateOxnode) {
|
|
5344
|
+
updateMigrationProgress("Migrating oxnode scripts");
|
|
5345
|
+
migrateOxnodeToVpx(workspaceInfo.rootDir, workspaceInfo.packages, report);
|
|
5346
|
+
}
|
|
5347
|
+
if (plan.migrateTsNode) {
|
|
5348
|
+
updateMigrationProgress("Migrating ts-node scripts");
|
|
5349
|
+
migrateTsNodeToVpx(workspaceInfo.rootDir, workspaceInfo.packages, report);
|
|
5350
|
+
}
|
|
4381
5351
|
vitestV5Plan = applyRefreshedVitestV5Migration(vitestV5Plan);
|
|
4382
5352
|
const skipStagedMigration = shouldSkipStagedMigrationForHooks(workspaceInfo.rootDir, plan.shouldSetupHooks, plan.packageManager, workspaceInfo.packages);
|
|
4383
5353
|
updateMigrationProgress("Rewriting configs");
|
|
@@ -4443,11 +5413,11 @@ async function main() {
|
|
|
4443
5413
|
let vitestV5Plan = planVitestV5Migration(workspaceInfoOptional);
|
|
4444
5414
|
const vitestV5Preflight = formatVitestV5Findings(vitestV5Plan);
|
|
4445
5415
|
if (vitestV5Plan.findings.some((finding) => finding.severity === "block")) {
|
|
4446
|
-
log
|
|
5416
|
+
log.warn(vitestV5Preflight);
|
|
4447
5417
|
cancelAndExit("Resolve the blocking Vitest v5 findings, then re-run `vp migrate`. No project files were changed.", 1);
|
|
4448
5418
|
}
|
|
4449
5419
|
const rootPkg = readNearestPackageJson(workspaceInfoOptional.rootDir);
|
|
4450
|
-
if (workspaceInfoOptional.packageManager && !hasPackageManagerDeclaration(rootPkg)) log
|
|
5420
|
+
if (workspaceInfoOptional.packageManager && !hasPackageManagerDeclaration(rootPkg)) log.warn(`No package manager is declared in package.json; using ${workspaceInfoOptional.packageManager} for this migration without adding a pin. Run \`vp env pin\` to declare it explicitly.`);
|
|
4451
5421
|
if (hasVitePlusDependency(rootPkg) && !isForceOverrideMode()) {
|
|
4452
5422
|
let convertYarnPnp = await confirmYarnNodeModulesMode(workspaceInfoOptional.rootDir, workspaceInfoOptional.packageManager, workspaceInfoOptional.packageManagerVersion, options.interactive);
|
|
4453
5423
|
let didMigrate = vitestV5NeedsMigration(vitestV5Plan);
|
|
@@ -4456,6 +5426,7 @@ async function main() {
|
|
|
4456
5426
|
let finalInstallOk = true;
|
|
4457
5427
|
let canFormatMigratedProject = !process.env.VP_SKIP_INSTALL;
|
|
4458
5428
|
const report = createMigrationReport();
|
|
5429
|
+
const migrationWarnings = options.full ? detectMigrationWarnings(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages) : [];
|
|
4459
5430
|
const migrationProgress = options.interactive ? spinner({ indicator: "timer" }) : void 0;
|
|
4460
5431
|
let migrationProgressStarted = false;
|
|
4461
5432
|
const updateMigrationProgress = (message) => {
|
|
@@ -4515,12 +5486,14 @@ async function main() {
|
|
|
4515
5486
|
const fullSetup = options.full === true;
|
|
4516
5487
|
const skippedSetupCandidates = !fullSetup && !hasExplicitExistingVitePlusSetupRequest(options) && hasExistingVitePlusMigrationCandidates(workspaceInfoOptional, options);
|
|
4517
5488
|
if (!didMigrate && !convertYarnPnp && report.warnings.length === 0 && !vitePlusBootstrapPending && !(fullSetup ? hasExistingVitePlusMigrationCandidates(workspaceInfoOptional, options) : hasExplicitExistingVitePlusSetupRequest(options))) {
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
5489
|
+
showMigrationWarnings(migrationWarnings);
|
|
5490
|
+
if (vitestV5Preflight) log.warn(vitestV5Preflight);
|
|
5491
|
+
for (const warning of coreMigrationResult.taskCacheWarnings) log.warn(warning);
|
|
5492
|
+
if (skippedSetupCandidates) log$1(FULL_MIGRATION_HINT);
|
|
4521
5493
|
outro(`This project is already using Vite+! ${accent("Happy coding!")}`);
|
|
4522
5494
|
return;
|
|
4523
5495
|
}
|
|
5496
|
+
report.warnings.push(...migrationWarnings);
|
|
4524
5497
|
for (const warning of coreMigrationResult.taskCacheWarnings) addMigrationWarning(report, warning);
|
|
4525
5498
|
const setupOptions = getExistingVitePlusSetupOptions(options, fullSetup);
|
|
4526
5499
|
const plan = await collectMigrationSetupPlan(workspaceInfoOptional.rootDir, packageManager, setupOptions, workspaceInfoOptional.packages, fullSetup);
|
|
@@ -4610,6 +5583,24 @@ async function main() {
|
|
|
4610
5583
|
needsInstall ||= tsxResult.dependencyRemoved;
|
|
4611
5584
|
}
|
|
4612
5585
|
}
|
|
5586
|
+
if (fullSetup && detectOxnodeProject(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages)) {
|
|
5587
|
+
if (options.interactive) clearMigrationProgress();
|
|
5588
|
+
if (await confirmOxnodeMigration(options.interactive)) {
|
|
5589
|
+
updateMigrationProgress("Migrating oxnode scripts");
|
|
5590
|
+
const oxnodeResult = migrateOxnodeToVpx(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages, report);
|
|
5591
|
+
didMigrate ||= oxnodeResult.changed;
|
|
5592
|
+
needsInstall ||= oxnodeResult.dependencyRemoved;
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
if (fullSetup && detectTsNodeProject(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages)) {
|
|
5596
|
+
if (options.interactive) clearMigrationProgress();
|
|
5597
|
+
if (await confirmTsNodeMigration(options.interactive)) {
|
|
5598
|
+
updateMigrationProgress("Migrating ts-node scripts");
|
|
5599
|
+
const tsNodeResult = migrateTsNodeToVpx(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages, report);
|
|
5600
|
+
didMigrate ||= tsNodeResult.changed;
|
|
5601
|
+
needsInstall ||= tsNodeResult.dependencyRemoved;
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
4613
5604
|
if (fullSetup) {
|
|
4614
5605
|
const nodeVersionDetection = detectNodeVersionManagerFile(workspaceInfoOptional.rootDir);
|
|
4615
5606
|
if (nodeVersionDetection) {
|
|
@@ -4620,7 +5611,7 @@ async function main() {
|
|
|
4620
5611
|
if (convertYarnPnp) {
|
|
4621
5612
|
updateMigrationProgress("Configuring Yarn node-modules mode");
|
|
4622
5613
|
if (configureYarnNodeModulesMode(workspaceInfoOptional.rootDir)) {
|
|
4623
|
-
log
|
|
5614
|
+
log.success("✔ Switched Yarn to node-modules mode");
|
|
4624
5615
|
report.packageManagerBootstrapConfigured = true;
|
|
4625
5616
|
didMigrate = true;
|
|
4626
5617
|
needsInstall = true;
|
|
@@ -4723,7 +5714,7 @@ async function main() {
|
|
|
4723
5714
|
});
|
|
4724
5715
|
} else {
|
|
4725
5716
|
clearMigrationProgress();
|
|
4726
|
-
if (skippedSetupCandidates) log(FULL_MIGRATION_HINT);
|
|
5717
|
+
if (skippedSetupCandidates) log$1(FULL_MIGRATION_HINT);
|
|
4727
5718
|
outro(`This project is already using Vite+! ${accent("Happy coding!")}`);
|
|
4728
5719
|
}
|
|
4729
5720
|
return;
|
|
@@ -4740,7 +5731,7 @@ async function main() {
|
|
|
4740
5731
|
});
|
|
4741
5732
|
}
|
|
4742
5733
|
main().catch((err) => {
|
|
4743
|
-
log
|
|
5734
|
+
log.error(err.message);
|
|
4744
5735
|
console.error(err);
|
|
4745
5736
|
process.exit(1);
|
|
4746
5737
|
});
|