@vzn/vx-migrate 0.0.636 → 0.0.638
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 +298 -44
- package/package.json +3 -3
- package/src/adopt.ts +25 -8
- package/src/adoption-plugin.ts +2 -2
- package/src/manifest-graph.ts +65 -0
- package/src/manifests.ts +52 -0
- package/src/mapping-cache.ts +5 -9
- package/src/migrate-nx.ts +63 -22
- package/src/migrate-scripts.ts +1261 -0
- package/src/migrate-turbo.ts +6 -7
- package/src/migrate-vite-task.ts +5 -4
- package/src/migrate.ts +474 -173
- package/src/nx/export-graph.ts +15 -4
- package/src/nx/index.ts +82 -23
- package/src/nx/nx-boundaries.ts +191 -0
- package/src/nx/nx-deps.ts +3 -3
- package/src/nx/nx-map.ts +37 -41
- package/src/nx/nx-upstream.ts +3 -2
- package/src/nx-cache/index.ts +5 -11
- package/src/package-fs.ts +152 -0
- package/src/packages.ts +806 -0
- package/src/paths.ts +2 -2
- package/src/plugin-gaps.ts +20 -5
- package/src/remote-cache.ts +118 -0
- package/src/shared-outputs.ts +63 -19
- package/src/tracked-outputs.ts +54 -35
- package/src/turbo/index.ts +32 -20
- package/src/turbo/turbo-map.ts +96 -56
- package/src/turbo-cache/index.ts +14 -20
- package/src/vite-task/vite-task-map.ts +13 -12
- package/src/workspace-notes.ts +12 -6
- package/src/workspace-plugins.ts +41 -11
package/src/nx/export-graph.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The resolved Nx graph, exported by the workspace's own nx: `nx()` keys and
|
|
2
2
|
// keeps it, `vx-migrate --from nx` maps it once.
|
|
3
3
|
|
|
4
|
-
import { readFileSync, realpathSync } from 'node:fs'
|
|
4
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
5
5
|
import { mkdir } from 'node:fs/promises'
|
|
6
6
|
import path from 'node:path'
|
|
7
7
|
|
|
@@ -11,10 +11,9 @@ import path from 'node:path'
|
|
|
11
11
|
* daemon up the export is served from memory, without it Nx computes.
|
|
12
12
|
*/
|
|
13
13
|
export async function exportGraph(root: string, snapshot: string): Promise<string | null> {
|
|
14
|
-
const
|
|
15
|
-
const bin = (await Bun.file(linked).exists()) ? linked : lernasNx(root)
|
|
14
|
+
const bin = nxBin(root)
|
|
16
15
|
if (bin === undefined) {
|
|
17
|
-
return `no
|
|
16
|
+
return `no node_modules/.bin/nx — install nx, or export a graph with \`nx graph --file=<path>\` and pass it as graph: '<path>'`
|
|
18
17
|
}
|
|
19
18
|
// `discover` runs before the run opens (and makes) the cache dir.
|
|
20
19
|
await mkdir(path.dirname(snapshot), { recursive: true })
|
|
@@ -30,6 +29,18 @@ export async function exportGraph(root: string, snapshot: string): Promise<strin
|
|
|
30
29
|
return `nx graph --file exited ${code}${tail ? `: ${tail}` : ''}`
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The workspace's own nx: the linked bin, else where Nx's wrapper installs
|
|
34
|
+
* it in a repo with no package.json (`.nx/installation`), else Lerna's.
|
|
35
|
+
*/
|
|
36
|
+
export function nxBin(root: string): string | undefined {
|
|
37
|
+
for (const dir of [root, path.join(root, '.nx', 'installation')]) {
|
|
38
|
+
const bin = path.join(dir, 'node_modules', '.bin', 'nx')
|
|
39
|
+
if (existsSync(bin)) return bin
|
|
40
|
+
}
|
|
41
|
+
return lernasNx(root)
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
/**
|
|
34
45
|
* The nx Lerna 6+ runs on, from Lerna's own dependencies: pnpm links only
|
|
35
46
|
* a root's direct dependencies into `node_modules/.bin`, so a Lerna repo
|
package/src/nx/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { stat } from 'node:fs/promises'
|
|
|
23
23
|
import { availableParallelism } from 'node:os'
|
|
24
24
|
import path from 'node:path'
|
|
25
25
|
import {
|
|
26
|
+
type DiscoveredProject,
|
|
26
27
|
refuseUnknownOptions,
|
|
27
28
|
type PluginOptionKinds,
|
|
28
29
|
type GeneratedProject,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
UserError,
|
|
31
32
|
type VxPlugin,
|
|
32
33
|
} from '@vzn/vx'
|
|
34
|
+
import type { PackageMeta } from '../packages.js'
|
|
33
35
|
import { type AdoptionRun, adoptionPlugin } from '../adoption-plugin.js'
|
|
34
36
|
import { collectGaps } from '../plugin-gaps.js'
|
|
35
37
|
import {
|
|
@@ -43,6 +45,7 @@ import {
|
|
|
43
45
|
} from './nx-map.js'
|
|
44
46
|
import { exportGraph } from './export-graph.js'
|
|
45
47
|
import { lernaJsonText } from './lerna.js'
|
|
48
|
+
import { nxDependencyRules } from './nx-boundaries.js'
|
|
46
49
|
export type {
|
|
47
50
|
NxExecutors,
|
|
48
51
|
NxExecutorTarget,
|
|
@@ -52,6 +55,7 @@ export type {
|
|
|
52
55
|
import { listDotenv } from './nx-dotenv.js'
|
|
53
56
|
import { trackedKinds } from '../tracked-outputs.js'
|
|
54
57
|
import { relPosix } from '../paths.js'
|
|
58
|
+
import { memberProjects, withManifests } from '../manifests.js'
|
|
55
59
|
import type { AdoptionMapping } from '../mapping-cache.js'
|
|
56
60
|
import { yarnrcText } from '../script-command.js'
|
|
57
61
|
|
|
@@ -119,10 +123,12 @@ export function nx(options: NxPluginOptions = {}): VxPlugin {
|
|
|
119
123
|
cacheDir: string,
|
|
120
124
|
projects: readonly ProjectMeta[],
|
|
121
125
|
changes?: WorktreeChanges,
|
|
126
|
+
members: readonly DiscoveredProject[] = [],
|
|
122
127
|
) => {
|
|
123
128
|
if (graphFor !== projects) {
|
|
124
129
|
graphFor = projects
|
|
125
|
-
|
|
130
|
+
const also = members.map((m) => ({ name: m.name ?? '', dir: m.dir, configPath: null }))
|
|
131
|
+
graph = loadGraph(root, cacheDir, [...projects, ...also], options.graph, changes)
|
|
126
132
|
}
|
|
127
133
|
return graph!
|
|
128
134
|
}
|
|
@@ -142,33 +148,45 @@ export function nx(options: NxPluginOptions = {}): VxPlugin {
|
|
|
142
148
|
{
|
|
143
149
|
async config(workspace, ctx) {
|
|
144
150
|
const root = options.root ?? ctx.workspaceRoot
|
|
145
|
-
if (workspace.affectedBase === undefined) {
|
|
151
|
+
if (workspace.run?.affectedBase === undefined) {
|
|
146
152
|
const base = await nxBase(root)
|
|
147
|
-
if (base !== undefined) workspace.
|
|
153
|
+
if (base !== undefined) workspace.run = { ...workspace.run, affectedBase: base }
|
|
148
154
|
}
|
|
149
|
-
if (workspace.concurrency === undefined) {
|
|
155
|
+
if (workspace.run?.concurrency === undefined) {
|
|
150
156
|
const parallel = await nxParallel(root)
|
|
151
|
-
if (parallel !== undefined) workspace.
|
|
157
|
+
if (parallel !== undefined) workspace.run = { ...workspace.run, concurrency: parallel }
|
|
152
158
|
}
|
|
153
|
-
if (workspace.
|
|
159
|
+
if (workspace.run?.rules?.dependencies === undefined) {
|
|
160
|
+
const dependencies = await nxDependencyRules(root)
|
|
161
|
+
if (dependencies !== undefined && dependencies.length > 0)
|
|
162
|
+
workspace.run = {
|
|
163
|
+
...workspace.run,
|
|
164
|
+
rules: { ...workspace.run?.rules, dependencies },
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (workspace.cache?.retention === undefined) {
|
|
154
168
|
const maxSize = await nxMaxCacheSize(root)
|
|
155
|
-
if (maxSize !== undefined)
|
|
169
|
+
if (maxSize !== undefined)
|
|
170
|
+
workspace.cache = { ...workspace.cache, retention: { maxSize } }
|
|
156
171
|
}
|
|
157
172
|
},
|
|
158
|
-
//
|
|
159
|
-
//
|
|
173
|
+
// The package manager's members are projects, each with no vx.config
|
|
174
|
+
// written. An integrated Nx repo keeps projects out of that list
|
|
175
|
+
// (analogjs: `project.json` libraries no glob names), and their
|
|
160
176
|
// targets had nowhere to attach: 1 of 21 `build` tasks ran. Each
|
|
161
|
-
// graph node with targets at a directory
|
|
162
|
-
//
|
|
177
|
+
// graph node with targets at a directory neither found is named a
|
|
178
|
+
// project, by its package.json name or else its Nx name; a name a
|
|
163
179
|
// project already holds stays unattached, and the mapping says so.
|
|
164
180
|
async discover(ctx) {
|
|
165
181
|
const root = options.root ?? ctx.workspaceRoot
|
|
182
|
+
const members = await memberProjects(ctx.workspaceRoot)
|
|
166
183
|
// Core's status is the workspace's: a root elsewhere asks git itself.
|
|
167
184
|
const changes = options.root === undefined ? () => ctx.worktreeChanges() : undefined
|
|
168
|
-
const { graph } = await graphOf(root, ctx.cacheDir, ctx.projects, changes)
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
185
|
+
const { graph } = await graphOf(root, ctx.cacheDir, ctx.projects, changes, members)
|
|
186
|
+
const found = [...ctx.projects, ...members]
|
|
187
|
+
const dirs = new Set(found.map((m) => path.resolve(m.dir)))
|
|
188
|
+
const names = new Set(found.map((m) => m.name))
|
|
189
|
+
const named = members.map((m) => ({ dir: path.resolve(m.dir), name: m.name! }))
|
|
172
190
|
for (const [nodeName, node] of Object.entries(graph.nodes)) {
|
|
173
191
|
if (node?.data?.targets === undefined) continue
|
|
174
192
|
const dir = path.resolve(root, node.data.root ?? '')
|
|
@@ -190,12 +208,45 @@ export function nx(options: NxPluginOptions = {}): VxPlugin {
|
|
|
190
208
|
dirs.add(dir)
|
|
191
209
|
named.push({ dir, name })
|
|
192
210
|
}
|
|
193
|
-
|
|
211
|
+
const entries = withGraphEdges(root, graph, dirs, named)
|
|
212
|
+
const covered = new Set(entries.map((e) => e.dir))
|
|
213
|
+
return [...entries, ...named.filter((n) => !covered.has(n.dir))]
|
|
194
214
|
},
|
|
195
215
|
},
|
|
196
216
|
)
|
|
197
217
|
}
|
|
198
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Every project the graph and the workspace share, with its Nx graph edges
|
|
221
|
+
* (package deps, `implicitDependencies`, imports) as `deps`: nx() is an Nx
|
|
222
|
+
* repo's edge source, so `^task` follows Nx's graph with no lockfile plugin.
|
|
223
|
+
*/
|
|
224
|
+
function withGraphEdges(
|
|
225
|
+
root: string,
|
|
226
|
+
graph: NxGraph,
|
|
227
|
+
dirs: ReadonlySet<string>,
|
|
228
|
+
named: ReadonlyArray<{ dir: string; name: string }>,
|
|
229
|
+
): DiscoveredProject[] {
|
|
230
|
+
const dirOf = new Map<string, string>()
|
|
231
|
+
for (const [nodeName, node] of Object.entries(graph.nodes)) {
|
|
232
|
+
const dir = path.resolve(root, node?.data?.root ?? '')
|
|
233
|
+
if (dirs.has(dir)) dirOf.set(nodeName, dir)
|
|
234
|
+
}
|
|
235
|
+
const nameOf = new Map(named.map((n) => [n.dir, n.name]))
|
|
236
|
+
const edges = (graph.dependencies ?? {}) as Record<string, ReadonlyArray<{ target?: unknown }>>
|
|
237
|
+
const out: DiscoveredProject[] = []
|
|
238
|
+
for (const [nodeName, dir] of dirOf) {
|
|
239
|
+
const deps = new Set<string>()
|
|
240
|
+
for (const e of edges[nodeName] ?? []) {
|
|
241
|
+
const to = typeof e?.target === 'string' ? dirOf.get(e.target) : undefined
|
|
242
|
+
if (to !== undefined && to !== dir) deps.add(to)
|
|
243
|
+
}
|
|
244
|
+
const name = nameOf.get(dir)
|
|
245
|
+
out.push({ dir, ...(name === undefined ? {} : { name }), deps: [...deps].sort() })
|
|
246
|
+
}
|
|
247
|
+
return out
|
|
248
|
+
}
|
|
249
|
+
|
|
199
250
|
function isWithin(root: string, dir: string): boolean {
|
|
200
251
|
const rel = path.relative(root, dir)
|
|
201
252
|
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))
|
|
@@ -268,10 +319,11 @@ interface LoadedGraph {
|
|
|
268
319
|
|
|
269
320
|
async function mapAll(
|
|
270
321
|
root: string,
|
|
271
|
-
|
|
322
|
+
projects: readonly ProjectMeta[],
|
|
272
323
|
loaded: LoadedGraph,
|
|
273
324
|
executors: NxExecutors | undefined,
|
|
274
325
|
): Promise<AdoptionRun> {
|
|
326
|
+
const metas = await withManifests(projects)
|
|
275
327
|
const { text, graph, notes } = loaded
|
|
276
328
|
const reads = await nxReads(root, metas, text, graph, notes)
|
|
277
329
|
// The mapping is cached on what it read; a translator is code it ran.
|
|
@@ -304,7 +356,7 @@ const textOf = (file: string): Promise<string> =>
|
|
|
304
356
|
*/
|
|
305
357
|
async function nxReads(
|
|
306
358
|
root: string,
|
|
307
|
-
metas: readonly
|
|
359
|
+
metas: readonly PackageMeta[],
|
|
308
360
|
graphText: string,
|
|
309
361
|
graph: NxGraph,
|
|
310
362
|
notes: readonly string[],
|
|
@@ -346,13 +398,14 @@ async function nxReads(
|
|
|
346
398
|
|
|
347
399
|
async function index(
|
|
348
400
|
root: string,
|
|
349
|
-
metas: readonly
|
|
401
|
+
metas: readonly PackageMeta[],
|
|
350
402
|
graph: NxGraph,
|
|
351
403
|
loadNotes: readonly string[],
|
|
352
404
|
trackedFiles: () => Promise<readonly string[] | null>,
|
|
353
405
|
executors: NxExecutors | undefined,
|
|
354
406
|
): Promise<AdoptionMapping> {
|
|
355
|
-
const notes = [
|
|
407
|
+
const notes: string[] = []
|
|
408
|
+
const live = [...loadNotes]
|
|
356
409
|
const tracked = await trackedFiles()
|
|
357
410
|
const configs = new Map(
|
|
358
411
|
metas.flatMap((m) =>
|
|
@@ -406,7 +459,7 @@ async function index(
|
|
|
406
459
|
if (!uses(bin)) continue
|
|
407
460
|
loadsNx = true
|
|
408
461
|
if (!(await Bun.file(path.join(root, 'node_modules', '.bin', bin)).exists())) {
|
|
409
|
-
|
|
462
|
+
live.push(
|
|
410
463
|
`${what} through \`${bin}\`, which is not in node_modules/.bin — add ` +
|
|
411
464
|
"@vzn/vx-migrate to the workspace's devDependencies so its bin is on every task's PATH",
|
|
412
465
|
)
|
|
@@ -418,7 +471,7 @@ async function index(
|
|
|
418
471
|
loadsNx &&
|
|
419
472
|
!(await Bun.file(path.join(root, 'node_modules', 'nx', 'package.json')).exists())
|
|
420
473
|
) {
|
|
421
|
-
|
|
474
|
+
live.push(
|
|
422
475
|
'nx-exec and nx-env load Nx from the workspace, and node_modules/nx is not there — ' +
|
|
423
476
|
'install nx, or those tasks fail',
|
|
424
477
|
)
|
|
@@ -428,6 +481,7 @@ async function index(
|
|
|
428
481
|
gaps: collectGaps(
|
|
429
482
|
mapped.projects.filter((p) => byName.has(p.name)),
|
|
430
483
|
notes,
|
|
484
|
+
live,
|
|
431
485
|
),
|
|
432
486
|
}
|
|
433
487
|
}
|
|
@@ -535,6 +589,9 @@ async function graphInputKey(
|
|
|
535
589
|
// and stays in.
|
|
536
590
|
const nxCache = (p: string): boolean =>
|
|
537
591
|
p.startsWith('.nx/cache/') || p.startsWith('.nx/workspace-data/')
|
|
592
|
+
// vx's own directory (the lock `vx lock` rewrites, the workspace file):
|
|
593
|
+
// Nx reads nothing there, and under a root project each lock re-exported.
|
|
594
|
+
const vxDir = (p: string): boolean => p.startsWith('.vx/')
|
|
538
595
|
// What can move the graph: a file under a project root (its sources, the
|
|
539
596
|
// configs a plugin infers targets from), or a root file Nx reads. A task's
|
|
540
597
|
// stray write at the root (a report, a log) is not, and counting it
|
|
@@ -546,7 +603,9 @@ async function graphInputKey(
|
|
|
546
603
|
p.endsWith('/project.json') ||
|
|
547
604
|
roots.some((r) => r === '' || p.startsWith(`${r}/`)) ||
|
|
548
605
|
(!p.includes('/') && ROOT_GRAPH_FILE.test(p))
|
|
549
|
-
const listed = [
|
|
606
|
+
const listed = [
|
|
607
|
+
...new Set(changed.filter((p) => !inCache(p) && !nxCache(p) && !vxDir(p) && graphFile(p))),
|
|
608
|
+
]
|
|
550
609
|
// A manifest needs no read of its own: git lists it when it is edited, and
|
|
551
610
|
// HEAD moves when an edit is committed (reading all 2,000 cost 50 ms at
|
|
552
611
|
// 1,000 projects). nx.json's chain does: a base may sit in node_modules,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
/** `run.rules.dependencies`' entry, as core takes it. */
|
|
4
|
+
interface DependencyRule {
|
|
5
|
+
from: string
|
|
6
|
+
allow?: string[]
|
|
7
|
+
deny?: string[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const RULE_NAMES = ['@nx/enforce-module-boundaries', '@nrwl/nx/enforce-module-boundaries']
|
|
11
|
+
const FLAT = ['eslint.config.mjs', 'eslint.config.js', 'eslint.config.cjs', 'eslint.config.ts']
|
|
12
|
+
const LEGACY = ['.eslintrc.json', '.eslintrc']
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The root ESLint config's `@nx/enforce-module-boundaries` `depConstraints`,
|
|
16
|
+
* as vx's lock-time dependency rules. A flat config is read, never run:
|
|
17
|
+
* importing it loads ESLint's plugins on every vx run, for a rule only
|
|
18
|
+
* `vx lock` checks. So the constraints must be a literal array there; a
|
|
19
|
+
* spread or a variable maps nothing. Undefined when no rule is found.
|
|
20
|
+
*/
|
|
21
|
+
export async function nxDependencyRules(root: string): Promise<DependencyRule[] | undefined> {
|
|
22
|
+
for (const name of [...FLAT, ...LEGACY]) {
|
|
23
|
+
const file = Bun.file(path.join(root, name))
|
|
24
|
+
if (!(await file.exists())) continue
|
|
25
|
+
const text = await file.text()
|
|
26
|
+
const constraints = LEGACY.includes(name) ? fromJson(text) : fromSource(text)
|
|
27
|
+
return constraints === undefined ? undefined : mapConstraints(constraints)
|
|
28
|
+
}
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `rules` and each `overrides[].rules` of a JSON config, first rule found. */
|
|
33
|
+
function fromJson(text: string): unknown[] | undefined {
|
|
34
|
+
let json: unknown
|
|
35
|
+
try {
|
|
36
|
+
json = Bun.JSONC.parse(text)
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
const configs = [json, ...((json as { overrides?: unknown[] })?.overrides ?? [])]
|
|
41
|
+
for (const c of configs) {
|
|
42
|
+
const rules = (c as { rules?: Record<string, unknown> } | null)?.rules
|
|
43
|
+
for (const r of RULE_NAMES) {
|
|
44
|
+
const entry = rules?.[r]
|
|
45
|
+
const options = Array.isArray(entry) ? entry[1] : undefined
|
|
46
|
+
const dc = (options as { depConstraints?: unknown } | undefined)?.depConstraints
|
|
47
|
+
if (Array.isArray(dc)) return dc
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The literal after the rule's `depConstraints:` in JS source. */
|
|
54
|
+
function fromSource(text: string): unknown[] | undefined {
|
|
55
|
+
for (const r of RULE_NAMES) {
|
|
56
|
+
const at = text.indexOf(r)
|
|
57
|
+
if (at === -1) continue
|
|
58
|
+
const m = /depConstraints['"]?\s*:\s*\[/.exec(text.slice(at))
|
|
59
|
+
if (m === null) return undefined
|
|
60
|
+
try {
|
|
61
|
+
const v = new Literal(text, at + m.index + m[0].length - 1).value()
|
|
62
|
+
return Array.isArray(v) ? v : undefined
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Nx's constraint as a vx rule: `sourceTag` is `from`,
|
|
72
|
+
* `onlyDependOnLibsWithTags` is `allow`, `notDependOnLibsWithTags` is
|
|
73
|
+
* `deny`; `*` means any project. A constraint vx cannot say is left out:
|
|
74
|
+
* `allSourceTags` (every tag at once), a `/regex/` tag, or one that bans
|
|
75
|
+
* only external imports.
|
|
76
|
+
*/
|
|
77
|
+
function mapConstraints(constraints: unknown[]): DependencyRule[] {
|
|
78
|
+
const out: DependencyRule[] = []
|
|
79
|
+
const sel = (t: unknown) => (t === '*' ? '*' : `tag:${String(t)}`)
|
|
80
|
+
const tags = (v: unknown): string[] | undefined =>
|
|
81
|
+
Array.isArray(v) && v.every((t) => typeof t === 'string' && t !== '' && !t.startsWith('/'))
|
|
82
|
+
? v.map(sel)
|
|
83
|
+
: undefined
|
|
84
|
+
for (const c of constraints) {
|
|
85
|
+
const { sourceTag, onlyDependOnLibsWithTags, notDependOnLibsWithTags } = (c ?? {}) as Record<
|
|
86
|
+
string,
|
|
87
|
+
unknown
|
|
88
|
+
>
|
|
89
|
+
if (typeof sourceTag !== 'string' || sourceTag === '' || sourceTag.startsWith('/')) continue
|
|
90
|
+
const allow = tags(onlyDependOnLibsWithTags)
|
|
91
|
+
const deny = tags(notDependOnLibsWithTags)
|
|
92
|
+
if (allow === undefined && deny === undefined) continue
|
|
93
|
+
out.push({
|
|
94
|
+
from: sel(sourceTag),
|
|
95
|
+
...(allow !== undefined ? { allow } : {}),
|
|
96
|
+
...(deny !== undefined ? { deny } : {}),
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A JS literal: arrays, objects, strings, numbers, booleans, null. Anything else throws. */
|
|
103
|
+
class Literal {
|
|
104
|
+
constructor(
|
|
105
|
+
private readonly s: string,
|
|
106
|
+
private i: number,
|
|
107
|
+
) {}
|
|
108
|
+
|
|
109
|
+
value(): unknown {
|
|
110
|
+
this.skip()
|
|
111
|
+
const c = this.s[this.i]
|
|
112
|
+
if (c === '[') {
|
|
113
|
+
this.i++
|
|
114
|
+
const out: unknown[] = []
|
|
115
|
+
while (!this.eat(']')) {
|
|
116
|
+
out.push(this.value())
|
|
117
|
+
if (!this.eat(',')) {
|
|
118
|
+
this.expect(']')
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out
|
|
123
|
+
}
|
|
124
|
+
if (c === '{') {
|
|
125
|
+
this.i++
|
|
126
|
+
const out: Record<string, unknown> = {}
|
|
127
|
+
while (!this.eat('}')) {
|
|
128
|
+
const key = this.key()
|
|
129
|
+
this.expect(':')
|
|
130
|
+
out[key] = this.value()
|
|
131
|
+
if (!this.eat(',')) {
|
|
132
|
+
this.expect('}')
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out
|
|
137
|
+
}
|
|
138
|
+
if (c === "'" || c === '"' || c === '`') return this.string(c)
|
|
139
|
+
const m = this.match(/true|false|null|-?\d+(?:\.\d+)?/y)
|
|
140
|
+
if (m === null) throw new Error('not a literal')
|
|
141
|
+
return JSON.parse(m)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private key(): string {
|
|
145
|
+
this.skip()
|
|
146
|
+
const c = this.s[this.i]
|
|
147
|
+
if (c === "'" || c === '"') return this.string(c)
|
|
148
|
+
const m = this.match(/[A-Za-z_$][\w$]*/y)
|
|
149
|
+
if (m === null) throw new Error('not a key')
|
|
150
|
+
return m
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private string(q: string): string {
|
|
154
|
+
let out = ''
|
|
155
|
+
for (this.i++; this.i < this.s.length; this.i++) {
|
|
156
|
+
const c = this.s[this.i]!
|
|
157
|
+
if (c === q) {
|
|
158
|
+
this.i++
|
|
159
|
+
return out
|
|
160
|
+
}
|
|
161
|
+
if (c === '\\') out += this.s[++this.i]
|
|
162
|
+
else if (q === '`' && c === '$' && this.s[this.i + 1] === '{') throw new Error('template')
|
|
163
|
+
else out += c
|
|
164
|
+
}
|
|
165
|
+
throw new Error('unterminated')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private skip(): void {
|
|
169
|
+
while (this.match(/\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\//y) !== null);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The sticky `re`'s match at the cursor, consumed; null when none. */
|
|
173
|
+
private match(re: RegExp): string | null {
|
|
174
|
+
re.lastIndex = this.i
|
|
175
|
+
const m = re.exec(this.s)
|
|
176
|
+
if (m === null || m[0] === '') return null
|
|
177
|
+
this.i += m[0].length
|
|
178
|
+
return m[0]
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private eat(c: string): boolean {
|
|
182
|
+
this.skip()
|
|
183
|
+
if (this.s[this.i] !== c) return false
|
|
184
|
+
this.i++
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private expect(c: string): void {
|
|
189
|
+
if (!this.eat(c)) throw new Error(`expected ${c}`)
|
|
190
|
+
}
|
|
191
|
+
}
|
package/src/nx/nx-deps.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// vx-migrate itself wrote (walked the Nx path, 2026-09-20). Extracted from
|
|
7
7
|
// `buildTask` in item 606; the rules are unchanged.
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { PackageMeta } from '../packages.js'
|
|
10
10
|
|
|
11
11
|
/** The vx task an Nx `project:target:configuration` reaches, or null when the target lacks it. */
|
|
12
12
|
export type TaskNameFor = (project: string, target: string, configuration: string) => string | null
|
|
@@ -16,7 +16,7 @@ export type TaskNameFor = (project: string, target: string, configuration: strin
|
|
|
16
16
|
* target (`processTasksForSingleProject`), and says nothing otherwise:
|
|
17
17
|
* nx-examples' `targetDefaults` give every `typecheck` a `codegen` that
|
|
18
18
|
* one project declares. Passed through, core refused the whole run —
|
|
19
|
-
* "
|
|
19
|
+
* "dependsOn …#codegen but no such task is declared". Dropped here as
|
|
20
20
|
* Nx drops it, for this project and a named one alike.
|
|
21
21
|
*/
|
|
22
22
|
type HasTarget = (project: string, target: string) => boolean
|
|
@@ -142,7 +142,7 @@ function expandTargetGlobs(
|
|
|
142
142
|
|
|
143
143
|
export function mapNxDeps(
|
|
144
144
|
raw: readonly unknown[],
|
|
145
|
-
metaByNode: ReadonlyMap<string,
|
|
145
|
+
metaByNode: ReadonlyMap<string, PackageMeta>,
|
|
146
146
|
ownTarget: (name: string) => boolean,
|
|
147
147
|
taskNameFor: TaskNameFor,
|
|
148
148
|
hasTarget: HasTarget,
|