@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.
@@ -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
+ }