@vzn/vx-migrate 0.0.492 → 0.0.494

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 CHANGED
@@ -164,6 +164,10 @@ Two cached tasks on one workspace path cannot both keep their cache, and the fir
164
164
 
165
165
  `maxCacheSize` (or `NX_MAX_CACHE_SIZE`, above it as in Nx) is the run's `cacheRetention.maxSize` when `vx.workspace.ts` sets no retention, in Nx's grammar (`10GB`, `1.5 GB`, bare bytes); `0` is no cap.
166
166
 
167
+ ### Project tags
168
+
169
+ An Nx project's `tags` are its vx `tags`, so `vx run build --filter tag:scope:web` (or Nx's `--projects tag:scope:web`) selects what `nx run-many -t build -p tag:scope:web` does. A package whose `vx.config` declares `tags` keeps its own. A blank tag is dropped (vx refuses one).
170
+
167
171
  ### The graph snapshot
168
172
 
169
173
  Once per run the plugin keys its snapshot (`<cache dir>/nx-project-graph.json`) on what Nx computes the graph from: `nx.json` and the files its `extends` chain names by content, and the worktree as git sees it — `HEAD`, `git status -uall` (core's own for the run, `DiscoverContext.worktreeChanges`: the tree is walked once, which took ~96 ms per run on refine; a `root` elsewhere asks git itself), and the content of every listed path under a project root (the last graph's too, so a project `discover` names counts before it is one), any `project.json`, or at the root (manifests, lockfiles, `tsconfig*.json`). A different key, or no snapshot, runs `node_modules/.bin/nx graph --file=<snapshot>` — the one time Nx itself runs, served from the daemon when one is up. So a source edit that adds a cross-package `import` (an edge Nx derives) or a config an Nx plugin infers targets from (`vite.config.ts`) re-exports before the run, tracked or untracked, and a touch without an edit, or a stray file a task writes at the root, does not. Outside a git worktree the plugin falls back to the manifests' mtimes. The snapshot and its key live in the cache dir, which ignores itself (a `*` `.gitignore` inside it), so writing them moves nothing. Nx's own caches (`.nx/cache/`, `.nx/workspace-data/`, which the export writes) never enter the key: under a root project (a standalone repo) that does not ignore them, each export re-exported on the next run. An export that fails with a snapshot in hand warns and runs on the previous graph. The snapshot is machine-local, like the cache: nothing about it enters a key. Measured at 1,000 projects: the export runs once (1.5 s with the daemon off after an edit, 0.9 s with it on; Nx's own computation), and a warm run with nothing changed pays the key, 43 ms at min where the manifest stats it replaced took 9 (item 1075), plus the mapping. Under `vx watch` the same rule runs per cycle: a `project.json` edit is the next cycle's tasks, the export included — 1.3 s from the edit to the new command's effect on that workspace. The mapping itself is kept beside it (`<cache dir>/vx-migrate-nx-mapping.json`), keyed on everything it reads — the graph, `nx.json` and its `extends` chain, every package manifest, each project dir's `.env` names, `NX_LOAD_DOT_ENV_FILES`, which bins are installed and the mapper's own code — so a warm run with nothing changed maps nothing.
@@ -222,7 +226,7 @@ Reads the root pipeline (`tasks` in Turbo 2, `pipeline` in Turbo 1), per-package
222
226
 
223
227
  ### Nx
224
228
 
225
- Reads the **resolved** project graph only (`.nx/workspace-data/project-graph.json` when exported, else the one the workspace's own `nx graph` exports into a temp file, as `nx()` does), through the same mapper `nx()` runs live, executors aside (below). Targets Nx plugins infer at runtime are frozen as the snapshot saw them. `nx:run-commands` is the one shell line `nx()` runs (see [`nx:run-commands`](#nxrun-commands) above: where, parallel or in order, forwarded arguments, `env`, `readyWhen`) — storybook's `compile` is `cd ../../.. && node ./scripts/build/build-package.ts --cwd code/lib/cli`; a plain `command` is that shorthand; `nx:run-script` is the package's script body with its `pre<name>` / `post<name>` hooks folded in (or `yarn run <name>` when the body calls yarn's `run` builtin; an empty script is the placeholder with a todo), `nx:noop` is a group task; Nx 15–16's `@nrwl/workspace:run-commands` and `run-script` (and their `@nx/workspace:` names) are the `nx:` executors they re-exported; **an executor target is its `nx-exec` line**, as `nx()` runs it: the migrator translates no executor, so Nx and `@vzn/vx-migrate` stay installed until each such line is rewritten as the command it runs (a server executor Nx knows, `@nx/js:node` or a dev server, is still a persistent task). The report also says what `vx.workspace.ts` still holds, as the Turbo migration does: the `nx()` `vx init` declared, which keeps reading nx.json, and a lockfile with no `@vzn/vx-lockfile` plugin, where Nx keyed each project on the npm packages it depends on. A written command that still runs Nx itself (a run-commands `nx run b:build`, `npx nx test b`, a script's `nx exec -- tsc`) carries a TODO: it works only while Nx is installed. An Nx project no workspace glob lists (an integrated repo's `project.json` library) gets a `package.json` (`name`, `private`) where it has none, and a note names the directories to add to `workspaces` (or `pnpm-workspace.yaml`): core finds a project only through those globs, and the migrator never edits the root manifest. A target with `configurations` writes one task per configuration (`build`, `build:ci`). Named inputs expand from `nx.json` when readable. An output path is kept as written, a `!` one too (`{projectRoot}/dist` → `dist`, `{projectRoot}/bin/tool` → `bin/tool`; one naming an unset `{options.x}` is dropped, as Nx drops it; a dotted `{options.outputPath.base}` walks the options, as Nx does; an extglob is put in vx's grammar: Next's inferred `.next/!(cache)/**/*` is `.next/*/**/*` with `!.next/cache/**/*`, `@(js|map)` is `{js,map}`, and a form with no vx spelling is dropped with a TODO): vx reads a bare path as the file or the whole tree under it, so a directory and an extensionless binary both save and restore. nx.json's `parallel`, `defaultBase` and `maxCacheSize`, which `nx()` applies live, are each a note naming the `vx.workspace.ts` field to add (`concurrency`, `affectedBase`, `cacheRetention.maxSize`): the workspace file is written without them, and from nx.json alone, never the environment. vx derives package edges from `package.json`; an Nx graph edge with no manifest path (`implicitDependencies`, a tsconfig path) becomes, for each `^target` of the dependant, an explicit `pkg#target` edge to what Nx's own walk reaches — each dependency that has the target, and through one that lacks it, its dependencies — so the order and the key are Nx's. The reverse too: where a project's manifest names a workspace package its Nx graph does not reach (an `implicitDependencies: ["!a"]` that breaks a manifest cycle), its `^target` becomes the explicit edges Nx draws, since vx's `^` would follow the manifest and bring the cycle back.
229
+ Reads the **resolved** project graph only (`.nx/workspace-data/project-graph.json` when exported, else the one the workspace's own `nx graph` exports into a temp file, as `nx()` does), through the same mapper `nx()` runs live, executors aside (below). Targets Nx plugins infer at runtime are frozen as the snapshot saw them. `nx:run-commands` is the one shell line `nx()` runs (see [`nx:run-commands`](#nxrun-commands) above: where, parallel or in order, forwarded arguments, `env`, `readyWhen`) — storybook's `compile` is `cd ../../.. && node ./scripts/build/build-package.ts --cwd code/lib/cli`; a plain `command` is that shorthand; `nx:run-script` is the package's script body with its `pre<name>` / `post<name>` hooks folded in (or `yarn run <name>` when the body calls yarn's `run` builtin; an empty script is the placeholder with a todo), `nx:noop` is a group task; Nx 15–16's `@nrwl/workspace:run-commands` and `run-script` (and their `@nx/workspace:` names) are the `nx:` executors they re-exported; **an executor target is its `nx-exec` line**, as `nx()` runs it: the migrator translates no executor, so Nx and `@vzn/vx-migrate` stay installed until each such line is rewritten as the command it runs (a server executor Nx knows, `@nx/js:node` or a dev server, is still a persistent task). The report also says what `vx.workspace.ts` still holds, as the Turbo migration does: the `nx()` `vx init` declared, which keeps reading nx.json, and a lockfile with no `@vzn/vx-lockfile` plugin, where Nx keyed each project on the npm packages it depends on. A written command that still runs Nx itself (a run-commands `nx run b:build`, `npx nx test b`, a script's `nx exec -- tsc`) carries a TODO: it works only while Nx is installed. An Nx project no workspace glob lists (an integrated repo's `project.json` library) gets a `package.json` (`name`, `private`) where it has none, and a note names the directories to add to `workspaces` (or `pnpm-workspace.yaml`): core finds a project only through those globs, and the migrator never edits the root manifest. A target with `configurations` writes one task per configuration (`build`, `build:ci`). A project's Nx `tags` are written as its `tags`. Named inputs expand from `nx.json` when readable. An output path is kept as written, a `!` one too (`{projectRoot}/dist` → `dist`, `{projectRoot}/bin/tool` → `bin/tool`; one naming an unset `{options.x}` is dropped, as Nx drops it; a dotted `{options.outputPath.base}` walks the options, as Nx does; an extglob is put in vx's grammar: Next's inferred `.next/!(cache)/**/*` is `.next/*/**/*` with `!.next/cache/**/*`, `@(js|map)` is `{js,map}`, and a form with no vx spelling is dropped with a TODO): vx reads a bare path as the file or the whole tree under it, so a directory and an extensionless binary both save and restore. nx.json's `parallel`, `defaultBase` and `maxCacheSize`, which `nx()` applies live, are each a note naming the `vx.workspace.ts` field to add (`concurrency`, `affectedBase`, `cacheRetention.maxSize`): the workspace file is written without them, and from nx.json alone, never the environment. vx derives package edges from `package.json`; an Nx graph edge with no manifest path (`implicitDependencies`, a tsconfig path) becomes, for each `^target` of the dependant, an explicit `pkg#target` edge to what Nx's own walk reaches — each dependency that has the target, and through one that lacks it, its dependencies — so the order and the key are Nx's. The reverse too: where a project's manifest names a workspace package its Nx graph does not reach (an `implicitDependencies: ["!a"]` that breaks a manifest cycle), its `^target` becomes the explicit edges Nx draws, since vx's `^` would follow the manifest and bring the cycle back.
226
230
 
227
231
  ## `turboCache()` — a Turbo remote cache
228
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-migrate",
3
- "version": "0.0.492",
3
+ "version": "0.0.494",
4
4
  "license": "MIT",
5
5
  "description": "Adopt @vzn/vx from Turborepo or Nx: run the repo unchanged, migrate it to vx.config.ts, and keep your Turbo or Nx remote cache",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "peerDependencies": {
42
- "@vzn/vx": "^0.0.492"
42
+ "@vzn/vx": "^0.0.494"
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
@@ -101,6 +101,7 @@ export function adoptionPlugin(
101
101
  }
102
102
  const project = mapped.byName.get(ctx.name)
103
103
  if (project === undefined) return
104
+ if (project.tags !== undefined) config.tags ??= project.tags
104
105
  config.tasks ??= {}
105
106
  for (const t of project.tasks) {
106
107
  if (t.task === null) continue
@@ -19,11 +19,12 @@ import {
19
19
  } from '@vzn/vx'
20
20
  import type { Gaps } from './plugin-gaps.js'
21
21
 
22
- /** What the stage uses of a mapping: tasks per package name, and the gaps to report once. */
22
+ /** What the stage uses of a mapping: tags and tasks per package name, and the gaps to report once. */
23
23
  export interface AdoptionMapping {
24
24
  readonly byName: ReadonlyMap<
25
25
  string,
26
26
  {
27
+ readonly tags?: readonly string[]
27
28
  readonly tasks: readonly {
28
29
  readonly name: string
29
30
  readonly task: Record<string, unknown> | null
@@ -57,7 +58,13 @@ function hex(s: string): string {
57
58
 
58
59
  interface Held {
59
60
  readonly key: string
60
- readonly byName: [string, { tasks: { name: string; task: Record<string, unknown> | null }[] }][]
61
+ readonly byName: [
62
+ string,
63
+ {
64
+ tags?: readonly string[]
65
+ tasks: { name: string; task: Record<string, unknown> | null }[]
66
+ },
67
+ ][]
61
68
  readonly notes: string[]
62
69
  readonly todos: [string, string[]][]
63
70
  }
@@ -85,7 +92,10 @@ export async function cachedMapping(
85
92
  key,
86
93
  byName: [...mapping.byName].map(([name, p]) => [
87
94
  name,
88
- { tasks: p.tasks.map((t) => ({ name: t.name, task: t.task })) },
95
+ {
96
+ ...(p.tags !== undefined ? { tags: p.tags } : {}),
97
+ tasks: p.tasks.map((t) => ({ name: t.name, task: t.task })),
98
+ },
89
99
  ]),
90
100
  notes: [...mapping.gaps.notes],
91
101
  todos: [...mapping.gaps.todos].map(([todo, ids]) => [todo, [...ids]]),
package/src/nx/nx-map.ts CHANGED
@@ -68,6 +68,7 @@ interface NxNode {
68
68
  name?: string
69
69
  data?: {
70
70
  root?: string
71
+ tags?: unknown[]
71
72
  namedInputs?: Record<string, unknown[]>
72
73
  targets?: Record<string, NxTarget>
73
74
  metadata?: { targetGroups?: unknown }
@@ -282,6 +283,14 @@ export interface NxMapping {
282
283
  readonly notes: string[]
283
284
  }
284
285
 
286
+ /** A node's tags as vx takes them: a blank one would be refused at load. */
287
+ function nxTags(node: NxNode | undefined): string[] {
288
+ const tags = node?.data?.tags
289
+ return Array.isArray(tags)
290
+ ? tags.filter((t): t is string => typeof t === 'string' && t.trim() !== '')
291
+ : []
292
+ }
293
+
285
294
  function normRel(p: string): string {
286
295
  return p === '' || p === '.' ? '.' : p.replace(/\/$/, '')
287
296
  }
@@ -541,10 +550,12 @@ export async function mapNxWorkspace(
541
550
  }
542
551
  dropUnheldDeps(t, emitted, emittedIds)
543
552
  }
553
+ const tags = nxTags(nodeName === undefined ? undefined : nodeMap[nodeName])
544
554
  projects.push({
545
555
  name: meta.name,
546
556
  dir: meta.dir,
547
557
  importLines: [],
558
+ ...(tags.length > 0 ? { tags } : {}),
548
559
  tasks: resolveSharedOutputs(tasks),
549
560
  })
550
561
  }
@@ -820,7 +831,7 @@ function buildTask(
820
831
  patterns,
821
832
  Object.entries(nodeMap).map(([name, n]) => ({
822
833
  name,
823
- tags: ((n?.data as { tags?: unknown } | undefined)?.tags as string[] | undefined) ?? [],
834
+ tags: nxTags(n),
824
835
  ...(typeof n?.data?.root === 'string' ? { root: n.data.root } : {}),
825
836
  })),
826
837
  ),