@vzn/vx-migrate 0.0.650 → 0.0.652
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/README.md +30 -25
- package/package.json +2 -2
- package/src/adopt.ts +6 -4
- package/src/bin.ts +6 -6
- package/src/migrate-scripts.ts +2 -2
- package/src/migrate-turbo.ts +11 -4
- package/src/migrate.ts +4 -3
- package/src/nx/nx-map.ts +22 -7
- package/src/package-fs.ts +18 -2
- package/src/remote-refusal.ts +6 -1
- package/src/shared-outputs.ts +22 -16
- package/src/tracked-outputs.ts +1 -1
- package/src/turbo/index.ts +47 -0
- package/src/turbo/root-engines.ts +19 -0
- package/src/turbo/turbo-map.ts +210 -29
- package/src/turbo/turbo-reads.ts +95 -4
- package/src/turbo-cache/index.ts +38 -11
- package/src/vite-task/vite-task-map.ts +3 -2
package/src/turbo/turbo-reads.ts
CHANGED
|
@@ -37,6 +37,8 @@ export const FRAMEWORK_ENV: ReadonlyArray<{
|
|
|
37
37
|
env: readonly string[]
|
|
38
38
|
all: boolean
|
|
39
39
|
dependencies: readonly string[]
|
|
40
|
+
/** Turbo's `envConditionals`: `include` is keyed while `key` holds `value`. */
|
|
41
|
+
conditionals?: ReadonlyArray<{ key: string; value: string; include: readonly string[] }>
|
|
40
42
|
}> = [
|
|
41
43
|
{ slug: 'astro', env: ['PUBLIC_*'], all: true, dependencies: ['astro'] },
|
|
42
44
|
{ slug: 'blitzjs', env: ['NEXT_PUBLIC_*'], all: true, dependencies: ['blitz'] },
|
|
@@ -53,6 +55,10 @@ export const FRAMEWORK_ENV: ReadonlyArray<{
|
|
|
53
55
|
env: ['NEXT_PUBLIC_*', 'NEXT_DEPLOYMENT_ID'],
|
|
54
56
|
all: true,
|
|
55
57
|
dependencies: ['next'],
|
|
58
|
+
// Skew protection builds the deployment id into the app (vercel/turborepo#9853).
|
|
59
|
+
conditionals: [
|
|
60
|
+
{ key: 'VERCEL_SKEW_PROTECTION_ENABLED', value: '1', include: ['VERCEL_DEPLOYMENT_ID'] },
|
|
61
|
+
],
|
|
56
62
|
},
|
|
57
63
|
{
|
|
58
64
|
slug: 'nitro',
|
|
@@ -85,20 +91,105 @@ export const FRAMEWORK_ENV: ReadonlyArray<{
|
|
|
85
91
|
},
|
|
86
92
|
]
|
|
87
93
|
|
|
94
|
+
/** The variables each framework conditional reads; a live mapping is keyed on their values. */
|
|
95
|
+
export const ENV_CONDITION_KEYS: readonly string[] = FRAMEWORK_ENV.flatMap((f) =>
|
|
96
|
+
(f.conditionals ?? []).map((c) => c.key),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Turbo's own env, from crates/turborepo-env (2026-10-27, edace4621): the
|
|
100
|
+
// promise is a Turbo repo run unchanged, so these mirror it exactly.
|
|
101
|
+
/** `DEFAULT_ENV_VARS`: hashed into every task as if listed in `globalEnv` (a `!` there takes them back). */
|
|
102
|
+
export const TURBO_DEFAULT_ENV: readonly string[] = ['VERCEL_ANALYTICS_ID', 'VERCEL_TARGET_ENV']
|
|
103
|
+
/** `BUILTIN_PASS_THROUGH_ENV`: passed to every task, never hashed. */
|
|
104
|
+
export const TURBO_BUILTIN_PASS_THROUGH: readonly string[] = [
|
|
105
|
+
'HOME',
|
|
106
|
+
'USER',
|
|
107
|
+
'TZ',
|
|
108
|
+
'LANG',
|
|
109
|
+
'SHELL',
|
|
110
|
+
'PWD',
|
|
111
|
+
'XDG_DATA_*',
|
|
112
|
+
'XDG_RUNTIME_DIR',
|
|
113
|
+
'XAUTHORITY',
|
|
114
|
+
'DBUS_SESSION_BUS_ADDRESS',
|
|
115
|
+
'CI',
|
|
116
|
+
'NODE_OPTIONS',
|
|
117
|
+
'COREPACK_*',
|
|
118
|
+
'LD_LIBRARY_PATH',
|
|
119
|
+
'DYLD_FALLBACK_LIBRARY_PATH',
|
|
120
|
+
'LIBPATH',
|
|
121
|
+
'LD_PRELOAD',
|
|
122
|
+
'DYLD_INSERT_LIBRARIES',
|
|
123
|
+
'COLORTERM',
|
|
124
|
+
'FORCE_COLOR',
|
|
125
|
+
'NO_COLOR',
|
|
126
|
+
'TERM',
|
|
127
|
+
'TERM_PROGRAM',
|
|
128
|
+
'DISPLAY',
|
|
129
|
+
'TMP',
|
|
130
|
+
'TEMP',
|
|
131
|
+
'WINDIR',
|
|
132
|
+
'ProgramFiles',
|
|
133
|
+
'ProgramFiles(x86)',
|
|
134
|
+
'VSCODE_*',
|
|
135
|
+
'ELECTRON_RUN_AS_NODE',
|
|
136
|
+
'DOCKER_*',
|
|
137
|
+
'BUILDKIT_*',
|
|
138
|
+
'COMPOSE_*',
|
|
139
|
+
'JB_IDE_*',
|
|
140
|
+
'JB_INTERPRETER',
|
|
141
|
+
'_JETBRAINS_TEST_RUNNER_RUN_SCOPE_TYPE',
|
|
142
|
+
'TURBO_*',
|
|
143
|
+
'VERCEL',
|
|
144
|
+
'VERCEL_*',
|
|
145
|
+
'NEXT_*',
|
|
146
|
+
'USE_OUTPUT_FOR_EDGE_FUNCTIONS',
|
|
147
|
+
'NOW_BUILDER',
|
|
148
|
+
'VC_MICROFRONTENDS_CONFIG_FILE_NAME',
|
|
149
|
+
'GITHUB_*',
|
|
150
|
+
'RUNNER_*',
|
|
151
|
+
'__NIXOS_*',
|
|
152
|
+
'NIX_*',
|
|
153
|
+
'APPDATA',
|
|
154
|
+
'LOCALAPPDATA',
|
|
155
|
+
'PATH',
|
|
156
|
+
'PROGRAMDATA',
|
|
157
|
+
'SYSTEMROOT',
|
|
158
|
+
'SYSTEMDRIVE',
|
|
159
|
+
'USERPROFILE',
|
|
160
|
+
'HOMEDRIVE',
|
|
161
|
+
'HOMEPATH',
|
|
162
|
+
'PNPM_HOME',
|
|
163
|
+
'pnpm_config_verify_deps_before_run',
|
|
164
|
+
'NPM_CONFIG_STORE_DIR',
|
|
165
|
+
'COMSPEC',
|
|
166
|
+
'PATHEXT',
|
|
167
|
+
]
|
|
168
|
+
|
|
88
169
|
/** A name core takes in `inputs.env` and `exec.env.passThrough`. */
|
|
89
170
|
export const keyable = (name: string): boolean =>
|
|
90
171
|
name.length > 0 && !name.startsWith('!') && !/[*?[\]{}=\0]/.test(name)
|
|
91
172
|
|
|
92
173
|
/**
|
|
93
174
|
* The names of `names` a live mapping can read differently: each a `*` in
|
|
94
|
-
* one of `configs` (turbo.json texts, any string, a superset)
|
|
95
|
-
* framework table
|
|
175
|
+
* one of `configs` (turbo.json texts, any string, a superset), in Turbo's
|
|
176
|
+
* framework table or in its builtin env matches, each of those tables'
|
|
177
|
+
* literals, and each core cannot key (a literal one is
|
|
96
178
|
* dropped only when no variable has it). The kept mapping keyed on every
|
|
97
179
|
* name, so any variable that came or went (a CI step's, `VX_DEBUG_TIMING`)
|
|
98
180
|
* remapped the workspace: ~330 ms a run on vercel/ai.
|
|
99
181
|
*/
|
|
100
182
|
export function envNamesThatMap(configs: readonly string[], names: readonly string[]): string[] {
|
|
101
|
-
const patterns = new Set(
|
|
183
|
+
const patterns = new Set(
|
|
184
|
+
[...FRAMEWORK_ENV.flatMap((f) => f.env), ...TURBO_BUILTIN_PASS_THROUGH].filter((e) =>
|
|
185
|
+
e.includes('*'),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
// A builtin or conditional literal is passed or keyed only when set.
|
|
189
|
+
const literals = new Set([
|
|
190
|
+
...TURBO_BUILTIN_PASS_THROUGH.filter((e) => !e.includes('*')),
|
|
191
|
+
...FRAMEWORK_ENV.flatMap((f) => (f.conditionals ?? []).flatMap((c) => c.include)),
|
|
192
|
+
])
|
|
102
193
|
for (const text of configs)
|
|
103
194
|
for (const m of text.matchAll(/"((?:[^"\\]|\\.)*)"/g))
|
|
104
195
|
if (m[1]!.includes('*')) patterns.add(m[1]!)
|
|
@@ -110,7 +201,7 @@ export function envNamesThatMap(configs: readonly string[], names: readonly stri
|
|
|
110
201
|
`^${p.replaceAll('\\', '').replace(/^!/, '').split('*').map(RegExp.escape).join('.*')}$`,
|
|
111
202
|
),
|
|
112
203
|
)
|
|
113
|
-
return names.filter((n) => !keyable(n) || res.some((re) => re.test(n)))
|
|
204
|
+
return names.filter((n) => !keyable(n) || literals.has(n) || res.some((re) => re.test(n)))
|
|
114
205
|
}
|
|
115
206
|
|
|
116
207
|
/**
|
package/src/turbo-cache/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type RemoteOpenContext,
|
|
24
24
|
type VxPlugin,
|
|
25
25
|
refuseUnknownOptions,
|
|
26
|
+
ui,
|
|
26
27
|
UserError,
|
|
27
28
|
type PluginOptionKinds,
|
|
28
29
|
} from '@vzn/vx'
|
|
@@ -265,7 +266,7 @@ export function resolveTurboCacheConfig(
|
|
|
265
266
|
file: TurboJsonRemoteCache = {},
|
|
266
267
|
/** The repo's `.turbo/config.json` (`turbo link` writes it): below the environment, above turbo.json. */
|
|
267
268
|
local: TurboLocalConfig = {},
|
|
268
|
-
warn: (message: string) => void = (m) =>
|
|
269
|
+
warn: (message: string) => void = (m) => ui.say('warn', `[vx] plugin '@vzn/vx-migrate': ${m}`),
|
|
269
270
|
): TurboCacheConfig | undefined {
|
|
270
271
|
// `enabled: false` turns Turbo's remote cache off whatever the env says;
|
|
271
272
|
// options name a cache for vx alone, and win.
|
|
@@ -301,10 +302,20 @@ export function resolveTurboCacheConfig(
|
|
|
301
302
|
fromFile(file.teamId)
|
|
302
303
|
const teamSlug =
|
|
303
304
|
options.teamSlug ?? set('TURBO_TEAM') ?? fromFile(local.teamSlug) ?? fromFile(file.teamSlug)
|
|
304
|
-
// Turbo
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
|
|
305
|
+
// Turbo uses its remote only when linked, a team set (`is_linked`): a
|
|
306
|
+
// token without one ran remote here, team-less, where `turbo` runs local.
|
|
307
|
+
// An option naming the cache is vx's own, and stands.
|
|
308
|
+
const named =
|
|
309
|
+
options.url !== undefined || options.token !== undefined || options.oidc !== undefined
|
|
310
|
+
if (!named && teamId === undefined && teamSlug === undefined) {
|
|
311
|
+
if (token !== undefined)
|
|
312
|
+
warn(
|
|
313
|
+
'a Turbo token is set but no team (TURBO_TEAM, TURBO_TEAMID or .turbo/config.json): remote cache off for this run, as for turbo',
|
|
314
|
+
)
|
|
315
|
+
return undefined
|
|
316
|
+
}
|
|
317
|
+
// Turbo's own default when no URL is set: Vercel's hosted Remote Cache.
|
|
318
|
+
// A team without a token asks the OS keychain at `open`.
|
|
308
319
|
// Each source with its name: TURBO_API was refused as a `url`
|
|
309
320
|
// option the workspace never set.
|
|
310
321
|
const [rawUrl, urlFrom] =
|
|
@@ -899,16 +910,31 @@ export class TurboRemoteCache implements RemoteCacheLayer {
|
|
|
899
910
|
/**
|
|
900
911
|
* The repo's `.turbo/config.json`, by Turbo's field names and their
|
|
901
912
|
* aliases (`apiurl`, `ApiUrl`, `APIURL`, …); `{}` when absent or unreadable.
|
|
913
|
+
* One that does not parse is named: Turbo refuses it, and read as absent
|
|
914
|
+
* in silence a linked repo lost its remote with no word.
|
|
902
915
|
*/
|
|
903
|
-
function localConfigOf(root: string): TurboLocalConfig {
|
|
904
|
-
let
|
|
916
|
+
function localConfigOf(root: string, warn: (message: string) => void): TurboLocalConfig {
|
|
917
|
+
let text: string
|
|
905
918
|
try {
|
|
906
|
-
|
|
919
|
+
text = readFileSync(path.join(root, '.turbo', 'config.json'), 'utf8')
|
|
907
920
|
// quiet: a local config file: none read is none linked
|
|
908
921
|
} catch {
|
|
909
922
|
return {}
|
|
910
923
|
}
|
|
911
|
-
|
|
924
|
+
let raw: unknown
|
|
925
|
+
try {
|
|
926
|
+
raw = JSON.parse(text)
|
|
927
|
+
} catch (err) {
|
|
928
|
+
warn(
|
|
929
|
+
`.turbo/config.json does not parse as a JSON object (${(err as Error).message}): read as absent`,
|
|
930
|
+
)
|
|
931
|
+
return {}
|
|
932
|
+
}
|
|
933
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
934
|
+
const kind = Array.isArray(raw) ? 'an array' : raw === null ? 'null' : `a ${typeof raw}`
|
|
935
|
+
warn(`.turbo/config.json does not parse as a JSON object (${kind}): read as absent`)
|
|
936
|
+
return {}
|
|
937
|
+
}
|
|
912
938
|
const byLower = new Map(Object.entries(raw).map(([k, v]) => [k.toLowerCase(), v]))
|
|
913
939
|
return {
|
|
914
940
|
apiUrl: byLower.get('apiurl'),
|
|
@@ -1004,12 +1030,13 @@ export function turboCache(options: TurboCacheOptions = {}): VxPlugin {
|
|
|
1004
1030
|
let opened: { layer: TurboRemoteCache; warn: (m: string) => void } | undefined
|
|
1005
1031
|
return definePlugin(import.meta, {
|
|
1006
1032
|
cache(ctx): RemoteCacheLayer | undefined {
|
|
1033
|
+
const warn = (m: string) => ctx.warn(`[vx] plugin '@vzn/vx-migrate': ${m}`)
|
|
1007
1034
|
const config = resolveTurboCacheConfig(
|
|
1008
1035
|
options,
|
|
1009
1036
|
Bun.env,
|
|
1010
1037
|
remoteCacheOf(ctx.workspaceRoot),
|
|
1011
|
-
localConfigOf(ctx.workspaceRoot),
|
|
1012
|
-
|
|
1038
|
+
localConfigOf(ctx.workspaceRoot, warn),
|
|
1039
|
+
warn,
|
|
1013
1040
|
)
|
|
1014
1041
|
if (config === undefined) return undefined
|
|
1015
1042
|
// A signed download waits in a temp until its tag checks: in vx's
|
|
@@ -396,7 +396,7 @@ function autoTodo(what: string, known?: Record<string, unknown>): string {
|
|
|
396
396
|
const have = known === undefined ? '' : ` (declared so far: \`${JSON.stringify(known)}\`)`
|
|
397
397
|
return (
|
|
398
398
|
`cache: Vite Task traced this task's ${what} and vx infers none — add ` +
|
|
399
|
-
`\`inputs: { files: [...] },
|
|
399
|
+
`\`inputs: { files: [...] }, outputs: { files: [...] }, cache: true\` with the real ones${have}; ` +
|
|
400
400
|
'without it the task always runs'
|
|
401
401
|
)
|
|
402
402
|
}
|
|
@@ -481,7 +481,8 @@ function mapTask(name: string, def: TaskDef, ctx: TaskCtx): GeneratedTask {
|
|
|
481
481
|
todos.push(ownFileTodo(ownFile))
|
|
482
482
|
return { name, todos, task }
|
|
483
483
|
}
|
|
484
|
-
task['
|
|
484
|
+
task['outputs'] = outputs.lists
|
|
485
|
+
task['cache'] = true
|
|
485
486
|
return { name, todos, task }
|
|
486
487
|
}
|
|
487
488
|
|