@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/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx-migrate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.638",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "Adopt @vzn/vx from Turborepo or
|
|
5
|
+
"description": "Adopt @vzn/vx from Turborepo, Nx or package.json scripts: run the repo unchanged, migrate it to vx.config.ts, and keep your Turbo or Nx remote cache",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"vx",
|
|
8
8
|
"vx-plugin",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@vzn/vx": "^0.0.
|
|
43
|
+
"@vzn/vx": "^0.0.638"
|
|
44
44
|
},
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
package/src/adopt.ts
CHANGED
|
@@ -152,7 +152,7 @@ export function listedPackages(root: string, packages: readonly string[]): strin
|
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/** Install `packages` (at `version`, given one) with the repo's manager
|
|
155
|
+
/** Install `packages` (at `version`, given one) with the repo's manager; its output only on failure. */
|
|
156
156
|
export function install(
|
|
157
157
|
root: string,
|
|
158
158
|
packages: readonly string[],
|
|
@@ -164,7 +164,7 @@ export function install(
|
|
|
164
164
|
installArgv(root, pm, version === null ? packages : packages.map((p) => `${p}@${version}`)),
|
|
165
165
|
'installing vx',
|
|
166
166
|
// pnpm 12 refuses a version younger than minimumReleaseAge, and vx
|
|
167
|
-
// releases daily; its words
|
|
167
|
+
// releases daily; its words print above, so the way out is named.
|
|
168
168
|
pm === 'pnpm'
|
|
169
169
|
? `; if pnpm refused a version published within its minimumReleaseAge, list ${packages.join(', ')} under minimumReleaseAgeExclude in pnpm-workspace.yaml`
|
|
170
170
|
: '',
|
|
@@ -206,11 +206,26 @@ async function runManager(
|
|
|
206
206
|
ranHint = '',
|
|
207
207
|
): Promise<string> {
|
|
208
208
|
const line = argv.join(' ')
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
// The report's `install` row says what ran; a terminal sees it while it
|
|
210
|
+
// runs, on a line the report then takes the place of.
|
|
211
|
+
const live = process.stdout.isTTY === true
|
|
212
|
+
if (live) process.stdout.write(`\n ${line.slice(0, (process.stdout.columns ?? 80) - 4)} …`)
|
|
213
|
+
// The manager's own output (progress, deprecations, audit, postinstalls)
|
|
214
|
+
// is shown only when it fails: mid-init it buried the report (Growth's
|
|
215
|
+
// first-run funnel F3, 2026-10-13).
|
|
216
|
+
let said = ''
|
|
217
|
+
const spawn = async (cmd: string[]) => {
|
|
218
|
+
const p = Bun.spawn(cmd, { cwd: root, stdin: 'inherit', stdout: 'pipe', stderr: 'pipe' })
|
|
219
|
+
const [out, err, code] = await Promise.all([
|
|
220
|
+
new Response(p.stdout).text(),
|
|
221
|
+
new Response(p.stderr).text(),
|
|
222
|
+
p.exited,
|
|
223
|
+
])
|
|
224
|
+
said = out + err
|
|
225
|
+
return code
|
|
226
|
+
}
|
|
212
227
|
let code: number
|
|
213
|
-
let
|
|
228
|
+
let failed = ''
|
|
214
229
|
try {
|
|
215
230
|
code = await spawn(argv)
|
|
216
231
|
} catch (e) {
|
|
@@ -220,12 +235,14 @@ async function runManager(
|
|
|
220
235
|
code = await spawn(launcher)
|
|
221
236
|
} catch (e2) {
|
|
222
237
|
code = -1
|
|
223
|
-
|
|
238
|
+
failed = `: ${(e2 as Error).message}`
|
|
224
239
|
}
|
|
225
240
|
}
|
|
241
|
+
if (live) process.stdout.write('\r\x1b[2K\x1b[1A')
|
|
226
242
|
if (code !== 0) {
|
|
243
|
+
process.stderr.write(said)
|
|
227
244
|
throw new UserError(
|
|
228
|
-
`${what} failed (${line} exited ${code}${
|
|
245
|
+
`${what} failed (${line} exited ${code}${failed}); run it, then vx-migrate again${code > 0 ? ranHint : ''}`,
|
|
229
246
|
)
|
|
230
247
|
}
|
|
231
248
|
return line
|
package/src/adoption-plugin.ts
CHANGED
|
@@ -29,7 +29,7 @@ async function spare(
|
|
|
29
29
|
if (ids === undefined) todos.set(todo, (ids = []))
|
|
30
30
|
ids.push(id)
|
|
31
31
|
}
|
|
32
|
-
return { byName: mapping.byName, gaps: {
|
|
32
|
+
return { byName: mapping.byName, gaps: { ...mapping.gaps, todos } }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -97,7 +97,7 @@ export function adoptionPlugin(
|
|
|
97
97
|
const mapped = await mapping!
|
|
98
98
|
if (!warned) {
|
|
99
99
|
warned = true
|
|
100
|
-
warnGaps(ctx.warn, plugin.name, mapped.gaps)
|
|
100
|
+
warnGaps(ctx.warn, plugin.name, mapped.gaps, ctx.lock)
|
|
101
101
|
}
|
|
102
102
|
const project = mapped.byName.get(ctx.name)
|
|
103
103
|
if (project === undefined) return
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The workspace edges the mappers reason about. Core reads no manifest
|
|
2
|
+
// edge: a run's `^task` follows a plugin's (`@vzn/vx-lockfile`'s, from the
|
|
3
|
+
// lockfile the manager wrote from these manifests). Turbo and Nx draw
|
|
4
|
+
// package edges from the same manifests, so the mappers read them here: a
|
|
5
|
+
// dependency, dev or optional entry whose key names a workspace package
|
|
6
|
+
// orders it; a peer on one only reaches it.
|
|
7
|
+
|
|
8
|
+
import type { PackageMeta } from './packages.js'
|
|
9
|
+
|
|
10
|
+
export interface ManifestGraph {
|
|
11
|
+
/** Packages this one builds after, sorted. */
|
|
12
|
+
directDeps(name: string): readonly string[]
|
|
13
|
+
/** Every package a change reaches this one through, sorted. */
|
|
14
|
+
transitiveDeps(name: string): readonly string[]
|
|
15
|
+
/** Every package a change here reaches, sorted. */
|
|
16
|
+
transitiveDependents(name: string): readonly string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ORDER_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies'] as const
|
|
20
|
+
|
|
21
|
+
export function manifestGraph(metas: readonly PackageMeta[]): ManifestGraph {
|
|
22
|
+
const names = new Set(metas.map((m) => m.name))
|
|
23
|
+
const order = new Map<string, string[]>()
|
|
24
|
+
const reach = new Map<string, string[]>()
|
|
25
|
+
const dependents = new Map<string, string[]>()
|
|
26
|
+
const keys = (field: unknown): string[] =>
|
|
27
|
+
field !== null && typeof field === 'object' ? Object.keys(field) : []
|
|
28
|
+
for (const m of metas) {
|
|
29
|
+
const pkg = m.packageJson as unknown as Record<string, unknown>
|
|
30
|
+
const own = new Set<string>()
|
|
31
|
+
for (const f of ORDER_FIELDS) for (const k of keys(pkg[f])) if (names.has(k)) own.add(k)
|
|
32
|
+
own.delete(m.name)
|
|
33
|
+
const all = new Set(own)
|
|
34
|
+
for (const k of keys(pkg['peerDependencies'])) if (names.has(k) && k !== m.name) all.add(k)
|
|
35
|
+
order.set(m.name, [...own].sort())
|
|
36
|
+
reach.set(m.name, [...all].sort())
|
|
37
|
+
for (const d of all) {
|
|
38
|
+
const list = dependents.get(d)
|
|
39
|
+
if (list === undefined) dependents.set(d, [m.name])
|
|
40
|
+
else list.push(m.name)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const closure = (edges: ReadonlyMap<string, readonly string[]>) => {
|
|
44
|
+
const memo = new Map<string, readonly string[]>()
|
|
45
|
+
return (name: string): readonly string[] => {
|
|
46
|
+
let out = memo.get(name)
|
|
47
|
+
if (out !== undefined) return out
|
|
48
|
+
const seen = new Set<string>()
|
|
49
|
+
const stack = [...(edges.get(name) ?? [])]
|
|
50
|
+
while (stack.length > 0) {
|
|
51
|
+
const n = stack.pop()!
|
|
52
|
+
if (seen.has(n)) continue
|
|
53
|
+
seen.add(n)
|
|
54
|
+
stack.push(...(edges.get(n) ?? []))
|
|
55
|
+
}
|
|
56
|
+
memo.set(name, (out = [...seen].sort()))
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
directDeps: (name) => order.get(name) ?? [],
|
|
62
|
+
transitiveDeps: closure(reach),
|
|
63
|
+
transitiveDependents: closure(dependents),
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/manifests.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Core reads no `package.json`: a project is a directory with a vx.config,
|
|
2
|
+
// named by its config, a `discover` plugin or its path. A Turbo or Nx repo
|
|
3
|
+
// writes no vx.config, so its projects are the package manager's members,
|
|
4
|
+
// which `discover` names here (`memberProjects`). The mappers read scripts,
|
|
5
|
+
// names and dependencies, so each project's manifest is read here once
|
|
6
|
+
// per run: the members' as `discover` lists them, the rest once per
|
|
7
|
+
// projects array (core hands every `project` call of a run the same one).
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { type DiscoveredProject, type ProjectMeta } from '@vzn/vx'
|
|
10
|
+
import { listPackages, type PackageMeta } from './packages.js'
|
|
11
|
+
|
|
12
|
+
/** The manifests the last `discover` read, by directory. */
|
|
13
|
+
let listed = new Map<string, PackageMeta>()
|
|
14
|
+
const read = new WeakMap<readonly ProjectMeta[], Promise<PackageMeta[]>>()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The package manager's members at `root` as `discover` entries, each
|
|
18
|
+
* named by its `package.json`; none where the root holds no
|
|
19
|
+
* `package.json` or `pnpm-workspace.yaml`.
|
|
20
|
+
*/
|
|
21
|
+
export async function memberProjects(root: string): Promise<DiscoveredProject[]> {
|
|
22
|
+
const exists = await Promise.all(
|
|
23
|
+
['package.json', 'pnpm-workspace.yaml'].map((f) => Bun.file(path.join(root, f)).exists()),
|
|
24
|
+
)
|
|
25
|
+
const members = exists.some(Boolean) ? await listPackages(root) : []
|
|
26
|
+
listed = new Map(members.map((m) => [path.resolve(m.dir), m]))
|
|
27
|
+
return members.map((m) => ({ dir: m.dir, name: m.name }))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** `metas` with each one's `package.json` (`{ name }` where it has none or it does not parse). */
|
|
31
|
+
export function withManifests(metas: readonly ProjectMeta[]): Promise<PackageMeta[]> {
|
|
32
|
+
let out = read.get(metas)
|
|
33
|
+
if (out === undefined) {
|
|
34
|
+
out = Promise.all(metas.map(withManifest))
|
|
35
|
+
read.set(metas, out)
|
|
36
|
+
}
|
|
37
|
+
return out
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function withManifest(meta: ProjectMeta): Promise<PackageMeta> {
|
|
41
|
+
const known = listed.get(path.resolve(meta.dir))
|
|
42
|
+
const pkg: unknown =
|
|
43
|
+
known?.packageJson ??
|
|
44
|
+
(await Bun.file(path.join(meta.dir, 'package.json'))
|
|
45
|
+
.json()
|
|
46
|
+
.catch(() => null))
|
|
47
|
+
const packageJson =
|
|
48
|
+
pkg !== null && typeof pkg === 'object' && !Array.isArray(pkg)
|
|
49
|
+
? (pkg as PackageMeta['packageJson'])
|
|
50
|
+
: { name: meta.name }
|
|
51
|
+
return { name: meta.name, dir: meta.dir, configPath: meta.configPath, packageJson }
|
|
52
|
+
}
|
package/src/mapping-cache.ts
CHANGED
|
@@ -10,13 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { mkdir, rename } from 'node:fs/promises'
|
|
12
12
|
import path from 'node:path'
|
|
13
|
-
import {
|
|
14
|
-
buildPackageGraph,
|
|
15
|
-
foldScriptHooks,
|
|
16
|
-
isLiteralPattern,
|
|
17
|
-
outputsOverlap,
|
|
18
|
-
VERSION,
|
|
19
|
-
} from '@vzn/vx'
|
|
13
|
+
import { foldScriptHooks, isLiteralPattern, outputsOverlap, VERSION } from '@vzn/vx'
|
|
20
14
|
import type { Gaps } from './plugin-gaps.js'
|
|
21
15
|
|
|
22
16
|
/** What the stage uses of a mapping: tags and tasks per package name, and the gaps to report once. */
|
|
@@ -41,7 +35,7 @@ function codeId(): Promise<string> {
|
|
|
41
35
|
code ??= (async () => {
|
|
42
36
|
const parts: string[] = [
|
|
43
37
|
VERSION,
|
|
44
|
-
...[
|
|
38
|
+
...[foldScriptHooks, isLiteralPattern, outputsOverlap].map(String),
|
|
45
39
|
]
|
|
46
40
|
const files: string[] = []
|
|
47
41
|
for await (const f of new Bun.Glob('**/*.{ts,cjs}').scan(import.meta.dir)) files.push(f)
|
|
@@ -67,6 +61,7 @@ interface Held {
|
|
|
67
61
|
][]
|
|
68
62
|
readonly notes: string[]
|
|
69
63
|
readonly todos: [string, string[]][]
|
|
64
|
+
readonly live: string[]
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
/** `map()`, or the mapping it returned last under the same `parts`. */
|
|
@@ -84,7 +79,7 @@ export async function cachedMapping(
|
|
|
84
79
|
if (held?.key === key) {
|
|
85
80
|
return {
|
|
86
81
|
byName: new Map(held.byName),
|
|
87
|
-
gaps: { notes: held.notes, todos: new Map(held.todos) },
|
|
82
|
+
gaps: { notes: held.notes, todos: new Map(held.todos), live: held.live },
|
|
88
83
|
}
|
|
89
84
|
}
|
|
90
85
|
const mapping = await map()
|
|
@@ -99,6 +94,7 @@ export async function cachedMapping(
|
|
|
99
94
|
]),
|
|
100
95
|
notes: [...mapping.gaps.notes],
|
|
101
96
|
todos: [...mapping.gaps.todos].map(([todo, ids]) => [todo, [...ids]]),
|
|
97
|
+
live: [...mapping.gaps.live],
|
|
102
98
|
}
|
|
103
99
|
// Best-effort, and whole or absent: a reader never sees half a file, and
|
|
104
100
|
// a cache dir it cannot write costs the next run a mapping, not the run.
|
package/src/migrate-nx.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { stat } from 'node:fs/promises'
|
|
5
5
|
import path from 'node:path'
|
|
6
|
-
import { type
|
|
6
|
+
import { type GeneratedProject, type MigrationFormat, type MigrationPlan } from '@vzn/vx'
|
|
7
|
+
import type { PackageMeta } from './packages.js'
|
|
7
8
|
import { mapNxWorkspace, nxSizeText, parseNxGraph, readNxJson } from './nx/nx-map.js'
|
|
8
9
|
import { spareTrackedOutputs, trackedFiles, trackedKinds } from './tracked-outputs.js'
|
|
9
10
|
import { adoptedToolNotes } from './workspace-notes.js'
|
|
@@ -17,11 +18,13 @@ export const NX_GRAPH_REL = '.nx/workspace-data/project-graph.json'
|
|
|
17
18
|
/** `snapshot`: the graph file to read, absolute; the checked-in one by default. */
|
|
18
19
|
export async function migrateNx(
|
|
19
20
|
root: string,
|
|
20
|
-
metas: readonly
|
|
21
|
+
metas: readonly PackageMeta[],
|
|
21
22
|
format: MigrationFormat = 'ts',
|
|
22
23
|
snapshot = path.join(root, NX_GRAPH_REL),
|
|
23
|
-
/** This run writes
|
|
24
|
+
/** This run writes `.vx/workspace.ts`, with nx.json's run settings as fields. */
|
|
24
25
|
writesWorkspace = false,
|
|
26
|
+
/** No package.json anywhere: each config names its project and holds every edge. */
|
|
27
|
+
bare = false,
|
|
25
28
|
): Promise<MigrationPlan> {
|
|
26
29
|
const graph = parseNxGraph(await Bun.file(snapshot).text(), path.relative(root, snapshot))
|
|
27
30
|
const tracked = await trackedFiles(root)
|
|
@@ -33,9 +36,8 @@ export async function migrateNx(
|
|
|
33
36
|
manifestField: (key) => ({ raw: `pkg.${key}` }),
|
|
34
37
|
migration: true,
|
|
35
38
|
})
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// clean deletes them.
|
|
39
|
+
// A reader whose `!` hides a committed file under another task's output
|
|
40
|
+
// runs uncached, as under nx().
|
|
39
41
|
if (tracked !== null)
|
|
40
42
|
for (const [id, todo] of spareTrackedOutputs(root, mapped.projects, tracked)) {
|
|
41
43
|
const at = id.lastIndexOf('#')
|
|
@@ -48,14 +50,20 @@ export async function migrateNx(
|
|
|
48
50
|
runner: 'nx',
|
|
49
51
|
keys: (lock) => `Nx keys each project on the npm packages it depends on in ${lock}`,
|
|
50
52
|
})
|
|
51
|
-
const unlisted =
|
|
53
|
+
const unlisted = bare
|
|
54
|
+
? { manifests: [], notes: bareNotes(mapped.projects) }
|
|
55
|
+
: await unlistedProjects(root, metas, mapped.projects)
|
|
56
|
+
if (bare) for (const p of mapped.projects) explicitOnly(p)
|
|
52
57
|
return {
|
|
53
58
|
headerNotes: [
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
+
bare
|
|
60
|
+
? 'migrating from the resolved project-graph snapshot — plugin-inferred targets ' +
|
|
61
|
+
'are frozen as static config; `nx:run-commands` targets are their shell lines'
|
|
62
|
+
: 'migrating from the resolved project-graph snapshot — plugin-inferred targets ' +
|
|
63
|
+
'are frozen as static config; `nx:run-commands` targets are their shell lines, and ' +
|
|
64
|
+
'every other executor runs as itself through `nx-exec` (keep Nx and @vzn/vx-migrate ' +
|
|
65
|
+
'installed until those targets are rewritten as commands); targets with `.env` files ' +
|
|
66
|
+
'run through `nx-env`',
|
|
59
67
|
...adopted,
|
|
60
68
|
],
|
|
61
69
|
projects: mapped.projects.map((p) =>
|
|
@@ -74,6 +82,38 @@ export async function migrateNx(
|
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
85
|
+
/**
|
|
86
|
+
* A project with no manifest is named by its path unless its config names
|
|
87
|
+
* it, and a `^` edge follows the lockfile plugin's package edges, which a
|
|
88
|
+
* repo with no package.json has none of: the config keeps Nx's name, and
|
|
89
|
+
* the explicit edges the mapper drew from Nx's graph are every edge.
|
|
90
|
+
*/
|
|
91
|
+
function explicitOnly(p: GeneratedProject): void {
|
|
92
|
+
p.writeName = true
|
|
93
|
+
for (const t of p.tasks) {
|
|
94
|
+
const deps = t.task?.['dependsOn']
|
|
95
|
+
if (!Array.isArray(deps)) continue
|
|
96
|
+
const kept = deps.filter((d) => typeof d !== 'string' || !d.startsWith('^'))
|
|
97
|
+
// A group keeps its list, as `dropUnheldDeps` keeps one.
|
|
98
|
+
if (kept.length > 0 || t.task!['exec'] === undefined) t.task!['dependsOn'] = kept
|
|
99
|
+
else delete t.task!['dependsOn']
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** `nx-exec` and `nx-env` are this package's bins, and a repo with no package.json cannot install it. */
|
|
104
|
+
function bareNotes(projects: readonly GeneratedProject[]): string[] {
|
|
105
|
+
const ids = projects.flatMap((p) =>
|
|
106
|
+
p.tasks.flatMap((t) => {
|
|
107
|
+
const cmd = (t.task?.['exec'] as { command?: unknown } | undefined)?.command
|
|
108
|
+
return typeof cmd === 'string' && /^nx-(exec|env) /.test(cmd) ? [`${p.name}#${t.name}`] : []
|
|
109
|
+
}),
|
|
110
|
+
)
|
|
111
|
+
if (ids.length === 0) return []
|
|
112
|
+
return [
|
|
113
|
+
`${ids.join(', ')} run${ids.length === 1 ? 's' : ''} through nx-exec or nx-env, @vzn/vx-migrate's bins, which a repo with no package.json cannot install: rewrite ${ids.length === 1 ? 'it' : 'them'} as commands`,
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
|
|
77
117
|
/**
|
|
78
118
|
* Nx projects no workspace glob lists (an integrated repo's `project.json`
|
|
79
119
|
* libraries): core finds a project only by a listed package.json, so a
|
|
@@ -83,7 +123,7 @@ export async function migrateNx(
|
|
|
83
123
|
*/
|
|
84
124
|
async function unlistedProjects(
|
|
85
125
|
root: string,
|
|
86
|
-
metas: readonly
|
|
126
|
+
metas: readonly PackageMeta[],
|
|
87
127
|
projects: readonly { name: string; dir: string }[],
|
|
88
128
|
): Promise<{ manifests: { relPath: string; contents: string }[]; notes: string[] }> {
|
|
89
129
|
const listed = new Set(metas.map((m) => path.resolve(m.dir)))
|
|
@@ -122,9 +162,10 @@ async function unlistedProjects(
|
|
|
122
162
|
}
|
|
123
163
|
}
|
|
124
164
|
|
|
125
|
-
/** A
|
|
165
|
+
/** A `.vx/workspace.ts` field nx.json's run settings call for. */
|
|
126
166
|
export interface NxWorkspaceField {
|
|
127
|
-
|
|
167
|
+
/** The setting's path: its group, then its field. */
|
|
168
|
+
readonly field: 'run.concurrency' | 'run.affectedBase' | 'cache.retention'
|
|
128
169
|
/** The value as TypeScript source; undefined when vx cannot read it. */
|
|
129
170
|
readonly source: string | undefined
|
|
130
171
|
/** What to tell a user whose workspace file this run does not write. */
|
|
@@ -135,7 +176,7 @@ const quoted = (s: string) => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}
|
|
|
135
176
|
|
|
136
177
|
/**
|
|
137
178
|
* nx.json's run-wide settings `nx()` applies live: each a field of the
|
|
138
|
-
* written
|
|
179
|
+
* written `.vx/workspace.ts`, or a migrated repo that ran one task at a time
|
|
139
180
|
* for a shared database ran on every core. nx.json only: the files are
|
|
140
181
|
* written for every machine, so `NX_PARALLEL` and friends are not read.
|
|
141
182
|
*/
|
|
@@ -153,23 +194,23 @@ export function nxWorkspaceFields(json: Record<string, unknown> | undefined): Nx
|
|
|
153
194
|
const parallel = nx?.parallel ?? nx?.tasksRunnerOptions?.default?.options?.parallel
|
|
154
195
|
if (typeof parallel === 'number' && Number.isInteger(parallel) && parallel > 0)
|
|
155
196
|
out.push({
|
|
156
|
-
field: 'concurrency',
|
|
197
|
+
field: 'run.concurrency',
|
|
157
198
|
source: String(parallel),
|
|
158
|
-
note: `nx.json \`parallel: ${parallel}\`: add \`concurrency: ${parallel}\` to vx
|
|
199
|
+
note: `nx.json \`parallel: ${parallel}\`: add \`run: { concurrency: ${parallel} }\` to .vx/workspace.ts`,
|
|
159
200
|
})
|
|
160
201
|
const base = nx?.defaultBase ?? nx?.affected?.defaultBase
|
|
161
202
|
if (typeof base === 'string' && base.trim() !== '')
|
|
162
203
|
out.push({
|
|
163
|
-
field: 'affectedBase',
|
|
204
|
+
field: 'run.affectedBase',
|
|
164
205
|
source: quoted(base.trim()),
|
|
165
|
-
note: `nx.json \`defaultBase\` ${JSON.stringify(base.trim())}: add \`affectedBase: ${JSON.stringify(base.trim())}\` to vx
|
|
206
|
+
note: `nx.json \`defaultBase\` ${JSON.stringify(base.trim())}: add \`run: { affectedBase: ${JSON.stringify(base.trim())} }\` to .vx/workspace.ts`,
|
|
166
207
|
})
|
|
167
208
|
const size = nxSizeText(nx?.maxCacheSize)
|
|
168
209
|
if (size !== undefined)
|
|
169
210
|
out.push({
|
|
170
|
-
field: '
|
|
211
|
+
field: 'cache.retention',
|
|
171
212
|
source: /^\d+[KMG]?B$/.test(size) ? `{ maxSize: ${quoted(size)} }` : undefined,
|
|
172
|
-
note: `nx.json \`maxCacheSize\`: add \`
|
|
213
|
+
note: `nx.json \`maxCacheSize\`: add \`cache: { retention: { maxSize: ${JSON.stringify(size)} } }\` to .vx/workspace.ts`,
|
|
173
214
|
})
|
|
174
215
|
return out
|
|
175
216
|
}
|