@vzn/vx-migrate 0.0.645 → 0.0.648
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 +77 -73
- package/package.json +3 -3
- package/src/adopt.ts +53 -12
- package/src/adoption-plugin.ts +9 -11
- package/src/bin.ts +21 -1
- package/src/glob-grammar.ts +82 -0
- package/src/index.ts +36 -10
- package/src/mapping-cache.ts +14 -6
- package/src/migrate-nx.ts +7 -4
- package/src/migrate-scripts.ts +10 -102
- package/src/migrate-turbo.ts +2 -6
- package/src/migrate.ts +57 -38
- package/src/nx/index.ts +19 -5
- package/src/nx/nx-inputs.ts +44 -9
- package/src/nx/nx-map.ts +65 -25
- package/src/nx/nx-outputs.ts +16 -6
- package/src/nx/nx-upstream.ts +15 -7
- package/src/nx-cache/index.ts +60 -50
- package/src/nx-exec.cjs +2 -2
- package/src/package-fs.ts +6 -1
- package/src/packages.ts +9 -7
- package/src/plugin-gaps.ts +32 -6
- package/src/remote-cache.ts +5 -1
- package/src/remote-deadline.ts +7 -1
- package/src/remote-refusal.ts +31 -0
- package/src/remote-retry.ts +9 -4
- package/src/remote-url.ts +28 -0
- package/src/script-command.ts +110 -4
- package/src/shared-outputs.ts +24 -12
- package/src/tracked-outputs.ts +11 -7
- package/src/turbo/index.ts +33 -17
- package/src/turbo/turbo-map.ts +97 -192
- package/src/turbo/turbo-reads.ts +183 -0
- package/src/turbo-cache/index.ts +261 -102
- package/src/turbo-cache/oidc.ts +52 -26
- package/src/turbo-cache/scope.ts +5 -3
- package/src/vite-task/vite-task-map.ts +11 -6
- package/src/workspace-plugins.ts +5 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// What a turbo() run reads before it knows whether its kept mapping still
|
|
2
|
+
// holds: the mapping itself (`turbo-map.ts`, ~240 KB with what it imports)
|
|
3
|
+
// loads only when it does not.
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { relPosix } from '../paths.js'
|
|
7
|
+
|
|
8
|
+
/** Turbo's name for the workspace root in `//#task` and `extends`. */
|
|
9
|
+
export const ROOT = '//'
|
|
10
|
+
|
|
11
|
+
// Turbo 2's framework inference: a package that depends on a framework
|
|
12
|
+
// has its variables hashed into its tasks and passed to them, with
|
|
13
|
+
// nothing in turbo.json saying so. vx env names are explicit, so the
|
|
14
|
+
// variables were stripped in silence and a build that inlines them
|
|
15
|
+
// (Next's `NEXT_PUBLIC_*`) read empty values (item 940). A live mapping
|
|
16
|
+
// (`envNames`) infers them as Turbo does; the migrate CLI lists the names
|
|
17
|
+
// the package's own files spell (`sourceNames`) and says so in a note.
|
|
18
|
+
// Turbo's own table (`packages/turbo-types/src/json/frameworks.json`),
|
|
19
|
+
// in its order: a package takes the FIRST framework it matches, `all`
|
|
20
|
+
// needing every dependency and `some` any one.
|
|
21
|
+
const NITRO_ENV = [
|
|
22
|
+
'NITRO_*',
|
|
23
|
+
'SERVER_*',
|
|
24
|
+
'AWS_APP_ID',
|
|
25
|
+
'INPUT_AZURE_STATIC_WEB_APPS_API_TOKEN',
|
|
26
|
+
'CLEAVR',
|
|
27
|
+
'CF_PAGES',
|
|
28
|
+
'FIREBASE_APP_HOSTING',
|
|
29
|
+
'NETLIFY',
|
|
30
|
+
'STORMKIT',
|
|
31
|
+
'NOW_BUILDER',
|
|
32
|
+
'ZEABUR',
|
|
33
|
+
'RENDER',
|
|
34
|
+
]
|
|
35
|
+
export const FRAMEWORK_ENV: ReadonlyArray<{
|
|
36
|
+
slug: string
|
|
37
|
+
env: readonly string[]
|
|
38
|
+
all: boolean
|
|
39
|
+
dependencies: readonly string[]
|
|
40
|
+
}> = [
|
|
41
|
+
{ slug: 'astro', env: ['PUBLIC_*'], all: true, dependencies: ['astro'] },
|
|
42
|
+
{ slug: 'blitzjs', env: ['NEXT_PUBLIC_*'], all: true, dependencies: ['blitz'] },
|
|
43
|
+
{
|
|
44
|
+
slug: 'create-react-app',
|
|
45
|
+
env: ['REACT_APP_*'],
|
|
46
|
+
all: false,
|
|
47
|
+
dependencies: ['react-scripts', 'react-dev-utils'],
|
|
48
|
+
},
|
|
49
|
+
{ slug: 'expo', env: ['EXPO_PUBLIC_*'], all: true, dependencies: ['expo'] },
|
|
50
|
+
{ slug: 'gatsby', env: ['GATSBY_*'], all: true, dependencies: ['gatsby'] },
|
|
51
|
+
{
|
|
52
|
+
slug: 'nextjs',
|
|
53
|
+
env: ['NEXT_PUBLIC_*', 'NEXT_DEPLOYMENT_ID'],
|
|
54
|
+
all: true,
|
|
55
|
+
dependencies: ['next'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
slug: 'nitro',
|
|
59
|
+
env: NITRO_ENV,
|
|
60
|
+
all: false,
|
|
61
|
+
dependencies: ['nitropack', 'nitropack-nightly', 'nitro', 'nitro-nightly'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
slug: 'nuxtjs',
|
|
65
|
+
env: ['NUXT_*', 'NUXT_ENV_*', ...NITRO_ENV, 'LAUNCH_EDITOR'],
|
|
66
|
+
all: false,
|
|
67
|
+
dependencies: ['nuxt', 'nuxt-edge', 'nuxt3', 'nuxt3-edge'],
|
|
68
|
+
},
|
|
69
|
+
{ slug: 'redwoodjs', env: ['REDWOOD_ENV_*'], all: true, dependencies: ['@redwoodjs/core'] },
|
|
70
|
+
{
|
|
71
|
+
slug: 'remix',
|
|
72
|
+
env: ['REMIX_*'],
|
|
73
|
+
all: false,
|
|
74
|
+
dependencies: ['@remix-run/dev', '@remix-run/react', '@remix-run/serve', '@react-router/dev'],
|
|
75
|
+
},
|
|
76
|
+
{ slug: 'sanity', env: ['SANITY_STUDIO_*'], all: true, dependencies: ['@sanity/cli'] },
|
|
77
|
+
{ slug: 'solidstart', env: ['VITE_*'], all: true, dependencies: ['solid-js', 'solid-start'] },
|
|
78
|
+
{ slug: 'sveltekit', env: ['VITE_*', 'PUBLIC_*'], all: true, dependencies: ['@sveltejs/kit'] },
|
|
79
|
+
{ slug: 'vite', env: ['VITE_*'], all: true, dependencies: ['vite'] },
|
|
80
|
+
{
|
|
81
|
+
slug: 'vue',
|
|
82
|
+
env: ['VUE_APP_*', 'LAUNCH_EDITOR'],
|
|
83
|
+
all: true,
|
|
84
|
+
dependencies: ['@vue/cli-service'],
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
/** A name core takes in `inputs.env` and `exec.env.passThrough`. */
|
|
89
|
+
export const keyable = (name: string): boolean =>
|
|
90
|
+
name.length > 0 && !name.startsWith('!') && !/[*?[\]{}=\0]/.test(name)
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 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
|
|
96
|
+
* dropped only when no variable has it). The kept mapping keyed on every
|
|
97
|
+
* name, so any variable that came or went (a CI step's, `VX_DEBUG_TIMING`)
|
|
98
|
+
* remapped the workspace: ~330 ms a run on vercel/ai.
|
|
99
|
+
*/
|
|
100
|
+
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('*')))
|
|
102
|
+
for (const text of configs)
|
|
103
|
+
for (const m of text.matchAll(/"((?:[^"\\]|\\.)*)"/g))
|
|
104
|
+
if (m[1]!.includes('*')) patterns.add(m[1]!)
|
|
105
|
+
// Backslashes and a leading `!` dropped, every `*` a wildcard: wider
|
|
106
|
+
// than Turbo's reading, never narrower.
|
|
107
|
+
const res = [...patterns].map(
|
|
108
|
+
(p) =>
|
|
109
|
+
new RegExp(
|
|
110
|
+
`^${p.replaceAll('\\', '').replace(/^!/, '').split('*').map(RegExp.escape).join('.*')}$`,
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
return names.filter((n) => !keyable(n) || res.some((re) => re.test(n)))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A directory's Turbo config: `turbo.json`, else `turbo.jsonc` (Turbo 2.5+
|
|
118
|
+
* reads either). Only `turbo.json` was looked for, so a `turbo.jsonc` root
|
|
119
|
+
* failed the run with ENOENT and a package's `turbo.jsonc` overlay was
|
|
120
|
+
* skipped in silence — its `inputs` then keyed nothing (item 938).
|
|
121
|
+
*/
|
|
122
|
+
export async function turboConfigFile(dir: string): Promise<string | null> {
|
|
123
|
+
for (const name of ['turbo.json', 'turbo.jsonc']) {
|
|
124
|
+
const file = path.join(dir, name)
|
|
125
|
+
if (await Bun.file(file).exists()) return file
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The workspace root as a project to name when turbo.json declares `//#`
|
|
132
|
+
* tasks: its package name and directory. Null with no such task, a
|
|
133
|
+
* nameless root, or a file the mapping refuses (it names the file).
|
|
134
|
+
*/
|
|
135
|
+
export async function rootTaskProject(root: string): Promise<{ name: string; dir: string } | null> {
|
|
136
|
+
const file = await turboConfigFile(root)
|
|
137
|
+
if (file === null) return null
|
|
138
|
+
let turbo: { tasks?: unknown; pipeline?: unknown } | null
|
|
139
|
+
let pkg: { name?: unknown } | null
|
|
140
|
+
try {
|
|
141
|
+
turbo = Bun.JSONC.parse(await Bun.file(file).text()) as typeof turbo
|
|
142
|
+
pkg = (await Bun.file(path.join(root, 'package.json')).json()) as typeof pkg
|
|
143
|
+
} catch {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
const tasks = turbo?.tasks ?? turbo?.pipeline
|
|
147
|
+
const hasRootTask =
|
|
148
|
+
typeof tasks === 'object' &&
|
|
149
|
+
tasks !== null &&
|
|
150
|
+
Object.keys(tasks).some((k) => k.startsWith(`${ROOT}#`))
|
|
151
|
+
if (!hasRootTask || typeof pkg?.name !== 'string' || pkg.name === '') return null
|
|
152
|
+
return { name: pkg.name, dir: root }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The microfrontends configs Turbo hashes into every task: each package's
|
|
157
|
+
* (the root's too) `microfrontends.json`, else `microfrontends.jsonc`, or
|
|
158
|
+
* the one name `VC_MICROFRONTENDS_CONFIG_FILE_NAME` gives, unless it is a
|
|
159
|
+
* child's `{ "partOf": … }` (Turbo's `update_turbo_json` appends them to
|
|
160
|
+
* the root's global deps). Unread, an edit to `web`'s routes replayed every
|
|
161
|
+
* sibling app's build (with-microfrontends, Turbo 2.11's dry run moves all
|
|
162
|
+
* 18 tasks). Paths are root-relative; `text` is what the mapping read.
|
|
163
|
+
*/
|
|
164
|
+
export function microfrontendsConfigs(
|
|
165
|
+
root: string,
|
|
166
|
+
dirs: readonly string[],
|
|
167
|
+
custom = process.env['VC_MICROFRONTENDS_CONFIG_FILE_NAME'] ?? '',
|
|
168
|
+
): { rel: string; text: string }[] {
|
|
169
|
+
const names = custom === '' ? ['microfrontends.json', 'microfrontends.jsonc'] : [custom]
|
|
170
|
+
const out: { rel: string; text: string }[] = []
|
|
171
|
+
for (const dir of new Set(dirs.map((d) => path.resolve(d)))) {
|
|
172
|
+
const file = names.map((n) => path.join(dir, n)).find((f) => existsSync(f))
|
|
173
|
+
if (file === undefined) continue
|
|
174
|
+
const text = readFileSync(file, 'utf8')
|
|
175
|
+
let child = false
|
|
176
|
+
try {
|
|
177
|
+
const parsed = Bun.JSONC.parse(text) as { partOf?: unknown } | null
|
|
178
|
+
child = typeof parsed?.partOf === 'string'
|
|
179
|
+
} catch {}
|
|
180
|
+
if (!child) out.push({ rel: relPosix(root, file), text })
|
|
181
|
+
}
|
|
182
|
+
return out.sort((a, b) => (a.rel < b.rel ? -1 : 1))
|
|
183
|
+
}
|