@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.
@@ -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) or in Turbo's
95
- * framework table matches, and each core cannot key (a literal one is
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(FRAMEWORK_ENV.flatMap((f) => f.env).filter((e) => e.includes('*')))
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
  /**
@@ -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) => console.error(`[vx] plugin '@vzn/vx-migrate': ${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's own default when a token or a team is set and no URL is:
305
- // Vercel's hosted Remote Cache. A token alone is a configured cache, as it
306
- // is for `turbo`; a URL or a team without one asks the OS keychain at
307
- // `open`; none of them is the declined, local run.
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 raw: unknown
916
+ function localConfigOf(root: string, warn: (message: string) => void): TurboLocalConfig {
917
+ let text: string
905
918
  try {
906
- raw = JSON.parse(readFileSync(path.join(root, '.turbo', 'config.json'), 'utf8'))
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
- if (typeof raw !== 'object' || raw === null) return {}
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
- (m) => ctx.warn(`[vx] plugin '@vzn/vx-migrate': ${m}`),
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: [...] }, cache: { outputs: { files: [...] } }\` with the real ones${have}; ` +
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['cache'] = { outputs: outputs.lists }
484
+ task['outputs'] = outputs.lists
485
+ task['cache'] = true
485
486
  return { name, todos, task }
486
487
  }
487
488