vite-plus 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/LICENSE +1 -878
- package/README.md +1 -1
- package/bin/vpx +9 -0
- package/binding/index.cjs +59 -54
- package/binding/index.d.cts +159 -2
- package/dist/{agent-97KmGCyL.js → agent-DkQD3xqe.js} +5 -7
- package/dist/bin.js +35 -34
- package/dist/{cli-parse-DHE8NrBU.js → cli-parse-aeUeFDlC.js} +2 -3
- package/dist/command-TjS_6y9O.js +1 -0
- package/dist/{command-CdMBoZFD.js → command-rY79K5NI.js} +28 -30
- package/dist/{comparison-CmVirWIW-DUGaiYqF.js → comparison-CmVirWIW-UlEf2maz.js} +1 -2
- package/dist/config/bin.js +8 -10
- package/dist/{constants-DR55DpRm.js → constants-D2P2aVKb.js} +8 -6
- package/dist/create/bin.d.ts +4 -8
- package/dist/create/bin.js +151 -120
- package/dist/{define-config-Bexdd6RZ.d.ts → define-config-BrvHRi-G.d.ts} +31 -13
- package/dist/{define-config-BxDc_9BK.cjs → define-config-CwtQqYC1.cjs} +41 -14
- package/dist/{define-config-CvE5pHqB.js → define-config-wR57OZG_.js} +34 -12
- package/dist/define-config.cjs +2 -1
- package/dist/define-config.d.ts +2 -2
- package/dist/define-config.js +1 -1
- package/dist/{dist-DIpm7mot.js → dist-Bs0SObNC.js} +73 -145
- package/dist/{dist-Cqz8OtiO.js → dist-CdkMvIiQ.js} +11 -5
- package/dist/dist-zDZV5eEc.js +3 -0
- package/dist/{documentation-DqcZSthV.js → documentation-cv_y0Auv.js} +1 -2
- package/dist/{editor-CtJGQP7C.js → editor-aOy1T15H.js} +2735 -5072
- package/dist/{help-BmKpeOP9.js → help-B03t_iQ4.js} +1 -2
- package/dist/hooks/bin.js +4 -5
- package/dist/{hooks-DhwsWb46.js → hooks-w_e2whkQ.js} +45 -25
- package/dist/index.cjs +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{json-JP24qHUF.js → json-kl84kDyH.js} +9 -18
- package/dist/lint-plugins.cjs +1 -1
- package/dist/migration/bin.js +1782 -256
- package/dist/migration/compat/worker.js +4 -6
- package/dist/{oxlint-plugin-config-BIzrO2y2.js → oxlint-plugin-config-CHsI_zKo.js} +3 -4
- package/dist/oxlint-plugin.d.ts +0 -2
- package/dist/oxlint-plugin.js +33 -9
- package/dist/{pack-BK38MLVZ.d.ts → pack-ARPFMefW.d.ts} +0 -2
- package/dist/pack-bin.d.ts +1 -3
- package/dist/pack-bin.js +4 -5
- package/dist/pack.d.ts +1 -1
- package/dist/{package-CQDXTm6j.js → package-DfFo6-E-.js} +10 -95
- package/dist/{package-overrides-D1RcXSFl.js → package-overrides-Ci8GXouY.js} +5 -8
- package/dist/picomatch-DEUoj81q.js +1916 -0
- package/dist/{prompts-1xNz_ne0.js → prompts-C7DI63WP.js} +30 -50
- package/dist/{protocol-D99W10Qi.js → protocol-CkNmY-Mr.js} +1 -2
- package/dist/register.d.ts +1 -0
- package/dist/register.js +12 -0
- package/dist/{report-NhQk8VHb.js → report-BEdpIQvC.js} +4 -2
- package/dist/resolve-vite-config-33bzSaeT.js +1 -0
- package/dist/{resolve-vite-config-BVr1SUNg.js → resolve-vite-config-Cx3cnVVI.js} +9 -25
- package/dist/{rolldown-runtime-kJ_i4yPo.js → rolldown-runtime-DegdyBIC.js} +1 -2
- package/dist/script-esm-hooks.d.ts +11 -0
- package/dist/script-esm-hooks.js +27 -0
- package/dist/script-hooks-9Fof5UMY.js +176 -0
- package/dist/script-preload.cjs +491 -0
- package/dist/script-register.d.ts +1 -0
- package/dist/script-register.js +27 -0
- package/dist/script-watch.d.ts +1 -0
- package/dist/script-watch.js +2002 -0
- package/dist/staged/bin.js +53 -2011
- package/dist/sync-versions/bin.js +26 -48
- package/dist/sync-versions/bin.mjs +109 -217
- package/dist/{terminal-MKGAuy-p.js → terminal-BcuPUy-j.js} +1 -2
- package/dist/toolchain.js +38 -12
- package/dist/toolchain.json +38 -12
- package/dist/{tsconfig-zWgHiQ3X.js → tsconfig-BE3Wj9OX.js} +1 -1
- package/dist/{tsconfig-BZu7kl-x.js → tsconfig-shNmmCE_.js} +8 -10
- package/dist/version.d.ts +1 -3
- package/dist/version.js +5 -6
- package/dist/versions.js +7 -7
- package/dist/{vitest-ecosystem-BU0AqZq-.js → vitest-ecosystem-BeEYz-qF.js} +1 -2
- package/dist/{vitest-v5-entry-points-DthU1e_O.js → vitest-v5-entry-points-NyJsFeux.js} +1 -2
- package/dist/vpx-bin.d.ts +1 -0
- package/dist/vpx-bin.js +232 -0
- package/dist/vpx-script-EXgV5QOf.js +434 -0
- 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 +10 -2
- package/docs/config/staged.md +1 -1
- package/docs/guide/check.md +6 -1
- package/docs/guide/ci.md +13 -3
- package/docs/guide/create.md +1 -1
- package/docs/guide/env.md +11 -4
- package/docs/guide/global-cli.md +59 -3
- 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/local-cli.md +21 -0
- package/docs/guide/migrate-rules.md +120 -18
- package/docs/guide/migrate.md +9 -9
- package/docs/guide/package-management.md +1 -1
- 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 +210 -9
- package/package.json +43 -34
- package/dist/command-DVTILtS-.js +0 -1
- package/dist/dist-Dhu07wmN.js +0 -3
- package/dist/resolve-vite-config-7WDtXO4G.js +0 -1
package/dist/migration/bin.js
CHANGED
|
@@ -1,129 +1,1544 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
6
|
-
import { t as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import { i as
|
|
11
|
-
import { n as addMigrationWarning, r as createMigrationReport } from "../report-
|
|
12
|
-
import {
|
|
13
|
-
import { n as
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { t as
|
|
17
|
-
import { t as
|
|
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-C7DI63WP.js";
|
|
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";
|
|
3
|
+
import { i as selectAgentTargetPaths, l as displayRelative, n as detectAgentConflicts, r as detectExistingAgentTargetPaths, s as writeAgentInstructions } from "../agent-DkQD3xqe.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-D2P2aVKb.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-aOy1T15H.js";
|
|
7
|
+
import { a as hasVitePlusDependency, i as hasPackageManagerDeclaration, n as detectPackageMetadata, o as readNearestPackageJson } from "../package-DfFo6-E-.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-shNmmCE_.js";
|
|
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";
|
|
15
|
+
import { t as require_dist } from "../dist-Bs0SObNC.js";
|
|
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";
|
|
18
|
+
import { i as WATCH_OPTIONS_WITH_VALUE, n as SCRIPT_EXTENSIONS, t as NODE_OPTIONS_WITH_VALUE } from "../vpx-script-EXgV5QOf.js";
|
|
18
19
|
import path from "node:path";
|
|
19
20
|
import { fileURLToPath } from "node:url";
|
|
20
|
-
import { analyzeMigrationSource, isDirectoryGitignored, parseMigrateArgs } from "../../binding/index.js";
|
|
21
|
+
import { ResolverFactory, analyzeMigrationSource, isDirectoryGitignored, parseLiteralCommands, parseMigrateArgs, parseSync } from "../../binding/index.js";
|
|
21
22
|
import { styleText } from "node:util";
|
|
22
23
|
import fs, { existsSync } from "node:fs";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
// src/migration/migrator/tsx.ts
|
|
26
|
+
const TSX_COMMAND = /(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?(?:\/(?:esm|cjs))?(?=$|[\s;&|)"'`])/i;
|
|
27
|
+
const TSX_LOADER = /^tsx(?:\/(?:esm|cjs))?$/;
|
|
28
|
+
const KEEP_EVAL = "`vpx` does not run TypeScript passed to --eval or --print";
|
|
29
|
+
const KEEP_CHECK = "`vpx` does not transform TypeScript for -c or --check";
|
|
30
|
+
const KEEP_REPL$1 = "`vpx` does not start a TypeScript REPL";
|
|
31
|
+
const KEEP_TEST_GLOBS = "`--test` without files finds `.ts` tests with tsx globs; list the test files for `vpx --test`";
|
|
32
|
+
const KEEP_WATCH_PATH = "`--watch-path` has no `vpx` equivalent; use `--include`";
|
|
33
|
+
const KEEP_NODE_OPTIONS = "`NODE_OPTIONS` loads tsx, whose hooks would compete with those of `vpx`; remove it first";
|
|
34
|
+
const KEEP_OPTIONS = /* @__PURE__ */ new Map([
|
|
35
|
+
["-e", KEEP_EVAL],
|
|
36
|
+
["--eval", KEEP_EVAL],
|
|
37
|
+
["-p", KEEP_EVAL],
|
|
38
|
+
["--print", KEEP_EVAL],
|
|
39
|
+
["-pe", KEEP_EVAL],
|
|
40
|
+
["-c", KEEP_CHECK],
|
|
41
|
+
["--check", KEEP_CHECK],
|
|
42
|
+
["-i", KEEP_REPL$1],
|
|
43
|
+
["--interactive", KEEP_REPL$1],
|
|
44
|
+
["--watch-path", KEEP_WATCH_PATH]
|
|
45
|
+
]);
|
|
46
|
+
const TSX_ENV_SHEBANG = /^(#!\s*\S*\benv\s+(?:-S\s+)?)(tsx(?=[ \t\r\n]|$)[^\r\n]*)/;
|
|
47
|
+
const TSX_SHEBANG = /^#![^\n]*[\s/]tsx(?=\s|$)/;
|
|
48
|
+
const TSX_LOADER_FLAG = /(?:(?:--import|--loader|--experimental-loader|--require|-r)[= \t]+|(?<![\w-])(?:import|loader|experimental-loader|require)=)['"]?tsx(?:\/(?:esm|cjs))?(?![\w./-])/;
|
|
49
|
+
const TSX_CODE_REFERENCE = new RegExp([
|
|
50
|
+
/(?:\bfrom|\bimport|\bimport\.meta\.resolve|\brequire(?:\.resolve)?)\s*\(?\s*(?:\/\*[\s\S]*?\*\/\s*)*['"`]tsx(?:\/[^'"`]*)?['"`]/.source,
|
|
51
|
+
/['"`](?:npx |pnpm (?:exec )?|yarn |bunx )?tsx\s/.source,
|
|
52
|
+
/\(\s*\[?\s*['"`]tsx['"`]\s*[,)\]]/.source,
|
|
53
|
+
TSX_LOADER_FLAG.source,
|
|
54
|
+
/['"`](?:--import|--loader|--experimental-loader|--require|-r)['"`]\s*,\s*['"`]tsx(?:\/(?:esm|cjs))?['"`]/.source
|
|
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/;
|
|
61
|
+
const TSX_TEXT_REFERENCE = new RegExp([/(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?(?:\/(?:esm|cjs))?['"]?(?:[ \t]+\S|[ \t]*$)/.source, TSX_LOADER_FLAG.source].join("|"), "m");
|
|
62
|
+
const TSX_CONFIG_VALUE = new RegExp([
|
|
63
|
+
/(?:^|[\s;&|("'=/\\`])tsx(?:\.(?:cmd|exe|ps1))?[ \t]+\S/.source,
|
|
64
|
+
TSX_LOADER_FLAG.source,
|
|
65
|
+
/^tsx\//.source
|
|
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;
|
|
57
135
|
}
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
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`;
|
|
65
145
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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`";
|
|
259
|
+
const IGNORED_FIELDS = /* @__PURE__ */ new Set([
|
|
260
|
+
"bundleDependencies",
|
|
261
|
+
"bundledDependencies",
|
|
262
|
+
"dependencies",
|
|
263
|
+
"description",
|
|
264
|
+
"devDependencies",
|
|
265
|
+
"keywords",
|
|
266
|
+
"name",
|
|
267
|
+
"optionalDependencies",
|
|
268
|
+
"overrides",
|
|
269
|
+
"peerDependencies",
|
|
270
|
+
"peerDependenciesMeta",
|
|
271
|
+
"pnpm",
|
|
272
|
+
"resolutions"
|
|
273
|
+
]);
|
|
274
|
+
const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
275
|
+
...SCRIPT_EXTENSIONS,
|
|
276
|
+
".astro",
|
|
277
|
+
".svelte",
|
|
278
|
+
".vue"
|
|
279
|
+
]);
|
|
280
|
+
const REFERENCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
281
|
+
...CODE_EXTENSIONS,
|
|
282
|
+
".json",
|
|
283
|
+
".jsonc",
|
|
284
|
+
".toml",
|
|
285
|
+
".yml",
|
|
286
|
+
".yaml",
|
|
287
|
+
".sh",
|
|
288
|
+
".bash",
|
|
289
|
+
".zsh",
|
|
290
|
+
".fish",
|
|
291
|
+
".ps1",
|
|
292
|
+
".cmd",
|
|
293
|
+
".bat"
|
|
294
|
+
]);
|
|
295
|
+
const LOCKFILES = /* @__PURE__ */ new Set([
|
|
296
|
+
"bun.lock",
|
|
297
|
+
"npm-shrinkwrap.json",
|
|
298
|
+
"package-lock.json",
|
|
299
|
+
"pnpm-lock.yaml",
|
|
300
|
+
"yarn.lock"
|
|
301
|
+
]);
|
|
302
|
+
const USE_SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
303
|
+
...RETENTION_SCAN_SKIP_DIRS,
|
|
304
|
+
".cache",
|
|
305
|
+
".next",
|
|
306
|
+
".nuxt",
|
|
307
|
+
".svelte-kit",
|
|
308
|
+
".turbo",
|
|
309
|
+
".vite",
|
|
310
|
+
"coverage"
|
|
311
|
+
]);
|
|
312
|
+
const shellCommand = (previous) => /^-[a-z]*c$/.test(previous);
|
|
313
|
+
const COMMAND_LAUNCHERS = /* @__PURE__ */ new Map([
|
|
314
|
+
["bash", shellCommand],
|
|
315
|
+
["concurrently", () => true],
|
|
316
|
+
["nodemon", (previous) => previous === "--exec" || previous === "-x"],
|
|
317
|
+
["sh", shellCommand],
|
|
318
|
+
["zsh", shellCommand]
|
|
319
|
+
]);
|
|
320
|
+
/**
|
|
321
|
+
* Rewrite supported runner and Node.js loader invocations while preserving shell
|
|
322
|
+
* syntax and arguments. Unsupported forms remain, with their reasons in `kept`.
|
|
323
|
+
*/
|
|
324
|
+
function rewriteScriptRunnerCommand(runner, command, cwd, environment = [], checkingScripts = /* @__PURE__ */ new Set()) {
|
|
325
|
+
if (!runner.commandPattern.test(command) && !runner.envKeepReason(environment)) return {
|
|
326
|
+
command,
|
|
327
|
+
kept: []
|
|
328
|
+
};
|
|
329
|
+
const segments = parseLiteralCommands(command, true);
|
|
330
|
+
if (!segments || /%\w+%/.test(command)) return {
|
|
331
|
+
command,
|
|
332
|
+
kept: [KEEP_SHELL]
|
|
90
333
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
334
|
+
const rewrite = {
|
|
335
|
+
command,
|
|
336
|
+
cwd,
|
|
337
|
+
environment,
|
|
338
|
+
edits: [],
|
|
339
|
+
kept: [],
|
|
340
|
+
checkingScripts
|
|
341
|
+
};
|
|
342
|
+
for (const tokens of segments) {
|
|
343
|
+
const name = tokens[0].value;
|
|
344
|
+
if (SHELL_STATE_COMMANDS.includes(name) || name === "chdir") {
|
|
345
|
+
rewrite.cwd = void 0;
|
|
346
|
+
continue;
|
|
102
347
|
}
|
|
348
|
+
rewriteSegment(runner, rewrite, tokens);
|
|
103
349
|
}
|
|
104
|
-
|
|
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}`);
|
|
352
|
+
return {
|
|
353
|
+
command: rewritten,
|
|
354
|
+
kept: rewrite.kept
|
|
355
|
+
};
|
|
105
356
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let
|
|
357
|
+
function rewriteSegment(runner, rewrite, tokens) {
|
|
358
|
+
const values = tokens.map(({ value }) => value);
|
|
359
|
+
let first = 0;
|
|
360
|
+
const skipAssignments = () => {
|
|
361
|
+
while (/^[A-Za-z_]\w*=/.test(values[first] ?? "")) first++;
|
|
362
|
+
};
|
|
363
|
+
skipAssignments();
|
|
364
|
+
const shell = values[first] === "cross-env-shell";
|
|
365
|
+
if (values[first] === "cross-env" || shell) {
|
|
366
|
+
first++;
|
|
367
|
+
skipAssignments();
|
|
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
|
+
}
|
|
379
|
+
if (first >= values.length) return;
|
|
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;
|
|
388
|
+
const start = first + getCommandStartIndex(values.slice(first));
|
|
109
389
|
if ([
|
|
390
|
+
"env",
|
|
391
|
+
"cross-env",
|
|
392
|
+
"cross-env-shell"
|
|
393
|
+
].includes(values[start])) environment.keepReason ??= inheritedOptionsKeepReason(runner);
|
|
394
|
+
const launcher = COMMAND_LAUNCHERS.get(values[start]);
|
|
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",
|
|
110
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;
|
|
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 (![
|
|
111
449
|
"npm",
|
|
450
|
+
"pnpm",
|
|
112
451
|
"yarn",
|
|
113
|
-
"bun"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
};
|
|
492
|
+
}
|
|
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;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* An option's name without `=value`. Node.js reads `_` as `-` in long option names, as
|
|
526
|
+
* in `--watch_path`, and so does `vpx`.
|
|
527
|
+
*/
|
|
528
|
+
function optionName(arg) {
|
|
529
|
+
const name = arg.split("=", 1)[0];
|
|
530
|
+
return name.startsWith("--") ? `--${name.slice(2).replaceAll("_", "-")}` : name;
|
|
531
|
+
}
|
|
532
|
+
/** Remove `tokens[index]` with the space before it. */
|
|
533
|
+
function removal(tokens, index) {
|
|
534
|
+
return {
|
|
535
|
+
start: tokens[index - 1]?.end ?? tokens[index].start,
|
|
536
|
+
end: tokens[index].end,
|
|
537
|
+
text: ""
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Why `vpx` cannot run an invocation: `positionals` are the entry
|
|
542
|
+
* and its arguments, or with `--test`, the test files; `preloads` are local `--require`
|
|
543
|
+
* and `--import` modules, which load first.
|
|
544
|
+
*/
|
|
545
|
+
function runKeepReason(runner, cwd, test, positionals, preloads) {
|
|
546
|
+
const [entry] = positionals;
|
|
547
|
+
if (entry === void 0) return test ? runner.testWithoutFilesReason : KEEP_REPL;
|
|
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`;
|
|
549
|
+
const scripts = test ? positionals.filter((value) => !value.startsWith("-")) : [entry];
|
|
550
|
+
for (const script of [...preloads, ...scripts]) {
|
|
551
|
+
const reason = runner.scriptKeepReason?.(cwd, script);
|
|
552
|
+
if (reason) return reason;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function rewriteRunner(runner, rewrite, tokens, first, start, keepReason, environment) {
|
|
556
|
+
if (keepReason) {
|
|
557
|
+
rewrite.kept.push(keepReason);
|
|
558
|
+
return;
|
|
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;
|
|
566
|
+
let index = start + 1;
|
|
567
|
+
const watch = runner.watchSubcommand !== false && tokens[index]?.value === "watch";
|
|
568
|
+
if (watch) index++;
|
|
569
|
+
const scanned = scanOptions(runner, tokens, index, true);
|
|
570
|
+
const reason = runReason(runner, rewrite.cwd, tokens, scanned, watch);
|
|
571
|
+
if (reason) {
|
|
572
|
+
rewrite.kept.push(reason);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
rewrite.edits.push({
|
|
576
|
+
start: tokens[first].start,
|
|
577
|
+
end: tokens[index - 1].end,
|
|
578
|
+
text: watch ? "vpx --watch" : "vpx"
|
|
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);
|
|
607
|
+
if (!scanned.loaders) {
|
|
608
|
+
if (keepReason) rewrite.kept.push(keepReason);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const reason = keepReason ?? runReason(runner, rewrite.cwd, tokens, scanned, false);
|
|
612
|
+
if (reason) {
|
|
613
|
+
rewrite.kept.push(reason);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
rewrite.edits.push({
|
|
617
|
+
start: tokens[first].start,
|
|
618
|
+
end: tokens[start].end,
|
|
619
|
+
text: "vpx"
|
|
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
|
+
};
|
|
677
|
+
}
|
|
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) {
|
|
692
|
+
const scanned = {
|
|
693
|
+
entryIndex: tokens.length,
|
|
694
|
+
removals: [],
|
|
695
|
+
loaders: 0,
|
|
696
|
+
runnerLoader: false,
|
|
697
|
+
imports: [],
|
|
698
|
+
inlineCode: false,
|
|
699
|
+
preloads: [],
|
|
700
|
+
test: false,
|
|
701
|
+
watch: false
|
|
702
|
+
};
|
|
703
|
+
for (; index < tokens.length; index++) {
|
|
704
|
+
const { value } = tokens[index];
|
|
705
|
+
if (value === "--" || !value.startsWith("-")) {
|
|
706
|
+
scanned.entryIndex = value === "--" ? index + 1 : index;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
const name = optionName(value);
|
|
710
|
+
const inline = value.includes("=");
|
|
711
|
+
const argument = inline ? value.slice(value.indexOf("=") + 1) : tokens[index + 1]?.value;
|
|
712
|
+
if (directRunner && runner.dropOptions?.has(name)) {
|
|
713
|
+
scanned.removals.push(removal(tokens, index));
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (LOADER_OPTIONS.has(name)) {
|
|
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
|
+
}
|
|
726
|
+
scanned.removals.push({
|
|
727
|
+
...removal(tokens, index),
|
|
728
|
+
end: tokens[inline ? index : index + 1].end
|
|
729
|
+
});
|
|
730
|
+
scanned.loaders++;
|
|
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
|
+
}
|
|
742
|
+
if (!inline) index++;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
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
|
+
}
|
|
757
|
+
scanned.test ||= name === "--test";
|
|
758
|
+
scanned.watch ||= name === "--watch";
|
|
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++;
|
|
769
|
+
}
|
|
770
|
+
return scanned;
|
|
771
|
+
}
|
|
772
|
+
/** Why `vpx` cannot run what the scanned options and the scripts after them run. */
|
|
773
|
+
function runReason(runner, cwd, tokens, scanned, watch) {
|
|
774
|
+
if (scanned.keepReason) return scanned.keepReason;
|
|
775
|
+
if ((watch || scanned.watch) && scanned.test) return KEEP_WATCH_TEST;
|
|
776
|
+
return runKeepReason(runner, cwd, scanned.test, tokens.slice(scanned.entryIndex).map(({ value }) => value), scanned.preloads);
|
|
777
|
+
}
|
|
778
|
+
/** Rewrite a quoted launcher command with the environment it inherits. */
|
|
779
|
+
function rewriteQuotedCommand(runner, rewrite, token, environment) {
|
|
780
|
+
const raw = rewrite.command.slice(token.start, token.end);
|
|
781
|
+
const quote = raw[0];
|
|
782
|
+
if (quote !== "\"" && quote !== "'" || raw.length < 2 || !raw.endsWith(quote)) return false;
|
|
783
|
+
const inner = rewriteScriptRunnerCommand(runner, token.value, rewrite.cwd, environment, rewrite.checkingScripts);
|
|
784
|
+
rewrite.kept.push(...inner.kept);
|
|
785
|
+
if (inner.command !== token.value) rewrite.edits.push(literalEdit(rewrite.command, token, inner.command));
|
|
786
|
+
return token.value.trim() !== "";
|
|
787
|
+
}
|
|
788
|
+
/** The commands in a scripts or lint-staged record. */
|
|
789
|
+
function recordCommands(record) {
|
|
790
|
+
if (!isPlainRecord(record)) return [];
|
|
791
|
+
return Object.values(record).flatMap((value) => Array.isArray(value) ? value : [value]).filter((command) => typeof command === "string");
|
|
792
|
+
}
|
|
793
|
+
/** The commands a package.json runs: its scripts and lint-staged tasks. */
|
|
794
|
+
function packageCommands(pkg) {
|
|
795
|
+
return [...recordCommands(pkg.scripts), ...recordCommands(pkg["lint-staged"])];
|
|
796
|
+
}
|
|
797
|
+
function lintStagedJsonFiles(dir) {
|
|
798
|
+
return LINT_STAGED_JSON_CONFIG_FILES.map((name) => path.join(dir, name)).filter((file) => fs.existsSync(file) && isJsonFile(file));
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
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.
|
|
803
|
+
*/
|
|
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);
|
|
806
|
+
}
|
|
807
|
+
function packageDirs(rootDir, packages) {
|
|
808
|
+
return [rootDir, ...(packages ?? []).map((pkg) => path.join(rootDir, pkg.path))];
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Detect supported invocations, or retained uses when the profile requests warnings.
|
|
812
|
+
*/
|
|
813
|
+
function detectScriptRunnerProject(runner, rootDir, packages) {
|
|
814
|
+
return packageDirs(rootDir, packages).some((dir) => {
|
|
815
|
+
const pkg = readPackageJsonIfExists(path.join(dir, "package.json"));
|
|
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);
|
|
819
|
+
return result !== void 0 && "content" in result;
|
|
820
|
+
}, { includeExtensionless: runner.includeExtensionlessShebangs });
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
/** Rewrite each command of a scripts or lint-staged record; returns whether one changed. */
|
|
824
|
+
function rewriteCommandRecord(runner, record, label, cwd, report) {
|
|
825
|
+
let edited = false;
|
|
826
|
+
for (const [key, value] of Object.entries(record)) {
|
|
827
|
+
const commands = Array.isArray(value) ? value : [value];
|
|
828
|
+
const rewritten = commands.map((command) => {
|
|
829
|
+
if (typeof command !== "string") return command;
|
|
830
|
+
const result = rewriteScriptRunnerCommand(runner, command, cwd);
|
|
831
|
+
for (const reason of result.kept) warnMigration(`Kept ${runner.name} in ${label} "${key}": ${reason}.`, report);
|
|
832
|
+
return result.command;
|
|
833
|
+
});
|
|
834
|
+
if (rewritten.some((command, index) => command !== commands[index])) {
|
|
835
|
+
record[key] = Array.isArray(value) ? rewritten : rewritten[0];
|
|
836
|
+
edited = true;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return edited;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* The migrated runner or Node.js preload shebang, or its retention reason.
|
|
843
|
+
*/
|
|
844
|
+
function rewriteRunnerShebang(runner, content, file, packageDir) {
|
|
845
|
+
const match = runner.envShebang.exec(content);
|
|
846
|
+
if (!match) return;
|
|
847
|
+
const [line, prefix, command] = match;
|
|
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)}`);
|
|
867
|
+
if (reason) return { reason };
|
|
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)}` };
|
|
871
|
+
}
|
|
872
|
+
/** Rewrite matching runner and Node.js preload shebangs in a package's scripts. */
|
|
873
|
+
function rewriteRunnerShebangs(runner, dir, report) {
|
|
874
|
+
let changed = false;
|
|
875
|
+
sourceTreeMatches(dir, (content, file) => {
|
|
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);
|
|
878
|
+
else if (result) {
|
|
879
|
+
fs.writeFileSync(file, result.content);
|
|
880
|
+
changed = true;
|
|
881
|
+
}
|
|
882
|
+
return false;
|
|
883
|
+
}, { includeExtensionless: runner.includeExtensionlessShebangs });
|
|
884
|
+
return changed;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Find a remaining use, including nested packages and build output that can resolve
|
|
888
|
+
* the package's runner dependencies.
|
|
889
|
+
*/
|
|
890
|
+
function findRunnerUse(runner, dir, retainUnverified = true) {
|
|
891
|
+
let found;
|
|
892
|
+
sourceTreeMatches(dir, (content, file) => {
|
|
893
|
+
if (LOCKFILES.has(path.basename(file))) return false;
|
|
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;
|
|
897
|
+
}, {
|
|
898
|
+
crossPackageBoundaries: true,
|
|
899
|
+
extensions: REFERENCE_EXTENSIONS,
|
|
900
|
+
fileNamePattern: runner.referenceFileNamePattern,
|
|
901
|
+
includeExtensionless: true,
|
|
902
|
+
includePackageReferences: true,
|
|
903
|
+
matchesPackage: (packageDir, pkg) => {
|
|
904
|
+
const field = Object.entries(pkg).find(([key, value]) => !IGNORED_FIELDS.has(key) && jsonUsesRunner(runner, value, [
|
|
905
|
+
"imports",
|
|
906
|
+
"lint-staged",
|
|
907
|
+
"scripts"
|
|
908
|
+
].includes(key)))?.[0];
|
|
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;
|
|
915
|
+
},
|
|
916
|
+
skipDirs: USE_SCAN_SKIP_DIRS
|
|
917
|
+
});
|
|
918
|
+
return found;
|
|
919
|
+
}
|
|
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;
|
|
956
|
+
const name = path.basename(file);
|
|
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;
|
|
959
|
+
const extension = path.extname(file);
|
|
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) {
|
|
972
|
+
let changed = false;
|
|
973
|
+
const migrated = [];
|
|
974
|
+
for (const dir of packageDirs(rootDir, packages)) {
|
|
975
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
976
|
+
const pkg = readPackageJsonIfExists(packageJsonPath);
|
|
977
|
+
if (!pkg) continue;
|
|
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);
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
migrated.push(dir);
|
|
983
|
+
editJsonFile(packageJsonPath, (json) => {
|
|
984
|
+
let edited = false;
|
|
985
|
+
for (const field of ["scripts", "lint-staged"]) {
|
|
986
|
+
const record = json[field];
|
|
987
|
+
if (isPlainRecord(record) && rewriteCommandRecord(runner, record, `${displayRelative(packageJsonPath)} ${field}`, dir, report)) edited = true;
|
|
988
|
+
}
|
|
989
|
+
changed ||= edited;
|
|
990
|
+
return edited ? json : void 0;
|
|
991
|
+
});
|
|
992
|
+
for (const file of lintStagedJsonFiles(dir)) editJsonFile(file, (config) => {
|
|
993
|
+
const edited = rewriteCommandRecord(runner, config, displayRelative(file), dir, report);
|
|
994
|
+
changed ||= edited;
|
|
995
|
+
return edited ? config : void 0;
|
|
996
|
+
});
|
|
997
|
+
if (rewriteRunnerShebangs(runner, dir, report)) changed = true;
|
|
998
|
+
}
|
|
999
|
+
let dependencyRemoved = false;
|
|
1000
|
+
for (const dir of migrated) {
|
|
1001
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
1002
|
+
const devDependencies = readPackageJsonIfExists(packageJsonPath)?.devDependencies;
|
|
1003
|
+
if (!runner.dependencies.some((dependency) => devDependencies?.[dependency] !== void 0)) continue;
|
|
1004
|
+
const use = findRunnerUse(runner, dir);
|
|
1005
|
+
if (use) {
|
|
1006
|
+
warnMigration(`Kept the ${runner.dependencies.join(" / ")} devDependency in ${displayRelative(packageJsonPath)}: ${use}.`, report);
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
editJsonFile(packageJsonPath, (json) => {
|
|
1010
|
+
for (const dependency of runner.dependencies) delete json.devDependencies[dependency];
|
|
1011
|
+
return json;
|
|
1012
|
+
});
|
|
1013
|
+
dependencyRemoved = true;
|
|
1014
|
+
}
|
|
1015
|
+
return {
|
|
1016
|
+
changed: changed || dependencyRemoved,
|
|
1017
|
+
dependencyRemoved,
|
|
1018
|
+
scriptsChanged: changed
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
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;
|
|
120
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) {
|
|
1261
|
+
if (interactive) {
|
|
1262
|
+
const confirmed = await confirm({
|
|
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."),
|
|
1264
|
+
initialValue: true
|
|
1265
|
+
});
|
|
1266
|
+
if (isCancel(confirmed)) cancelAndExit();
|
|
1267
|
+
return confirmed;
|
|
1268
|
+
}
|
|
1269
|
+
log.info("ts-node scripts detected. Auto-migrating supported commands to Vite+...");
|
|
1270
|
+
return true;
|
|
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
|
+
}
|
|
1519
|
+
// src/migration/vitest-v5/commands.ts
|
|
1520
|
+
/** Read test commands without evaluating shell state or directory changes. */
|
|
1521
|
+
function literalTestCommands(command) {
|
|
1522
|
+
const commands = parseLiteralCommands(command, true);
|
|
1523
|
+
if (!commands) return;
|
|
1524
|
+
const result = [];
|
|
1525
|
+
for (const tokens of commands) {
|
|
1526
|
+
const values = tokens.map(({ value }) => value);
|
|
1527
|
+
while (/^[A-Za-z_]\w*=/.test(values[0] ?? "")) values.shift();
|
|
1528
|
+
if (!values.length || SHELL_STATE_COMMANDS.includes(values[0])) return;
|
|
1529
|
+
result.push(values);
|
|
1530
|
+
}
|
|
1531
|
+
return result;
|
|
1532
|
+
}
|
|
1533
|
+
/** Locate the runner's arguments after a supported package-manager wrapper. */
|
|
1534
|
+
function vitestCommandArgsStart(values) {
|
|
1535
|
+
const start = getCommandStartIndex(values);
|
|
121
1536
|
if (values[start] === "vitest") return start + 1;
|
|
122
1537
|
if (values[start] === "vp" && values[start + 1] === "test") return start + 2;
|
|
123
1538
|
return -1;
|
|
124
1539
|
}
|
|
125
1540
|
function migrateBenchmarkOutput(command) {
|
|
126
|
-
const argv =
|
|
1541
|
+
const argv = parseLiteralCommands(command, false)?.[0];
|
|
127
1542
|
if (!argv) return command;
|
|
128
1543
|
const values = argv.map(({ value }) => value);
|
|
129
1544
|
const start = vitestCommandArgsStart(values);
|
|
@@ -182,7 +1597,7 @@ function migrateVitestV5Command(file, command, preserveV4, line = 1, reviewV4 =
|
|
|
182
1597
|
findings
|
|
183
1598
|
};
|
|
184
1599
|
command = migrateBenchmarkOutput(command);
|
|
185
|
-
const argv =
|
|
1600
|
+
const argv = parseLiteralCommands(command, false)?.[0];
|
|
186
1601
|
const values = argv?.map(({ value }) => value);
|
|
187
1602
|
const runner = values ? vitestCommandArgsStart(values) : -1;
|
|
188
1603
|
let removedFlag = /(?:^|\s)(?:--compare|--outputJson)(?:\s|=|$)/.test(command);
|
|
@@ -230,8 +1645,7 @@ function migrateVitestV5Command(file, command, preserveV4, line = 1, reviewV4 =
|
|
|
230
1645
|
findings
|
|
231
1646
|
};
|
|
232
1647
|
}
|
|
233
|
-
|
|
234
|
-
//#region src/migration/vitest-v5/ast.ts
|
|
1648
|
+
// src/migration/vitest-v5/ast.ts
|
|
235
1649
|
function isNode(value) {
|
|
236
1650
|
return typeof value === "object" && value !== null && "type" in value && "start" in value;
|
|
237
1651
|
}
|
|
@@ -505,7 +1919,7 @@ var SourceEditor = class {
|
|
|
505
1919
|
}
|
|
506
1920
|
}
|
|
507
1921
|
const propertyIndent = multilinePrefix && object.properties.length ? indent : `${objectIndent} `;
|
|
508
|
-
const entries =
|
|
1922
|
+
const entries = Array.from(additions.values(), (property) => printAddedProperty(property, propertyIndent, newline));
|
|
509
1923
|
const suffix = multiline && !multilinePrefix ? `${newline}${object.properties.length ? propertyIndent : objectIndent}` : "";
|
|
510
1924
|
this.edit(start, multiline && !multilinePrefix && /^\s*$/.test(prefix) ? first : start, multiline ? `${newline}${propertyIndent}${entries.join(`,${newline}${propertyIndent}`)}${object.properties.length ? "," : ""}${suffix}` : ` ${entries.join(", ")}${object.properties.length ? "," : ""} `);
|
|
511
1925
|
}
|
|
@@ -538,8 +1952,7 @@ var SourceEditor = class {
|
|
|
538
1952
|
};
|
|
539
1953
|
}
|
|
540
1954
|
};
|
|
541
|
-
|
|
542
|
-
//#region src/migration/vitest-v5/benchmark-config.ts
|
|
1955
|
+
// src/migration/vitest-v5/benchmark-config.ts
|
|
543
1956
|
const REPORTERS = /* @__PURE__ */ new Set([
|
|
544
1957
|
"default",
|
|
545
1958
|
"verbose",
|
|
@@ -607,8 +2020,7 @@ function migrateBenchmarkConfig(editor, test) {
|
|
|
607
2020
|
oldJson
|
|
608
2021
|
]) if (prop) editor.remove(benchmark, prop);
|
|
609
2022
|
}
|
|
610
|
-
|
|
611
|
-
//#region src/migration/vitest-v5/compatibility.ts
|
|
2023
|
+
// src/migration/vitest-v5/compatibility.ts
|
|
612
2024
|
const settings = {
|
|
613
2025
|
clearMocks: {
|
|
614
2026
|
value: "false",
|
|
@@ -656,8 +2068,7 @@ function compatibilityProperty(key) {
|
|
|
656
2068
|
comment: `// Vitest v4 compatibility: ${reason}\n// ${adoption}\n// ${documentationUrl$1("/guide/vitest-v5#remove-unneeded-compatibility-settings")}\n// https://vitest.dev/guide/migration/#${section}`
|
|
657
2069
|
};
|
|
658
2070
|
}
|
|
659
|
-
|
|
660
|
-
//#region src/migration/vitest-v5/config-object.ts
|
|
2071
|
+
// src/migration/vitest-v5/config-object.ts
|
|
661
2072
|
const CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
662
2073
|
"root",
|
|
663
2074
|
"test",
|
|
@@ -703,8 +2114,7 @@ function resolveConfigObject(editor, node) {
|
|
|
703
2114
|
}
|
|
704
2115
|
return node;
|
|
705
2116
|
}
|
|
706
|
-
|
|
707
|
-
//#region src/migration/vitest-v5/config.ts
|
|
2117
|
+
// src/migration/vitest-v5/config.ts
|
|
708
2118
|
function isTrue(node) {
|
|
709
2119
|
return isBoolean(node) && node.value;
|
|
710
2120
|
}
|
|
@@ -1001,8 +2411,7 @@ function migrateConfig(file, source, options, mergedConfig, benchmarkOnly = fals
|
|
|
1001
2411
|
});
|
|
1002
2412
|
return editor.finish();
|
|
1003
2413
|
}
|
|
1004
|
-
|
|
1005
|
-
//#region src/migration/vitest-v5/documentation.ts
|
|
2414
|
+
// src/migration/vitest-v5/documentation.ts
|
|
1006
2415
|
const guide = documentationUrl$1("/guide/vitest-v5");
|
|
1007
2416
|
const upstream = "https://vitest.dev/guide/migration/";
|
|
1008
2417
|
const sections = {
|
|
@@ -1054,12 +2463,49 @@ function vitestV5Documentation(code) {
|
|
|
1054
2463
|
if (sections[code]) return `${upstream}#${sections[code]}`;
|
|
1055
2464
|
return `${guide}#${localSections[code] ?? "resolve-migration-findings"}`;
|
|
1056
2465
|
}
|
|
1057
|
-
|
|
1058
|
-
//#region src/migration/vitest-v5/lockfile.ts
|
|
2466
|
+
// src/migration/vitest-v5/lockfile.ts
|
|
1059
2467
|
var import_dist = require_dist();
|
|
1060
2468
|
function record$1(value) {
|
|
1061
2469
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1062
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
|
+
}
|
|
1063
2509
|
function declaredVitest(pkg) {
|
|
1064
2510
|
const manifest = record$1(pkg);
|
|
1065
2511
|
for (const field of [
|
|
@@ -1075,7 +2521,7 @@ function declaredVitest(pkg) {
|
|
|
1075
2521
|
/** Read a direct upstream Vitest edge without installing or changing a lockfile.
|
|
1076
2522
|
* Unsupported protocols, ambiguous layouts and stale entries remain unknown.
|
|
1077
2523
|
* In particular, never infer a runner from an unrelated transitive package. */
|
|
1078
|
-
function lockedVitestVersion(root, directory, manager, spec) {
|
|
2524
|
+
function lockedVitestVersion(root, directory, manager, spec, readLockfile) {
|
|
1079
2525
|
const range = spec.replace(/^npm:vitest@/, "");
|
|
1080
2526
|
const validRange = normalizeRange(range);
|
|
1081
2527
|
if (!validRange && !/^[\w.-]+$/.test(range)) return;
|
|
@@ -1084,10 +2530,9 @@ function lockedVitestVersion(root, directory, manager, spec) {
|
|
|
1084
2530
|
return !validRange || satisfies(version, range, { includePrerelease: true }) ? version : void 0;
|
|
1085
2531
|
}
|
|
1086
2532
|
const relative = path.relative(root, directory).replaceAll("\\", "/");
|
|
1087
|
-
const read = (name) => fs.readFileSync(path.join(root, name), "utf8").replace(/^\uFEFF/, "");
|
|
1088
2533
|
try {
|
|
1089
2534
|
if (manager === PackageManager.npm) {
|
|
1090
|
-
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);
|
|
1091
2536
|
if (!packages) return;
|
|
1092
2537
|
if (declaredVitest(packages[relative]) !== spec) return;
|
|
1093
2538
|
let owner = relative;
|
|
@@ -1100,34 +2545,29 @@ function lockedVitestVersion(root, directory, manager, spec) {
|
|
|
1100
2545
|
}
|
|
1101
2546
|
}
|
|
1102
2547
|
if (manager === PackageManager.yarn) {
|
|
1103
|
-
const
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
1106
|
-
if (document.errors.length) return;
|
|
2548
|
+
const lock = readLockfile(path.join(root, "yarn.lock"), parseYarnLockfile);
|
|
2549
|
+
if (!lock) return;
|
|
2550
|
+
if (lock.modern) {
|
|
1107
2551
|
const descriptors = /* @__PURE__ */ new Set([`vitest@${spec}`, `vitest@npm:${range}`]);
|
|
1108
|
-
const entries = Object.entries(
|
|
2552
|
+
const entries = Object.entries(lock.entries).filter(([keys]) => keys.split(/,\s+/).some((key) => descriptors.has(key)));
|
|
1109
2553
|
if (entries.length !== 1) return;
|
|
1110
2554
|
const entry = record$1(entries[0][1]);
|
|
1111
2555
|
return typeof entry?.resolution === "string" && entry.resolution.startsWith("vitest@npm:") ? valid(entry.resolution.slice(11)) : void 0;
|
|
1112
2556
|
}
|
|
1113
2557
|
const matches = [];
|
|
1114
|
-
for (const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const document = (0, import_dist.parseDocument)(`[${header.slice(0, -1)}]`);
|
|
1118
|
-
const keys = document.errors.length ? void 0 : document.toJS();
|
|
1119
|
-
if (Array.isArray(keys) && keys.includes(`vitest@${spec}`)) {
|
|
1120
|
-
const version = /^ version "([^"]+)"\r?$/m.exec(block)?.[1];
|
|
1121
|
-
if (!version) return;
|
|
1122
|
-
matches.push(version);
|
|
1123
|
-
}
|
|
2558
|
+
for (const { keys, version } of lock.entries) if (keys.includes(`vitest@${spec}`)) {
|
|
2559
|
+
if (!version) return;
|
|
2560
|
+
matches.push(version);
|
|
1124
2561
|
}
|
|
1125
2562
|
return matches.length === 1 ? valid(matches[0]) : void 0;
|
|
1126
2563
|
}
|
|
1127
2564
|
if (manager === PackageManager.bun) {
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
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;
|
|
1131
2571
|
const packages = record$1(lock?.packages);
|
|
1132
2572
|
const direct = packages?.vitest;
|
|
1133
2573
|
if (!Array.isArray(direct) || typeof direct[0] !== "string" || !direct[0].startsWith("vitest@")) return;
|
|
@@ -1143,8 +2583,7 @@ function lockedVitestVersion(root, directory, manager, spec) {
|
|
|
1143
2583
|
}
|
|
1144
2584
|
} catch {}
|
|
1145
2585
|
}
|
|
1146
|
-
|
|
1147
|
-
//#region src/migration/vitest-v5/storybook.ts
|
|
2586
|
+
// src/migration/vitest-v5/storybook.ts
|
|
1148
2587
|
const STORYBOOK_PLUGIN = /* @__PURE__ */ new Set(["@storybook/addon-vitest/vitest-plugin"]);
|
|
1149
2588
|
const NODE_PATH = /* @__PURE__ */ new Set(["node:path", "path"]);
|
|
1150
2589
|
const NODE_URL = /* @__PURE__ */ new Set(["node:url", "url"]);
|
|
@@ -1268,8 +2707,7 @@ function resolveStorybookScope(editor, object, sources, file, cwd) {
|
|
|
1268
2707
|
return unknown(`cannot parse ${label}/main`);
|
|
1269
2708
|
}
|
|
1270
2709
|
}
|
|
1271
|
-
|
|
1272
|
-
//#region src/migration/vitest-v5/scopes.ts
|
|
2710
|
+
// src/migration/vitest-v5/scopes.ts
|
|
1273
2711
|
const DEFAULT_TEST_INCLUDE = ["**/*.{test,spec}.?(c|m)[jt]s?(x)"];
|
|
1274
2712
|
const DEFAULT_TEST_EXCLUDE = ["**/node_modules/**", "**/.git/**"];
|
|
1275
2713
|
const DEFAULT_BENCHMARK_INCLUDE = ["**/*.{bench,benchmark}.?(c|m)[jt]s?(x)"];
|
|
@@ -1659,7 +3097,7 @@ function resolveVitestV5TestModes(sources, configFiles, preserveV4, entries) {
|
|
|
1659
3097
|
if (!found) unknown();
|
|
1660
3098
|
}
|
|
1661
3099
|
return {
|
|
1662
|
-
modes: new Map(
|
|
3100
|
+
modes: new Map(Array.from(sources.keys(), (file) => {
|
|
1663
3101
|
const matching = [];
|
|
1664
3102
|
let benchmarkFile = false;
|
|
1665
3103
|
const hasInSourceTests = sources.get(file)?.includes("import.meta.vitest");
|
|
@@ -1696,8 +3134,7 @@ function resolveVitestV5TestModes(sources, configFiles, preserveV4, entries) {
|
|
|
1696
3134
|
findings
|
|
1697
3135
|
};
|
|
1698
3136
|
}
|
|
1699
|
-
|
|
1700
|
-
//#region src/migration/vitest-v5/benchmarks.ts
|
|
3137
|
+
// src/migration/vitest-v5/benchmarks.ts
|
|
1701
3138
|
/** Keep the workload as a separate callback: making it the test callback
|
|
1702
3139
|
* would measure nothing, and inlining it into a new closure can change scope. */
|
|
1703
3140
|
function migrateBenchmarks(editor, globals = false) {
|
|
@@ -1840,8 +3277,7 @@ function migrateBenchmarks(editor, globals = false) {
|
|
|
1840
3277
|
calls
|
|
1841
3278
|
};
|
|
1842
3279
|
}
|
|
1843
|
-
|
|
1844
|
-
//#region src/migration/vitest-v5/source.ts
|
|
3280
|
+
// src/migration/vitest-v5/source.ts
|
|
1845
3281
|
const RUNNER_SYMBOLS = {
|
|
1846
3282
|
test: "test",
|
|
1847
3283
|
it: "it",
|
|
@@ -2074,6 +3510,45 @@ function looksLikeConstructorMock(editor, node) {
|
|
|
2074
3510
|
}
|
|
2075
3511
|
return false;
|
|
2076
3512
|
}
|
|
3513
|
+
function unwrapTypeExpression(node) {
|
|
3514
|
+
while (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression" || node.type === "TSTypeAssertion") node = node.expression;
|
|
3515
|
+
return node;
|
|
3516
|
+
}
|
|
3517
|
+
/** Recognize a local promise consumed by a later statement without crossing
|
|
3518
|
+
* control flow or function boundaries. Ambiguous paths still need review. */
|
|
3519
|
+
function isAssertionBindingHandled(editor, node) {
|
|
3520
|
+
let initializer = node;
|
|
3521
|
+
let declarator = editor.parent(initializer);
|
|
3522
|
+
while (declarator && unwrapTypeExpression(declarator) === node) {
|
|
3523
|
+
initializer = declarator;
|
|
3524
|
+
declarator = editor.parent(initializer);
|
|
3525
|
+
}
|
|
3526
|
+
if (declarator?.type !== "VariableDeclarator" || declarator.init !== initializer || declarator.id.type !== "Identifier") return false;
|
|
3527
|
+
const binding = editor.binding(declarator.id);
|
|
3528
|
+
const declaration = editor.parent(declarator);
|
|
3529
|
+
const block = declaration && editor.parent(declaration);
|
|
3530
|
+
const fn = editor.functionParent(node);
|
|
3531
|
+
if (!binding?.constant || declaration?.type !== "VariableDeclaration" || declaration.kind !== "const" || block?.type !== "BlockStatement" || !fn || fn.generator) return false;
|
|
3532
|
+
for (let ancestor = editor.parent(block); ancestor && ancestor !== fn; ancestor = editor.parent(ancestor)) if (ancestor.type === "TryStatement") return false;
|
|
3533
|
+
for (const statement of block.body.slice(block.body.indexOf(declaration) + 1)) switch (statement.type) {
|
|
3534
|
+
case "ReturnStatement": {
|
|
3535
|
+
if (!statement.argument) return false;
|
|
3536
|
+
let expression = unwrapTypeExpression(statement.argument);
|
|
3537
|
+
if (expression.type === "AwaitExpression") expression = unwrapTypeExpression(expression.argument);
|
|
3538
|
+
return editor.binding(expression) === binding;
|
|
3539
|
+
}
|
|
3540
|
+
case "ExpressionStatement": {
|
|
3541
|
+
const expression = unwrapTypeExpression(statement.expression);
|
|
3542
|
+
if (expression.type === "AwaitExpression" && editor.binding(unwrapTypeExpression(expression.argument)) === binding) return true;
|
|
3543
|
+
break;
|
|
3544
|
+
}
|
|
3545
|
+
case "VariableDeclaration":
|
|
3546
|
+
case "FunctionDeclaration":
|
|
3547
|
+
case "EmptyStatement": break;
|
|
3548
|
+
default: return false;
|
|
3549
|
+
}
|
|
3550
|
+
return false;
|
|
3551
|
+
}
|
|
2077
3552
|
function chain(node, isApi) {
|
|
2078
3553
|
const members = [];
|
|
2079
3554
|
const calls = [];
|
|
@@ -2268,7 +3743,7 @@ function rewriteSource(file, source, options) {
|
|
|
2268
3743
|
if (asynchronous && parent?.type === "ExpressionStatement") {
|
|
2269
3744
|
if (canAwait(node)) editor.edit(node.start, node.start, "await ");
|
|
2270
3745
|
else editor.report(node, "unawaited-assertion", "Await or return this asynchronous assertion in an async-compatible function.");
|
|
2271
|
-
} else if (asynchronous && parent?.type !== "MemberExpression" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement") editor.report(node, "unawaited-assertion", "Check that the result of this asynchronous assertion is awaited or returned.");
|
|
3746
|
+
} else if (asynchronous && parent?.type !== "MemberExpression" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement" && !isAssertionBindingHandled(editor, node)) editor.report(node, "unawaited-assertion", "Check that the result of this asynchronous assertion is awaited or returned.");
|
|
2272
3747
|
}
|
|
2273
3748
|
if (importedName(editor, node.callee, RENDER_SOURCES) === "render" && parent?.type !== "AwaitExpression" && parent?.type !== "ReturnStatement") {
|
|
2274
3749
|
if (canAwait(node)) {
|
|
@@ -2352,8 +3827,7 @@ function rewriteSource(file, source, options) {
|
|
|
2352
3827
|
if (benchmark && result.content === source) editor.report(benchmark, "benchmark-api", "The benchmark rewrite was discarded. Migrate the remaining legacy bench API manually before upgrading to Vitest v5.", "block");
|
|
2353
3828
|
return result;
|
|
2354
3829
|
}
|
|
2355
|
-
|
|
2356
|
-
//#region src/migration/vitest-v5/webdriverio.ts
|
|
3830
|
+
// src/migration/vitest-v5/webdriverio.ts
|
|
2357
3831
|
const MIN_VERSION = "5.0.0";
|
|
2358
3832
|
const DEFAULT_SPEC = "^5.0.0";
|
|
2359
3833
|
const REGISTRY_ALIAS = `npm:${WEBDRIVERIO_PROVIDER}@`;
|
|
@@ -2395,7 +3869,7 @@ function migrateWebdriverioDependencies(workspace, sources, inputs) {
|
|
|
2395
3869
|
const set = (file, keys, value) => {
|
|
2396
3870
|
if (get(file, keys) === value) return;
|
|
2397
3871
|
const source = sources.get(file);
|
|
2398
|
-
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: {
|
|
2399
3873
|
insertSpaces: true,
|
|
2400
3874
|
tabSize: 2
|
|
2401
3875
|
} })));
|
|
@@ -2573,8 +4047,7 @@ function migrateWebdriverioDependencies(workspace, sources, inputs) {
|
|
|
2573
4047
|
overrideMap(path.join(workspace.rootDir, "pnpm-workspace.yaml"), ["overrides"]);
|
|
2574
4048
|
return findings;
|
|
2575
4049
|
}
|
|
2576
|
-
|
|
2577
|
-
//#region src/migration/migrator/vitest-v5.ts
|
|
4050
|
+
// src/migration/migrator/vitest-v5.ts
|
|
2578
4051
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2579
4052
|
"node_modules",
|
|
2580
4053
|
".git",
|
|
@@ -2662,17 +4135,16 @@ function finding(file, code, message, severity = "review", source = "", offset =
|
|
|
2662
4135
|
};
|
|
2663
4136
|
}
|
|
2664
4137
|
/** Follow this project's dependency edges, never an unrelated transitive runner. */
|
|
2665
|
-
function lockedSourceVersion(directory, root, pkg, manager, expectedRunnerPackage) {
|
|
4138
|
+
function lockedSourceVersion(directory, root, pkg, readLockfile, manager, expectedRunnerPackage) {
|
|
2666
4139
|
if (manager !== void 0 && manager !== PackageManager.pnpm) {
|
|
2667
4140
|
const spec = dependency(pkg, "vitest");
|
|
2668
|
-
return spec && !expectedRunnerPackage ? lockedVitestVersion(root, directory, manager, spec) : void 0;
|
|
4141
|
+
return spec && !expectedRunnerPackage ? lockedVitestVersion(root, directory, manager, spec, readLockfile) : void 0;
|
|
2669
4142
|
}
|
|
2670
|
-
const lockfile = path.join(root, "pnpm-lock.yaml");
|
|
2671
|
-
if (!fs.existsSync(lockfile)) return;
|
|
2672
4143
|
try {
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
|
|
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
|
+
});
|
|
2676
4148
|
const importer = record(record(lock?.importers)?.[path.relative(root, directory).replaceAll("\\", "/") || "."]);
|
|
2677
4149
|
if (!importer) return;
|
|
2678
4150
|
const name = dependency(pkg, "vitest") ? "vitest" : "vite-plus";
|
|
@@ -2729,7 +4201,7 @@ function installedSourceVersion(directory, name, expectedPackage, expectedRange)
|
|
|
2729
4201
|
}
|
|
2730
4202
|
return dependency(pkg, LEGACY_RUNNER) ? installedSourceVersion(installed.path, LEGACY_RUNNER) : void 0;
|
|
2731
4203
|
}
|
|
2732
|
-
function sourceVersion(directory, root, pkg, manager) {
|
|
4204
|
+
function sourceVersion(directory, root, pkg, readLockfile, manager) {
|
|
2733
4205
|
const catalog = createCatalogDependencyResolver(root, manager ?? PackageManager.pnpm);
|
|
2734
4206
|
const vitePlus = dependency(pkg, "vite-plus");
|
|
2735
4207
|
const vitePlusRange = vitePlus?.startsWith("catalog:") ? catalog?.(vitePlus, "vite-plus") : vitePlus;
|
|
@@ -2746,7 +4218,7 @@ function sourceVersion(directory, root, pkg, manager) {
|
|
|
2746
4218
|
if (bundled) return bundled;
|
|
2747
4219
|
}
|
|
2748
4220
|
const expectedRunnerPackage = legacyAlias ? LEGACY_RUNNER : void 0;
|
|
2749
|
-
const locked = lockedSourceVersion(directory, root, pkg, manager, expectedRunnerPackage);
|
|
4221
|
+
const locked = lockedSourceVersion(directory, root, pkg, readLockfile, manager, expectedRunnerPackage);
|
|
2750
4222
|
if (locked && (!spec || legacyAlias || !normalizeRange(spec) || satisfies(locked, spec, { includePrerelease: true }))) return locked;
|
|
2751
4223
|
const installed = installedSourceVersion(directory, "vitest", expectedRunnerPackage);
|
|
2752
4224
|
if (installed && (!spec || legacyAlias || satisfies(installed, spec, { includePrerelease: true }))) return installed;
|
|
@@ -2756,7 +4228,7 @@ function sourceVersion(directory, root, pkg, manager) {
|
|
|
2756
4228
|
const bundled = installedSourceVersion(directory, "vite-plus", "vite-plus", vitePlusRange);
|
|
2757
4229
|
if (bundled) return bundled;
|
|
2758
4230
|
}
|
|
2759
|
-
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);
|
|
2760
4232
|
}
|
|
2761
4233
|
function checkNodeRange(file, value, label, findings, publicContract = false) {
|
|
2762
4234
|
if (!publicContract && [
|
|
@@ -2818,7 +4290,7 @@ function migrateNode(file, source, findings) {
|
|
|
2818
4290
|
"version"
|
|
2819
4291
|
]);
|
|
2820
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"]);
|
|
2821
|
-
return applyEdits(source, edits);
|
|
4293
|
+
return applyEdits$1(source, edits);
|
|
2822
4294
|
}
|
|
2823
4295
|
return source;
|
|
2824
4296
|
}
|
|
@@ -2871,7 +4343,7 @@ function scanAndRewriteFile(file, source, project, testMode, mergedConfig) {
|
|
|
2871
4343
|
content: JSON.stringify(result.content)
|
|
2872
4344
|
});
|
|
2873
4345
|
}
|
|
2874
|
-
content = applyEdits(source, edits);
|
|
4346
|
+
content = applyEdits$1(source, edits);
|
|
2875
4347
|
} else if (/^tsconfig(?:\..+)?\.json$/.test(path.basename(file))) {
|
|
2876
4348
|
const types = record(record(parse(source))?.compilerOptions)?.types;
|
|
2877
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."));
|
|
@@ -2902,10 +4374,11 @@ function scanAndRewriteFile(file, source, project, testMode, mergedConfig) {
|
|
|
2902
4374
|
/** Read-only preflight. Call before package-manager conversion, installs,
|
|
2903
4375
|
* catalog updates, or generic import rewrites can erase the source version. */
|
|
2904
4376
|
function planVitestV5Migration(workspace, originalProjects) {
|
|
4377
|
+
const readLockfile = createLockfileReader();
|
|
2905
4378
|
const findings = [];
|
|
2906
4379
|
const directories = [workspace.rootDir, ...(workspace.packages ?? []).map((pkg) => path.resolve(workspace.rootDir, pkg.path))];
|
|
2907
4380
|
const projects = [];
|
|
2908
|
-
const projectSources = new Map(
|
|
4381
|
+
const projectSources = new Map(Array.from(new Set(directories), (directory) => [directory, new Map(filesInProject(directory).map((file) => [file, fs.readFileSync(file, "utf8")]))]));
|
|
2909
4382
|
const allSources = /* @__PURE__ */ new Map();
|
|
2910
4383
|
for (const sources of projectSources.values()) for (const [file, source] of sources) allSources.set(file, source);
|
|
2911
4384
|
const rewrittenSources = new Map(allSources);
|
|
@@ -2920,7 +4393,7 @@ function planVitestV5Migration(workspace, originalProjects) {
|
|
|
2920
4393
|
const pkg = readJson(path.join(directory, "package.json"));
|
|
2921
4394
|
const configFiles = new Set([...allConfigs].filter((file) => sources.has(file)));
|
|
2922
4395
|
const original = originalProjects?.get(directory);
|
|
2923
|
-
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);
|
|
2924
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)));
|
|
2925
4398
|
const options = {
|
|
2926
4399
|
preserveV4: original?.options.preserveV4 ?? (!!version && getMajor(version) < 5),
|
|
@@ -3032,7 +4505,7 @@ function currentProjectConfigs(plan) {
|
|
|
3032
4505
|
const filesByProject = new Map(plan.projects.map((project) => [project.directory, filesInProject(project.directory)]));
|
|
3033
4506
|
const sources = new Map([...filesByProject.values()].flat().map((file) => [file, fs.readFileSync(file, "utf8")]));
|
|
3034
4507
|
const configs = findVitestV5ConfigFiles(sources, findVitestV5ConfigEntries(sources, filesByProject.keys()).flatMap((entry) => entry.file ? [entry.file] : []));
|
|
3035
|
-
return new Map(
|
|
4508
|
+
return new Map(Array.from(filesByProject, ([directory, files]) => [directory, files.filter((file) => configs.has(file))]));
|
|
3036
4509
|
}
|
|
3037
4510
|
/** Run after other migration steps create or merge configs. Keep the source
|
|
3038
4511
|
* version and deferred reviews in memory until the final report; no state file. */
|
|
@@ -3080,8 +4553,7 @@ function finishVitestV5Migration(plan) {
|
|
|
3080
4553
|
const after = planVitestV5Migration(workspaceFromPlan(plan), new Map(plan.projects.map((project) => [project.directory, completedProjects.get(project.directory) ?? project])));
|
|
3081
4554
|
return [...findings, ...after.findings];
|
|
3082
4555
|
}
|
|
3083
|
-
|
|
3084
|
-
//#region src/migration/compat/runner.ts
|
|
4556
|
+
// src/migration/compat/runner.ts
|
|
3085
4557
|
/**
|
|
3086
4558
|
* Resolve the isolated compat worker emitted at `migration/compat/worker.js`.
|
|
3087
4559
|
*
|
|
@@ -3137,8 +4609,7 @@ async function checkRolldownCompatibility(rootDir, report) {
|
|
|
3137
4609
|
for (const warning of compatibilityResult?.warnings ?? []) addMigrationWarning(report, warning);
|
|
3138
4610
|
} catch {}
|
|
3139
4611
|
}
|
|
3140
|
-
|
|
3141
|
-
//#region src/migration/format.ts
|
|
4612
|
+
// src/migration/format.ts
|
|
3142
4613
|
const FORMAT_FAILURE_MESSAGE = "Automatic formatting failed. Run `vp fmt` manually after migration.";
|
|
3143
4614
|
const MAX_FORMAT_ARG_BYTES = process.platform === "win32" ? 24e3 : 1e5;
|
|
3144
4615
|
function chunkPathsByArgLength(paths) {
|
|
@@ -3264,8 +4735,29 @@ async function formatMigratedProject(projectRoot, interactive, report, options =
|
|
|
3264
4735
|
addMigrationWarning(report, FORMAT_FAILURE_MESSAGE);
|
|
3265
4736
|
return false;
|
|
3266
4737
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
4738
|
+
// src/migration/install-result.ts
|
|
4739
|
+
/**
|
|
4740
|
+
* Reconcile a CommandRunSummary from `runViteInstall` with the migration's
|
|
4741
|
+
* duration counter and exit-code state. `runViteInstall` returns
|
|
4742
|
+
* `{ status: 'failed', exitCode }` without throwing; treating that as a success
|
|
4743
|
+
* (incrementing duration unconditionally) would let the migration claim
|
|
4744
|
+
* "Dependencies installed" while node_modules is desynced from the just-mutated
|
|
4745
|
+
* package.json. This helper centralizes the right handling: credit duration on
|
|
4746
|
+
* success, warn + flip exitCode on failure, stay silent on skip.
|
|
4747
|
+
*/
|
|
4748
|
+
function handleInstallResult(installSummary, rootDir, report, options) {
|
|
4749
|
+
if (installSummary.status === "installed") return installSummary.durationMs;
|
|
4750
|
+
if (installSummary.status === "failed") {
|
|
4751
|
+
const exitCode = installSummary.exitCode ?? 1;
|
|
4752
|
+
const message = `Dependency installation failed (exit code ${exitCode}). Run \`vp install\` manually in ${rootDir} to resync node_modules.`;
|
|
4753
|
+
warnMsg(message);
|
|
4754
|
+
report.warnings.push(message);
|
|
4755
|
+
if (options?.propagateExitCode !== false) process.exitCode = exitCode;
|
|
4756
|
+
return 0;
|
|
4757
|
+
}
|
|
4758
|
+
return 0;
|
|
4759
|
+
}
|
|
4760
|
+
// src/migration/npm-reinstall.ts
|
|
3269
4761
|
const VITE_PLUS_CORE_PACKAGE = "@voidzero-dev/vite-plus-core";
|
|
3270
4762
|
function isViteInstallPath(packagePath) {
|
|
3271
4763
|
return packagePath === "node_modules/vite" || packagePath.endsWith("/node_modules/vite");
|
|
@@ -3354,18 +4846,20 @@ function prepareNpmViteAliasReinstall(rootDir, projectPaths = [rootDir]) {
|
|
|
3354
4846
|
}
|
|
3355
4847
|
};
|
|
3356
4848
|
}
|
|
3357
|
-
|
|
3358
|
-
//#region src/migration/setup-plan.ts
|
|
4849
|
+
// src/migration/setup-plan.ts
|
|
3359
4850
|
async function collectGitHooksDecision(rootDir, packageManager, options, packages = []) {
|
|
3360
|
-
|
|
3361
|
-
if (
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
log$1.warn(`⚠ ${reason}`);
|
|
3365
|
-
shouldSetupHooks = false;
|
|
3366
|
-
}
|
|
4851
|
+
const hasExistingHooks = options.hooks === void 0 && hasExistingViteHooksPolicy(rootDir);
|
|
4852
|
+
if (hasExistingHooks && isViteHooksSetupComplete(rootDir)) {
|
|
4853
|
+
log$1(`${muted("•")} Git hooks already configured, skipping`);
|
|
4854
|
+
return false;
|
|
3367
4855
|
}
|
|
3368
|
-
return
|
|
4856
|
+
if (!hasExistingHooks && !await promptGitHooks(options)) return false;
|
|
4857
|
+
const reason = preflightGitHooksSetup(rootDir, packageManager, packages);
|
|
4858
|
+
if (reason) {
|
|
4859
|
+
log.warn(`⚠ ${reason}`);
|
|
4860
|
+
return false;
|
|
4861
|
+
}
|
|
4862
|
+
return true;
|
|
3369
4863
|
}
|
|
3370
4864
|
async function collectAgentInstructionPlan(rootDir, options) {
|
|
3371
4865
|
const existingAgentTargetPaths = options.agent !== void 0 || !options.interactive ? void 0 : detectExistingAgentTargetPaths(rootDir);
|
|
@@ -3469,13 +4963,9 @@ async function collectMigrationSetupPlan(rootDir, packageManager, options, packa
|
|
|
3469
4963
|
...eslintPlan
|
|
3470
4964
|
};
|
|
3471
4965
|
}
|
|
3472
|
-
|
|
3473
|
-
//#region src/migration/bin.ts
|
|
4966
|
+
// src/migration/bin.ts
|
|
3474
4967
|
async function confirmNodeVersionFileMigration(interactive, detection) {
|
|
3475
|
-
const message = {
|
|
3476
|
-
"package.json": "Migrate Volta node version (package.json) to .node-version?",
|
|
3477
|
-
".nvmrc": "Migrate .nvmrc to .node-version?"
|
|
3478
|
-
}[detection.file];
|
|
4968
|
+
const message = { "package.json": "Migrate Volta node version (package.json) to .node-version?" }[detection.file];
|
|
3479
4969
|
if (interactive) {
|
|
3480
4970
|
const confirmed = await confirm({
|
|
3481
4971
|
message,
|
|
@@ -3505,7 +4995,7 @@ async function confirmYarnNodeModulesMode(rootDir, packageManager, packageManage
|
|
|
3505
4995
|
if (packageManager !== PackageManager.yarn) return false;
|
|
3506
4996
|
const pnp = detectYarnPnpMode(rootDir, packageManagerVersion);
|
|
3507
4997
|
if (!pnp) return false;
|
|
3508
|
-
log
|
|
4998
|
+
log.warn(`⚠ Vite+ does not currently support Yarn Plug'n'Play (PnP).`);
|
|
3509
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);
|
|
3510
5000
|
if (interactive) {
|
|
3511
5001
|
const confirmed = await confirm({
|
|
@@ -3609,7 +5099,7 @@ function hasExistingVitePlusMigrationCandidates(workspaceInfo, options) {
|
|
|
3609
5099
|
const eslintProject = detectEslintProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
3610
5100
|
const prettierProject = detectPrettierProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
3611
5101
|
const tsupProject = detectTsupProject(workspaceInfo.rootDir, workspaceInfo.packages);
|
|
3612
|
-
return hasExplicitExistingVitePlusSetupRequest(options) || detectLegacyGitHooksMigrationCandidate(workspaceInfo.rootDir) || hasBaseUrlInWorkspace(workspaceInfo) || eslintProject.hasDependency || prettierProject.hasDependency || tsupProject.hasDependency || detectNodeVersionManagerFile(workspaceInfo.rootDir) !== void 0 || getFrameworkShimCandidates(workspaceInfo.rootDir, workspaceInfo.packages).length > 0;
|
|
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;
|
|
3613
5103
|
}
|
|
3614
5104
|
function getExistingVitePlusSetupOptions(options, useFullMigrationDefaults = false) {
|
|
3615
5105
|
if (useFullMigrationDefaults) return options;
|
|
@@ -3629,6 +5119,9 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
|
|
|
3629
5119
|
if (prettierProject.hasDependency && prettierProject.configFile) migratePrettier = await confirmPrettierMigration(options.interactive);
|
|
3630
5120
|
else if (prettierProject.hasDependency) warnPackageLevelPrettier();
|
|
3631
5121
|
const { migrateTsup, tsupConfigFile } = await collectTsupMigrationDecision(rootDir, options, packages);
|
|
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);
|
|
3632
5125
|
const fixBaseUrl = hasBaseUrlInWorkspace({
|
|
3633
5126
|
rootDir,
|
|
3634
5127
|
packages
|
|
@@ -3646,83 +5139,70 @@ async function collectMigrationPlan(rootDir, detectedPackageManager, detectedPac
|
|
|
3646
5139
|
prettierConfigFile: prettierProject.configFile,
|
|
3647
5140
|
migrateTsup,
|
|
3648
5141
|
tsupConfigFile,
|
|
5142
|
+
migrateTsx,
|
|
5143
|
+
migrateOxnode,
|
|
5144
|
+
migrateTsNode,
|
|
3649
5145
|
fixBaseUrl,
|
|
3650
5146
|
migrateNodeVersionFile,
|
|
3651
5147
|
nodeVersionDetection,
|
|
3652
5148
|
frameworkShimFrameworks
|
|
3653
5149
|
};
|
|
3654
5150
|
}
|
|
3655
|
-
/**
|
|
3656
|
-
* Reconcile a CommandRunSummary from `runViteInstall` with the migration's
|
|
3657
|
-
* duration counter and exit-code state. `runViteInstall` returns
|
|
3658
|
-
* `{ status: 'failed', exitCode }` without throwing; treating that as a success
|
|
3659
|
-
* (incrementing duration unconditionally) would let the migration claim
|
|
3660
|
-
* "Dependencies installed" while node_modules is desynced from the just-mutated
|
|
3661
|
-
* package.json. This helper centralizes the right handling: credit duration on
|
|
3662
|
-
* success, warn + flip exitCode on failure, stay silent on skip.
|
|
3663
|
-
*/
|
|
3664
|
-
function handleInstallResult(installSummary, rootDir, report, options) {
|
|
3665
|
-
if (installSummary.status === "installed") return installSummary.durationMs;
|
|
3666
|
-
if (installSummary.status === "failed") {
|
|
3667
|
-
const exitCode = installSummary.exitCode ?? 1;
|
|
3668
|
-
const message = `Dependency installation failed (exit code ${exitCode}). Run \`vp install\` manually in ${rootDir} to resync node_modules.`;
|
|
3669
|
-
warnMsg(message);
|
|
3670
|
-
report.warnings.push(message);
|
|
3671
|
-
if (options?.propagateExitCode !== false) process.exitCode = exitCode;
|
|
3672
|
-
return 0;
|
|
3673
|
-
}
|
|
3674
|
-
return 0;
|
|
3675
|
-
}
|
|
3676
5151
|
const FULL_MIGRATION_HINT = `${styleText("gray", "•")} Skipped editor, hooks, and lint setup. Run \`vp migrate --full\` to apply them.`;
|
|
3677
5152
|
function showMigrationSummary(options) {
|
|
3678
5153
|
const { projectRoot, packageManager, packageManagerVersion, installDurationMs, finalInstallOk, report, updatedExistingVitePlus, suggestFullMigration } = options;
|
|
3679
5154
|
const projectLabel = displayRelative(projectRoot) || ".";
|
|
3680
5155
|
const configUpdates = report.createdViteConfigCount + report.mergedConfigCount + report.mergedStagedConfigCount + report.inlinedLintStagedConfigCount + report.removedConfigCount + report.tsdownImportCount + report.wrappedPluginConfigCount + report.migratedTaskCacheConfigCount;
|
|
3681
|
-
log(`${styleText("magenta", "◇")} ${updatedExistingVitePlus ? "Updated" : "Migrated"} ${accent(projectLabel)} to Vite+ ${VITE_PLUS_VERSION}`);
|
|
3682
|
-
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}`);
|
|
3683
5158
|
if (report.dependencyUpgrades.length > 0) {
|
|
3684
5159
|
const nameWidth = Math.max(...report.dependencyUpgrades.map((change) => change.name.length));
|
|
3685
5160
|
const fromWidth = Math.max(...report.dependencyUpgrades.map((change) => (change.from ?? "").length));
|
|
3686
|
-
log(`${styleText("gray", "•")} Dependencies:`);
|
|
5161
|
+
log$1(`${styleText("gray", "•")} Dependencies:`);
|
|
3687
5162
|
for (const change of report.dependencyUpgrades) {
|
|
3688
5163
|
const name = change.name.padEnd(nameWidth);
|
|
3689
5164
|
const from = muted((change.from ?? "").padEnd(fromWidth));
|
|
3690
5165
|
const to = styleText("green", change.to);
|
|
3691
|
-
log(` ${name} ${from} → ${to}`);
|
|
5166
|
+
log$1(` ${name} ${from} → ${to}`);
|
|
3692
5167
|
}
|
|
3693
5168
|
}
|
|
3694
|
-
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)}`);
|
|
3695
5170
|
if (configUpdates > 0 || report.rewrittenImportFileCount > 0) {
|
|
3696
5171
|
const parts = [];
|
|
3697
5172
|
if (configUpdates > 0) parts.push(`${configUpdates} ${configUpdates === 1 ? "config update" : "config updates"} applied`);
|
|
3698
5173
|
if (report.rewrittenImportFileCount > 0) parts.push(`${report.rewrittenImportFileCount} ${report.rewrittenImportFileCount === 1 ? "file had" : "files had"} imports rewritten`);
|
|
3699
|
-
log(`${styleText("gray", "•")} ${parts.join(", ")}`);
|
|
5174
|
+
log$1(`${styleText("gray", "•")} ${parts.join(", ")}`);
|
|
3700
5175
|
}
|
|
3701
|
-
if (report.preservedUpstreamVitestImportFileCount > 0) log(`${styleText("gray", "•")} Kept upstream \`vitest\` imports in ${report.preservedUpstreamVitestImportFileCount} ${report.preservedUpstreamVitestImportFileCount === 1 ? "file" : "files"} for @nuxt/test-utils compatibility`);
|
|
3702
|
-
if (report.eslintMigrated) log(`${styleText("gray", "•")} ESLint rules migrated to Oxlint`);
|
|
3703
|
-
if (report.prettierMigrated) log(`${styleText("gray", "•")} Prettier migrated to Oxfmt`);
|
|
3704
|
-
if (report.tsupMigrated) log(`${styleText("gray", "•")} tsup config migrated to tsdown (\`vp pack\`)`);
|
|
3705
|
-
if (report.
|
|
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`);
|
|
3706
5184
|
const setupVpFileCount = report.setupVpVersionUpdatedFileCount;
|
|
3707
5185
|
if (setupVpFileCount > 0) {
|
|
3708
5186
|
const fileLabel = setupVpFileCount === 1 ? "file" : "files";
|
|
3709
|
-
log(`${styleText("gray", "•")} setup-vp updated to ${SETUP_VP_VERSION} in ${setupVpFileCount} GitHub Actions ${fileLabel}`);
|
|
3710
|
-
}
|
|
3711
|
-
if (report.wrappedPluginConfigCount > 0) log(`${styleText("gray", "•")} Inline Vite plugins wrapped with lazyPlugins for check/lint/fmt`);
|
|
3712
|
-
if (report.migratedTaskCacheConfigCount > 0) log(`${styleText("gray", "•")} Task cache settings moved under \`cache\``);
|
|
3713
|
-
if (report.gitHooksConfigured) log(`${styleText("gray", "•")} Git hooks configured`);
|
|
3714
|
-
if (report.frameworkShimAdded) log(`${styleText("gray", "•")} TypeScript shim added for framework component files`);
|
|
3715
|
-
if (report.packageManagerBootstrapConfigured) log(`${styleText("gray", "•")} Package manager settings configured`);
|
|
3716
|
-
if (suggestFullMigration) log(FULL_MIGRATION_HINT);
|
|
3717
|
-
if (report.warnings.length > 0) {
|
|
3718
|
-
log(`${styleText("yellow", "!")} Warnings:`);
|
|
3719
|
-
for (const warning of report.warnings) log(` - ${warning}`);
|
|
5187
|
+
log$1(`${styleText("gray", "•")} setup-vp updated to ${SETUP_VP_VERSION} in ${setupVpFileCount} GitHub Actions ${fileLabel}`);
|
|
3720
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);
|
|
3721
5196
|
if (report.manualSteps.length > 0) {
|
|
3722
|
-
log(`${styleText("blue", "→")} Manual follow-up:`);
|
|
3723
|
-
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}`);
|
|
3724
5199
|
}
|
|
3725
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
|
+
}
|
|
3726
5206
|
async function downloadSupportedPackageManager(options) {
|
|
3727
5207
|
const { rootDir, packageManager, packageManagerVersion, interactive, updateMigrationProgress, failMigrationProgress } = options;
|
|
3728
5208
|
updateMigrationProgress("Preparing migration");
|
|
@@ -3732,11 +5212,11 @@ async function downloadSupportedPackageManager(options) {
|
|
|
3732
5212
|
await upgradeYarn(rootDir, interactive, true);
|
|
3733
5213
|
} else if (packageManager === PackageManager.pnpm && satisfies(downloadResult.version, "< 9.5.0")) {
|
|
3734
5214
|
failMigrationProgress("Migration failed");
|
|
3735
|
-
log
|
|
5215
|
+
log.error(`✘ pnpm@${downloadResult.version} is not supported by auto migration, please upgrade pnpm to >=9.5.0 first`);
|
|
3736
5216
|
cancelAndExit("Vite+ cannot automatically migrate this project yet.", 1);
|
|
3737
5217
|
} else if (packageManager === PackageManager.npm && satisfies(downloadResult.version, "< 8.3.0")) {
|
|
3738
5218
|
failMigrationProgress("Migration failed");
|
|
3739
|
-
log
|
|
5219
|
+
log.error(`✘ npm@${downloadResult.version} is not supported by auto migration, please upgrade npm to >=8.3.0 first`);
|
|
3740
5220
|
cancelAndExit("Vite+ cannot automatically migrate this project yet.", 1);
|
|
3741
5221
|
}
|
|
3742
5222
|
return downloadResult;
|
|
@@ -3753,7 +5233,7 @@ function completeVitestV5Migration(plan, report) {
|
|
|
3753
5233
|
function applyRefreshedVitestV5Migration(plan) {
|
|
3754
5234
|
const refreshed = refreshVitestV5Migration(plan);
|
|
3755
5235
|
if (refreshed.findings.some((finding) => finding.severity === "block")) {
|
|
3756
|
-
log
|
|
5236
|
+
log.warn(formatVitestV5Findings(refreshed));
|
|
3757
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);
|
|
3758
5238
|
}
|
|
3759
5239
|
applyVitestV5Migration(refreshed);
|
|
@@ -3761,6 +5241,7 @@ function applyRefreshedVitestV5Migration(plan) {
|
|
|
3761
5241
|
}
|
|
3762
5242
|
async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vitestV5Plan, preExistingChangedPaths) {
|
|
3763
5243
|
const report = createMigrationReport();
|
|
5244
|
+
report.warnings.push(...detectMigrationWarnings(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages));
|
|
3764
5245
|
const migrationProgress = interactive ? spinner({ indicator: "timer" }) : void 0;
|
|
3765
5246
|
let migrationProgressStarted = false;
|
|
3766
5247
|
const updateMigrationProgress = (message) => {
|
|
@@ -3801,7 +5282,7 @@ async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vi
|
|
|
3801
5282
|
if (plan.convertYarnPnp) {
|
|
3802
5283
|
updateMigrationProgress("Configuring Yarn node-modules mode");
|
|
3803
5284
|
report.packageManagerBootstrapConfigured = configureYarnNodeModulesMode(workspaceInfo.rootDir);
|
|
3804
|
-
if (report.packageManagerBootstrapConfigured) log
|
|
5285
|
+
if (report.packageManagerBootstrapConfigured) log.success("✔ Switched Yarn to node-modules mode");
|
|
3805
5286
|
}
|
|
3806
5287
|
if (plan.migrateNodeVersionFile && plan.nodeVersionDetection) {
|
|
3807
5288
|
updateMigrationProgress("Migrating node version file");
|
|
@@ -3855,6 +5336,18 @@ async function executeMigrationPlan(workspaceInfoOptional, plan, interactive, vi
|
|
|
3855
5336
|
cancelAndExit("Complete the tsup migration manually, then re-run `vp migrate`.", 1);
|
|
3856
5337
|
}
|
|
3857
5338
|
}
|
|
5339
|
+
if (plan.migrateTsx) {
|
|
5340
|
+
updateMigrationProgress("Migrating tsx scripts");
|
|
5341
|
+
migrateTsxToVpx(workspaceInfo.rootDir, workspaceInfo.packages, report);
|
|
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
|
+
}
|
|
3858
5351
|
vitestV5Plan = applyRefreshedVitestV5Migration(vitestV5Plan);
|
|
3859
5352
|
const skipStagedMigration = shouldSkipStagedMigrationForHooks(workspaceInfo.rootDir, plan.shouldSetupHooks, plan.packageManager, workspaceInfo.packages);
|
|
3860
5353
|
updateMigrationProgress("Rewriting configs");
|
|
@@ -3920,11 +5413,11 @@ async function main() {
|
|
|
3920
5413
|
let vitestV5Plan = planVitestV5Migration(workspaceInfoOptional);
|
|
3921
5414
|
const vitestV5Preflight = formatVitestV5Findings(vitestV5Plan);
|
|
3922
5415
|
if (vitestV5Plan.findings.some((finding) => finding.severity === "block")) {
|
|
3923
|
-
log
|
|
5416
|
+
log.warn(vitestV5Preflight);
|
|
3924
5417
|
cancelAndExit("Resolve the blocking Vitest v5 findings, then re-run `vp migrate`. No project files were changed.", 1);
|
|
3925
5418
|
}
|
|
3926
5419
|
const rootPkg = readNearestPackageJson(workspaceInfoOptional.rootDir);
|
|
3927
|
-
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.`);
|
|
3928
5421
|
if (hasVitePlusDependency(rootPkg) && !isForceOverrideMode()) {
|
|
3929
5422
|
let convertYarnPnp = await confirmYarnNodeModulesMode(workspaceInfoOptional.rootDir, workspaceInfoOptional.packageManager, workspaceInfoOptional.packageManagerVersion, options.interactive);
|
|
3930
5423
|
let didMigrate = vitestV5NeedsMigration(vitestV5Plan);
|
|
@@ -3933,6 +5426,7 @@ async function main() {
|
|
|
3933
5426
|
let finalInstallOk = true;
|
|
3934
5427
|
let canFormatMigratedProject = !process.env.VP_SKIP_INSTALL;
|
|
3935
5428
|
const report = createMigrationReport();
|
|
5429
|
+
const migrationWarnings = options.full ? detectMigrationWarnings(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages) : [];
|
|
3936
5430
|
const migrationProgress = options.interactive ? spinner({ indicator: "timer" }) : void 0;
|
|
3937
5431
|
let migrationProgressStarted = false;
|
|
3938
5432
|
const updateMigrationProgress = (message) => {
|
|
@@ -3987,16 +5481,19 @@ async function main() {
|
|
|
3987
5481
|
updateMigrationProgress("Rewriting toolchain imports across the workspace");
|
|
3988
5482
|
const coreMigrationResult = finalizeCoreMigrationForExistingVitePlus(workspaceInfoOptional, true, report, pendingCoreMigration);
|
|
3989
5483
|
if (coreMigrationResult.dependencies || coreMigrationResult.scripts || coreMigrationResult.tsconfigTypes || coreMigrationResult.imports || coreMigrationResult.tsdownConfig || coreMigrationResult.taskCacheConfig) didMigrate = true;
|
|
5484
|
+
if (retryPreservedOxlintConfigs(workspaceInfoOptional.rootDir, true, report, workspaceInfoOptional.packages)) didMigrate = true;
|
|
3990
5485
|
clearMigrationProgress();
|
|
3991
5486
|
const fullSetup = options.full === true;
|
|
3992
5487
|
const skippedSetupCandidates = !fullSetup && !hasExplicitExistingVitePlusSetupRequest(options) && hasExistingVitePlusMigrationCandidates(workspaceInfoOptional, options);
|
|
3993
5488
|
if (!didMigrate && !convertYarnPnp && report.warnings.length === 0 && !vitePlusBootstrapPending && !(fullSetup ? hasExistingVitePlusMigrationCandidates(workspaceInfoOptional, options) : hasExplicitExistingVitePlusSetupRequest(options))) {
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
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);
|
|
3997
5493
|
outro(`This project is already using Vite+! ${accent("Happy coding!")}`);
|
|
3998
5494
|
return;
|
|
3999
5495
|
}
|
|
5496
|
+
report.warnings.push(...migrationWarnings);
|
|
4000
5497
|
for (const warning of coreMigrationResult.taskCacheWarnings) addMigrationWarning(report, warning);
|
|
4001
5498
|
const setupOptions = getExistingVitePlusSetupOptions(options, fullSetup);
|
|
4002
5499
|
const plan = await collectMigrationSetupPlan(workspaceInfoOptional.rootDir, packageManager, setupOptions, workspaceInfoOptional.packages, fullSetup);
|
|
@@ -4077,6 +5574,33 @@ async function main() {
|
|
|
4077
5574
|
tsupMigrated = true;
|
|
4078
5575
|
}
|
|
4079
5576
|
}
|
|
5577
|
+
if (fullSetup && detectTsxProject(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages)) {
|
|
5578
|
+
if (options.interactive) clearMigrationProgress();
|
|
5579
|
+
if (await confirmTsxMigration(options.interactive)) {
|
|
5580
|
+
updateMigrationProgress("Migrating tsx scripts");
|
|
5581
|
+
const tsxResult = migrateTsxToVpx(workspaceInfoOptional.rootDir, workspaceInfoOptional.packages, report);
|
|
5582
|
+
didMigrate ||= tsxResult.changed;
|
|
5583
|
+
needsInstall ||= tsxResult.dependencyRemoved;
|
|
5584
|
+
}
|
|
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
|
+
}
|
|
4080
5604
|
if (fullSetup) {
|
|
4081
5605
|
const nodeVersionDetection = detectNodeVersionManagerFile(workspaceInfoOptional.rootDir);
|
|
4082
5606
|
if (nodeVersionDetection) {
|
|
@@ -4087,7 +5611,7 @@ async function main() {
|
|
|
4087
5611
|
if (convertYarnPnp) {
|
|
4088
5612
|
updateMigrationProgress("Configuring Yarn node-modules mode");
|
|
4089
5613
|
if (configureYarnNodeModulesMode(workspaceInfoOptional.rootDir)) {
|
|
4090
|
-
log
|
|
5614
|
+
log.success("✔ Switched Yarn to node-modules mode");
|
|
4091
5615
|
report.packageManagerBootstrapConfigured = true;
|
|
4092
5616
|
didMigrate = true;
|
|
4093
5617
|
needsInstall = true;
|
|
@@ -4102,7 +5626,10 @@ async function main() {
|
|
|
4102
5626
|
].includes(path.basename(file)));
|
|
4103
5627
|
if (eslintMigrated || prettierMigrated || tsupMigrated) {
|
|
4104
5628
|
updateMigrationProgress("Rewriting configs");
|
|
4105
|
-
|
|
5629
|
+
const rootDir = workspaceInfoOptional.rootDir;
|
|
5630
|
+
const oxlintConfigs = workspaceInfoOptional.isMonorepo ? prepareOxlintConfigs([rootDir, ...workspaceInfoOptional.packages.map((pkg) => path.join(rootDir, pkg.path))]) : void 0;
|
|
5631
|
+
mergeViteConfigFiles(rootDir, true, report, workspaceInfoOptional.packages, rootDir, oxlintConfigs);
|
|
5632
|
+
if (oxlintConfigs) removeMigratedOxlintConfigs(oxlintConfigs);
|
|
4106
5633
|
if (tsupMigrated) {
|
|
4107
5634
|
mergeTsdownConfigFile(workspaceInfoOptional.rootDir, true, report);
|
|
4108
5635
|
for (const pkg of workspaceInfoOptional.packages ?? []) mergeTsdownConfigFile(path.join(workspaceInfoOptional.rootDir, pkg.path), true, report);
|
|
@@ -4187,7 +5714,7 @@ async function main() {
|
|
|
4187
5714
|
});
|
|
4188
5715
|
} else {
|
|
4189
5716
|
clearMigrationProgress();
|
|
4190
|
-
if (skippedSetupCandidates) log(FULL_MIGRATION_HINT);
|
|
5717
|
+
if (skippedSetupCandidates) log$1(FULL_MIGRATION_HINT);
|
|
4191
5718
|
outro(`This project is already using Vite+! ${accent("Happy coding!")}`);
|
|
4192
5719
|
}
|
|
4193
5720
|
return;
|
|
@@ -4204,9 +5731,8 @@ async function main() {
|
|
|
4204
5731
|
});
|
|
4205
5732
|
}
|
|
4206
5733
|
main().catch((err) => {
|
|
4207
|
-
log
|
|
5734
|
+
log.error(err.message);
|
|
4208
5735
|
console.error(err);
|
|
4209
5736
|
process.exit(1);
|
|
4210
5737
|
});
|
|
4211
|
-
//#endregion
|
|
4212
5738
|
export {};
|