@vzn/vx-migrate 0.0.651 → 0.0.652
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 +21 -21
- package/package.json +2 -2
- package/src/migrate-scripts.ts +2 -2
- package/src/nx/nx-map.ts +5 -6
- package/src/shared-outputs.ts +22 -16
- package/src/tracked-outputs.ts +1 -1
- package/src/turbo/turbo-map.ts +14 -13
- package/src/vite-task/vite-task-map.ts +3 -2
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ The package exports `turbo`, `nx`, `turboCache`, `nxCache` and their options typ
|
|
|
30
30
|
|
|
31
31
|
## `turbo()` — a Turbo repo, mid-migration
|
|
32
32
|
|
|
33
|
-
Until `bunx @vzn/vx-migrate` writes native config, `vx run build --all` maps every package's `build` script the way `turbo run build` would: `dependsOn` edges (`^build`, same-package deps, `pkg#task`), `inputs` as every task's key and `outputs` as
|
|
33
|
+
Until `bunx @vzn/vx-migrate` writes native config, `vx run build --all` maps every package's `build` script the way `turbo run build` would: `dependsOn` edges (`^build`, same-package deps, `pkg#task`), `inputs` as every task's key and `outputs` as `cache: true`, `env` / `passThroughEnv`, `cache: false`, `persistent`. Turbo's global fields (`globalDependencies`, `globalEnv`, `globalPassThroughEnv`, Turbo 1's `globalDotEnv`) are inlined into every task; a `*` env name (`NEXT_PUBLIC_*`) expands over the run's environment, as Turbo matches it; `*` is Turbo's only wildcard (`\*`, a leading `\!`, `?` and `[` are literal), and a literal vx cannot key (unkey's `NEXT_PUBLIC_\*`) is left out, reported once only when the run's environment sets it; per-package `turbo.json` overlays apply. A `turbo.jsonc` (Turbo 2.5+) is read wherever a `turbo.json` would be, at the root and in a package; a directory holding both is refused, as Turbo refuses it, and a workspace root with neither is refused, naming the fix. An absolute path in `inputs`, `outputs`, `globalDependencies` or `global.inputs` is refused naming the file, as Turbo refuses it. Turbo 2's framework inference (a package on `next` has `NEXT_PUBLIC_*` and `NEXT_DEPLOYMENT_ID` hashed and passed; Turbo's whole table, `expo`, `nuxt`, `remix`, `@sveltejs/kit` and the rest, each package taking the first framework it matches, as Turbo does, and leaving out names under the platform's `TURBO_CI_VENDOR_ENV_KEY`) applies too: the prefix expands over the run's environment into that package's tasks, and a task's `!` entry takes names back (`bunx @vzn/vx-migrate`, which writes files for another environment, names each framework and its packages in a note instead). Turbo's builtin env applies as Turbo's source has it: `VERCEL_ANALYTICS_ID` and `VERCEL_TARGET_ENV` are keyed in every task (a `globalEnv` `!` entry such as `!VERCEL*` takes them back), Next's skew rule keys `VERCEL_DEPLOYMENT_ID` when `VERCEL_SKEW_PROTECTION_ENABLED=1`, and the literal names of Turbo's builtin pass-through list (`TZ`, `HOME`, `CI`, the Vercel, Docker and IDE names) reach every task unkeyed. Its wildcard families (`VERCEL_*`, `GITHUB_*`, `DOCKER_*` and the rest) are not matched against the run's environment: `exec.env.passThrough` is keyed by name, so a live match keyed each machine apart. The written config keys the defaults and `VERCEL_DEPLOYMENT_ID` always (a miss where Turbo would hit, never a stale hit) and passes the same literal names from the preset; a note names the wildcard families to add where a task reads them. What runs is what a migration would have written, minus the file: the key a task derives here equals the key the written config would derive.
|
|
34
34
|
|
|
35
35
|
| Option | Meaning |
|
|
36
36
|
| ------ | ----------------------------------------------------------------------------------------------------------- |
|
|
@@ -62,7 +62,7 @@ Rules:
|
|
|
62
62
|
- A transit node (Turbo's documented pattern, its with-vitest example: `transit: { dependsOn: ["^transit"] }` with no script anywhere, and `test: { dependsOn: ["transit"] }`) is a key-only task in each package that defines it: a keyed group (no command, nothing spawns) keyed on its inputs, with its `^transit` edge, so a dependant's `test` re-keys on its dependencies' sources as Turbo re-hashes it. A `^` task that some package has a script for, or that nothing depends on, is no transit node. A package without the script of a `^` task others run gets the same key-only task, as Turbo hashes a no-op node there: an edit to it re-keys its dependants. A `build` whose only edge is `^build` is not written: core gives every project with no `build` that node (solid's three script-less packages each got a `build` of their own). A dependency without the script whose own edges lack `^x` gets it too, so a `^x` stops there as Turbo's does (opencode's `build: { dependsOn: [] }`: core walked past to the builds below, which Turbo's `^build` never reaches).
|
|
63
63
|
- `with` (tasks Turbo runs alongside, `web#dev` with `api#dev`): an edge to each sidecar that is persistent, so vx starts the task once the sidecar has spawned and runs the sidecar only when the task runs; a sidecar that ends would be waited for, so it is a todo instead, and a pair that names each other keeps one edge (two would be a cycle). A task with no script whose `with` names persistent sidecars (Turbo's with-tailwind example: `ui` has no `dev` script, its `dev` starts `dev:styles` and `dev:components`) is a group task that depends on them.
|
|
64
64
|
- `inputs`: a structured entry (Turbo 2.11) is its `globs`, plus `**/*` with `withDefaults`, for `startup` and `jit` alike; `dependencyOutputs` adds none where the producer is cached (vx hashes each dependency's key into it); from a `cache: false` producer (vercel/vercel's `//#generate:cache-keys`, which records the host) the named files, or its outputs, are a `inputs.workspaceRuntime` probe, and `inputs.tasks` leaves the producer out, so core answers the probe after it ran. Then: absent or `[]` → `**/*` (Turbo's default); `$TURBO_DEFAULT$` → `**/*`; exclusions alone narrow `**/*`; `$TURBO_ROOT$/<path>` → `inputs.workspaceFiles` (negation kept); any other `$TURBO_ROOT$` use is a todo. `globalDependencies` and Turbo 1's `globalDotEnv` land in `workspaceFiles` too (Turbo hashes `globalDependencies` apart from a task's own inputs, so neither's `!` reaches the other's files, and vx subtracts each `!` from the whole list: a `!` that may reach the other side's files is dropped, which keys more, never a stale hit; Turbo 2.11's `global.inputs` joins each task's inputs, where its `!` entries apply), and a task's Turbo 1 `dotEnv` in its `files`. Turbo 1's `$NAME` entries, in `globalDependencies` or a task's `dependsOn`, are env vars: they join `inputs.env` and `exec.env.passThrough`. A package nested in the task's package (cal.com's apps inside `@calcom/app-store`) is hashed by Turbo with its parent, and core's globs stop at a project: each glob that reaches one is also listed in `inputs.workspaceFiles`. Turbo's glob grammar is translated as Nx's is (`*.[jt]s` is `*.{[jt],j,t}s`); an input with no safe form widens to `**/*` with a todo. A wildcard `X/**` input, `!` or not, also lists `X`, as outputs do: Turbo's `**` matches zero segments, so `*/**` keys on the package-root files too. A `.env`-shaped input (`.env*`, `.env.local`, a task's Turbo 1 `dotEnv`, create-turbo's `globalDependencies: ["**/.env.*local"]`) is gitignored as a rule, so it is keyed by a probe that hashes the `.env` files it can name (`inputs.runtime`) or, for a root entry, the files its root-relative globs name, hidden ones too, as Turbo's `*` matches them (`workspaceRuntime`; `**/<name>` is a `find -name` that skips `node_modules`, and another `**` walks the workspace), rather than as a file glob git never reports. A package whose `.env` globs all sit at its root gets a one-shell probe of that directory; one below the root walks the package.
|
|
65
|
-
- `outputs`: `$TURBO_ROOT$/<path>` → `
|
|
65
|
+
- `outputs`: `$TURBO_ROOT$/<path>` → `outputs.workspaceFiles`; a negated output rides beside its positives and takes its paths back from the clean, the save and the restore (Next's `.next/**` minus `!.next/cache/**` keeps the cache), and one with no positive beside it takes back nothing and is dropped. Past its first segment an output takes Turbo's grammar (`dist/**/*.[cm]js`); the first stays a literal (a route directory). A wildcard `X/**` also lists `X`: Turbo's `**` matches zero segments, so medusa's `*/**` caches the package-root `tsconfig.tsbuildinfo` too (its `package.json` is taken back, below); a literal `dist/**` needs no `dist`. An output whose first segment is a wildcard (`**/*.d.ts`, medusa's `*/**`) stays cached, as in `nx()` (`turbo()` 2026-10-14): Turbo never cleans an output, vx cleans it before every run and every restore, and such a glob can reach the sources; core never cleans a committed file, and every untracked file it reaches is deleted, which `vx lock`'s warnings name (`output "*/**": … every untracked file this glob reaches is deleted`). Negations beside it (`!src/**`) keep what they name. Without git the files cannot be told apart, and the task runs uncached with a todo. A glob that reaches no tracked kind gets no warning: one segment with a literal extension the package tracks no file of (`*.xml`), `**/<dir>/**` when the package tracks nothing under a directory of that name (vercel/ai's `**/dist/**`), and a first segment no tracked top-level entry matches (tldraw's `dist-*/**`). An output that covers the package's own `package.json` (trpc's client build lists it: the build rewrites `exports`) takes the manifest back with `!package.json` and keeps the cache, in `turbo()` as in `nx()` (2026-10-03): vx would otherwise delete it before every run. The build rewrites a file it may list as an input (`@vzn/vx-lockfile` lists `package.json`), so the Turbo mapping also sets `inputs.stability: { strategy: 'mutable' }` and an identical rewrite counts as unchanged. So does one that covers the package's vx.config; `turbo()` and `nx()` check the config the package has, and `vx-migrate` the one it writes, so sanity's `*.js` shims stay cached beside none. `nx()` keeps a wildcard-first output cached the same way (2026-10-03). A committed file under an output (typescript-eslint's `data/sponsors.json` in a cached `data`) needs nothing: core never cleans a file git tracks, so it survives and the task keeps its cache. A task whose command runs `tsc` that writes a `.tsbuildinfo` (`-b`, `composite` or `incremental`, read through the tsconfig's `extends` and, for `-b`, its `references`) outside its outputs also outputs that file: tsc re-emits only what its buildinfo calls changed, so with `rootDir: "src"` and `outDir: "dist"` its `tsconfig.tsbuildinfo` beside the tsconfig survived the clean before a miss and the miss cached a `dist/` holding only the edited file. Nx infers the file as an output; Turbo never cleans `dist/`.
|
|
66
66
|
- A `**` glued to text in an input, output or `globalDependencies` glob is read as Turbo reads it (turborepo#5431): `src/**.ts` → `src/**/*.ts`, `gen**` → `gen*/**`. Core refuses the glued spelling.
|
|
67
67
|
- `env` / `passThroughEnv`: explicit names go to `inputs.env` (env only) and `exec.env.passThrough` (both, plus both globals); a wildcard expands over the run's environment under `turbo()`, which maps where the tasks run (a new variable name such a wildcard matches maps afresh); the CLI, which writes for another environment, lists the names the task's package and its workspace dependencies spell (a root task: the whole repo; clerk's `E2E_*`), noted once per entry, and one nothing spells is a todo; `*` is the only wildcard, as in Turbo, so `\*`, a leading `\!`, `?` and `[` are literal: unkey's `NEXT_PUBLIC_\*` names one variable, which vx cannot key, so it is dropped and reported once for the workspace with the count of tasks naming it (openstatus's root `build` env `\*` had written one todo into each of 45 configs; `turbo()`: only when the run's environment sets it, as Turbo hashes nothing otherwise); and a `!` entry (openstatus' `!NEXT_PUBLIC_VERCEL_URL`) removes the names it matches from its list, with no todo, since vx matches no name it is not given. A name both a global list and the task's own list carry is listed once. A name that is no shell variable name (`p-q`) is keyed but left out of `exec.env.passThrough`, as `sh` drops it before the task runs.
|
|
68
68
|
- Framework inference (Turbo hashes and passes Next's `NEXT_PUBLIC_*`, Vite's `VITE_*`, … for a package that depends on the framework): `turbo()` expands the prefix over the run's environment; the CLI writes the names the tracked source and `.env.example` of the package and its workspace dependencies spell (the bare prefix, as a `startsWith` test spells it, is none) (Next bundles theirs: cal.com's web, 23 alone, 58 with them), and a note asks for any only an installed dependency reads.
|
|
@@ -70,7 +70,7 @@ Rules:
|
|
|
70
70
|
- **Two tasks of one package on one output path** (strapi's `build`, `build:code` and `build:types`, all on `dist/**`): vx cleans a task's outputs before it runs and before a restore, so the loader refuses two cached tasks whose outputs provably overlap. Under vx's default `rules.exclusiveOutputs` an edge between the two changes nothing (twenty's `build:individual` writing into `build`'s `dist`). The mapping resolves it before the file is written — `build` keeps its cache, else the task with a `^` edge, else the first declared (typescript-eslint's `typecheck` has `^typecheck` and writes declarations into `build`'s `dist`: keeping it left `build` uncached); the rest run uncached with a todo naming the keeper and the fix, their own output path. A shared tsc incremental file (create-t3-turbo's `build` and `typecheck` both on `.cache/tsbuildinfo.json`) stays with the keeper alone, and the other task keeps its cache when it emits nothing else (a typecheck); one that emits more runs uncached, as `tsc --incremental` skips files the keeper's tsbuildinfo calls built. When either side is a directory at any depth (solid's build `**/dist`, its types `**/types`), the two meet only where one tree nests in the other: the keeper takes the sibling's glob back (`!coverage/**`, `!**/types`) and both keep their cache, as under Turbo. Same rule for Nx targets.
|
|
71
71
|
- **A task whose inputs read another task's outputs** (a `test` keyed on `**/*` beside a `build` writing `dist/**`): `vx lock` refuses it, since such a key cannot be known before the producer ran. Each overlapping output is taken back from the reader's inputs with `!` (`['**/*', '!dist/**']`), in package and workspace inputs alike, and a workspace output another package writes into this one is taken back from its package inputs too; Turbo and Nx hash what git tracks, and a build's outputs are not, so the key reads what the adopted tool read. A literal input another task writes cannot be taken back: that task runs uncached with a todo. A committed file under such an output is still a source: a reader the `!` would hide it from runs uncached with a todo. A keyed group (an `inputs:<name>` twin, a transit node) runs nothing, so core lets it read what a task writes: it gets no `!` for a literal, the `!` that would hide a committed file goes, and it keeps its key (typescript-eslint's ast-spec commits the `*.shot` snapshots its `test` outputs, and its twin was written as `{}`, which core refuses). An output that names one committed file (eslint's `eslint-suppressions.json` in a `lint` task's outputs) is dropped, and so is the `!` each reader got for it: the file stays in every key, as Turbo and Nx hash it, and the readers stay cached. Same for Nx targets.
|
|
72
72
|
- **Two packages' tasks on one workspace output** (cal.com's shared `post-install` writes `../../node_modules/@prisma/client/**` from every package with the script): core refuses two cached tasks on one path with no edge between them, so `turbo()` (and `nx()`) keeps the first (in package and task order) cached and runs the rest uncached, each with a todo naming the keeper. A package's own outputs count at their workspace path: typescript-eslint's root project caches `dist` and each package's typecheck `dist/packages/<name>`, one path twice. Edges across packages are not read, so an ordered pair loses its cache too.
|
|
73
|
-
- `cache: false` or `persistent: true` → no `cache
|
|
73
|
+
- `cache: false` or `persistent: true` → no `cache: true`, the task still keyed on its `inputs` and `env` as Turbo hashes it; a persistent task gets `exec.persistent: {}` and, when some task depends on it, the consumer's `persistentTodo`.
|
|
74
74
|
- A task's `description` (Turbo 2.11.5's schema) is the vx task's `description`.
|
|
75
75
|
- `outputLogs: "new-only"` maps to nothing: frames for the tasks that ran and a one-liner per cache hit is vx's default flow already. The other values are per-run in vx, so they are a todo naming the flag (`vx run … --output-logs hash-only`).
|
|
76
76
|
- `interruptible` maps to nothing: `vx watch` keeps a persistent task up across cycles and restarts it only when its config changes or it dies.
|
|
@@ -97,7 +97,7 @@ import { nx } from '@vzn/vx-migrate'
|
|
|
97
97
|
export default defineWorkspace({ plugins: [nx()] })
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Until `bunx @vzn/vx-migrate` writes native config, `vx run build --all` maps every project's `build` target the way `nx run-many -t build` would. `nx()` is also the repo's edge source: each project's dependencies on Nx's graph (package deps, `implicitDependencies`, imports) are its `deps`, so `^build` follows Nx's order with no lockfile plugin. The plugin reads Nx's **resolved** project graph — where Nx has already applied `targetDefaults`, expanded `namedInputs`, inferred targets through its plugins and interpolated `{projectRoot}` and friends — so nothing is re-derived here. Per target: `nx:run-commands` and a plain `command` run as one shell line that does what Nx's run-commands does (below); `nx:run-script` is the package script, with the `$npm_package_name`, `$npm_package_version` and `$npm_lifecycle_event` it reads defined as `<pm> run` sets them (a migration reads the name and version from the manifest it imports, so a bump reaches them; any other `$npm_*` is a todo); `nx:noop` is a group task, and so is a target with neither an executor nor a command that has dependencies, as Nx normalizes it (one with none is dropped, as Nx drops it); **every other executor runs through `nx-exec`** with the executor and its options on the command line. Every target is keyed on its `inputs` (a target Nx does not cache on its own inputs alone, no `^` named inputs, since Nx hashes no dependency's hash into it), and a target Nx caches (`cache: true`, or the legacy `cacheableOperations` list in `nx.json`) gets its `outputs` as
|
|
100
|
+
Until `bunx @vzn/vx-migrate` writes native config, `vx run build --all` maps every project's `build` target the way `nx run-many -t build` would. `nx()` is also the repo's edge source: each project's dependencies on Nx's graph (package deps, `implicitDependencies`, imports) are its `deps`, so `^build` follows Nx's order with no lockfile plugin. The plugin reads Nx's **resolved** project graph — where Nx has already applied `targetDefaults`, expanded `namedInputs`, inferred targets through its plugins and interpolated `{projectRoot}` and friends — so nothing is re-derived here. Per target: `nx:run-commands` and a plain `command` run as one shell line that does what Nx's run-commands does (below); `nx:run-script` is the package script, with the `$npm_package_name`, `$npm_package_version` and `$npm_lifecycle_event` it reads defined as `<pm> run` sets them (a migration reads the name and version from the manifest it imports, so a bump reaches them; any other `$npm_*` is a todo); `nx:noop` is a group task, and so is a target with neither an executor nor a command that has dependencies, as Nx normalizes it (one with none is dropped, as Nx drops it); **every other executor runs through `nx-exec`** with the executor and its options on the command line. Every target is keyed on its `inputs` (a target Nx does not cache on its own inputs alone, no `^` named inputs, since Nx hashes no dependency's hash into it), and a target Nx caches (`cache: true`, or the legacy `cacheableOperations` list in `nx.json`) gets its `outputs` as `cache: true`, with no `inputs` meaning Nx's `default` and `^default`, named inputs resolved per project (nx.json's under the project's own; one neither defines, such as an `extends` preset not installed, keys the whole project with a todo, never an empty list that keys the task on its config alone), `{workspaceRoot}` / `{projectRoot}` / `{projectName}` interpolated anywhere in a path as Nx does (`{workspaceRoot}/coverage/{projectRoot}`), Nx's glob grammar translated (`*.[jt]s` is `*.{j,t}s`; the default `production` negation `?(*.)+(spec|test).[jt]s?(x)` becomes brace sets, a narrowing only a negation may take; what has no safe form is a todo), a project fileset of negations alone (nx-recipes' `noMarkdown`) starting from every project file, as Nx reads it, and an output outside the project dir (`dist/<project>` at the workspace root, Nx's default layout) a `workspaceFiles` output, an output outside the workspace (an old generator's `reportsDirectory: "../../coverage/<lib>"`) a todo and dropped, as vx caches only inside it, an output whose first segment is a wildcard (`{projectRoot}/**/*.d.ts`) cached, with the committed files it reaches kept (core never cleans one) and the rest cleaned before the run, and an output that covers the project's own `package.json` or vx.config cached with the file taken back by `!`; `dependsOn` becomes the edges (`^build` — dropped when no project has the target, as Nx gives it no edges and core refuses a `^name` nothing declares — `build`, `project:target` → `project#target`, everything past `project:` one target name, as Nx joins it (`ui:build:esm` is ui's `build:esm`; `ui:build:ci` names target `build:ci`, never `build`'s `ci` configuration, and is no edge where ui lacks it), this project's own target winning over a project name, `{ target, projects }` → each matched project's task, `projects` read as Nx's `findMatchingProjects` reads it: a name or a list of names, globs over names and over project directories (`libs/*`), `name:` / `tag:` / `directory:` labels, a bare word as a word in a name, and `!` exclusions; an edge to a target its project lacks is dropped without a word, as Nx drops it — `targetDefaults` that give every `typecheck` a `codegen` one project has; `^name` is every dependency's `name` whatever it holds, `^rsbuild:typecheck` included; a target glob — `test:e2e--*`, `^build-*`, `ui:build-{esm,cjs}`, a `{ target }` object's — expands over every target name in the workspace, as Nx 19.5+ does, before those rules; a same-project glob keeps its own project's matches), `{ env }` inputs pass through (one that is no shell variable name is keyed only), `{ runtime }` inputs run at the workspace root as Nx runs them (`inputs.workspaceRuntime`), with `{workspaceRoot}`, `{projectRoot}` and `{projectName}` written in as the paths and name they stand for (Nx runs the command as written, so `cat {workspaceRoot}/v.txt` keyed on cat's error there), and a command that exits non-zero keys its error and exit code and the task runs on, as Nx hashes its output whatever it exits (`git describe` in a repo with no tags): the written probe is `(<command>) || echo "exit $?"`, a `{ fileset, includeIgnored: true }` literal (Nx 23 hashes it from disk, gitignored or missing) is read by such a probe, since vx's globs see only what git lists, and a glob of one is a todo, a target that says `continuous` — or, in a graph from an Nx older than that field, runs a server executor (`@nx/vite:dev-server`, `@nx/next:server`, …) — is a persistent task and never cached, ready on spawn as Nx has it (Nx starts the dependents once it has started; a run-commands `readyWhen` gates them, below). The target's name never decides: a cached `dev` caches like any other target (nx#32610). `parallelism: false` (Nx runs the target alone) has no vx form and is a todo naming `--concurrency 1`; `syncGenerators` (Nx runs them first) is one workspace note per generator list, counting its tasks and naming `nx sync`; nx.json's `sync.globalGenerators` is one note per run naming `nx sync`. A target with `configurations` is one task per configuration (one whose name another target holds, `vite`'s `build` beside an inferred `vite:build`, is not written, with a todo, as Nx resolves `a:vite:build` to the target): `build` carries the default configuration's options, `build:ci` the `ci` one, and an edge of `build:ci` (own, named or `^`) reaches each target's `ci` task where it declares one, else its default, as Nx's `resolveConfiguration` does; a `^build` there becomes one edge per dependency Nx links. A `^name` input (`^production`, `{ input, dependencies: true }`, the pre-17 `{ input, projects: "dependencies" }`; `projects: "self"` is the own input, as Nx 23 still reads both) is what Nx hashes: each dependency's `name`, over the project graph, whether or not a task edge exists. A `{ input, projects }` list is read as `dependsOn`'s `projects` is; with `dependencies: true` beside it, both the listed projects' dependencies' input (Nx 24's reading) and this project's `^input` (Nx 23's) are keyed. Each project some reader reaches gets an `inputs:<name>` task — a keyed group (no command, nothing spawns, not counted in the run summary) keyed on its own `name` input, depending on its Nx dependencies' twins (a project nothing reaches gets none) — and a task that reads `^name` depends on its direct dependencies' twins, so the whole closure goes into its key with each project hashed once. A dependency FILESET (`^{projectRoot}/tsconfig.lib.json`, `{ fileset, dependencies: true }`, which Nx's own inferred `typecheck` writes) is keyed the same way: its twin, `inputs:fileset-<hash>` (a glob's `*` is no task name; the task's description names the fileset), keys on that fileset in each project. A twin lists its project's `package.json` and `project.json` first (whichever exists; Nx keys every task on its project's manifest and configuration): a fileset that matches nothing (an absent `tsconfig.spec.json`, a dependency's unbuilt `*.d.ts`) is nothing to Nx and no "matched no files" warning, and the file appearing re-keys its readers. The twins spawn nothing and stay out of a run's output and task count, so `vx run build` counts the tasks `nx run-many -t build` does. A path input inside an output one of the project's own targets declares (TanStack/table's `public` input lists `{projectRoot}/dist`) is generated, and gitignored where Nx caches it: Nx's file map skips gitignored files, so Nx hashes nothing there (a changed `dist` file is a cache hit under Nx 23.3), and it is dropped without a todo, and the task that writes it keys its dependants through `dependsOn`.
|
|
101
101
|
|
|
102
102
|
| Option | Meaning |
|
|
103
103
|
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -258,10 +258,10 @@ Reads the root pipeline (`tasks` in Turbo 2, `pipeline` in Turbo 1), per-package
|
|
|
258
258
|
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
259
259
|
| `dependsOn` | `dependsOn`, same micro-syntax (`$TURBO_ROOT$` deps are a TODO) |
|
|
260
260
|
| `inputs` | `inputs.files` (`$TURBO_DEFAULT$` → `**/*`; `$TURBO_ROOT$/x` → `workspaceFiles`) |
|
|
261
|
-
| `outputs` | `
|
|
261
|
+
| `outputs` | `outputs.files`, a negated one included |
|
|
262
262
|
| `env` | `inputs.env` **and** `exec.env.passThrough` (child envs are isolated) |
|
|
263
263
|
| `passThroughEnv` | `exec.env.passThrough` |
|
|
264
|
-
| `cache: false` / `persistent: true` | no `cache
|
|
264
|
+
| `cache: false` / `persistent: true` | no `cache: true`, still keyed on its `inputs`; `exec.persistent: {}`, with a TODO to set `readyWhen` when a task depends on it |
|
|
265
265
|
|
|
266
266
|
### Nx
|
|
267
267
|
|
|
@@ -271,19 +271,19 @@ Reads the **resolved** project graph only (`.nx/workspace-data/project-graph.jso
|
|
|
271
271
|
|
|
272
272
|
Detected when the root `package.json` lists `vite-plus`, after turbo.json and Nx: vite-plus is a whole toolchain, so a repo with either of those runs its tasks there; `--from vite-task` picks Vite Task anyway. There is no live plugin, so `--keep` is refused. Each package's `vite.config.*` (vite-plus's file order) is loaded by Bun as `vp run` sees it, a function config called in build mode, and its `run` block mapped with the package's `package.json` scripts, which `vp run` runs too. The root package's tasks and scripts (`vp run -w`) make it a project when it has a `name`. A command that opens with `vp run` (or `vpr`) is inlined as Vite Task inlines it: `vp run x`, `pkg#x`, `-r x`, `-w x` and `-F <name> x` are `dependsOn` edges (`-r` to each package with `x`, the task's own reference pruned), and the task is a group when nothing follows them. They run in parallel where Vite Task ran a chain in order; any other form (`--parallel`, `-t`, forwarded arguments, a `vp run` after another command) stays in the command with a TODO.
|
|
273
273
|
|
|
274
|
-
| Vite Task | vx
|
|
275
|
-
| --------------------------------------------------------- |
|
|
276
|
-
| `command` (a string, or an array run in order) | `exec.command`, the array joined with `&&`; `command: []` is a group task
|
|
277
|
-
| `cwd` | `cd <cwd> && ` before the command
|
|
278
|
-
| `dependsOn: 'x'` / `'pkg#x'` | the same
|
|
279
|
-
| `dependsOn: { task, from }` | `^task`; when a member listed under a field `from` leaves out also has the task, the explicit `pkg#task` edges, since `^` follows every field
|
|
280
|
-
| `cache.input` / `cache.output` globs | `inputs.files` / `
|
|
281
|
-
| omitted or `{ auto: true }` (traced files) | no `cache
|
|
282
|
-
| `cache.env` | `inputs.env` **and** `exec.env.passThrough`; a `*` entry lists the names the package's tracked files and its workspace dependencies' spell (a TODO when none match); `!` entries take names back
|
|
283
|
-
| `cache.untrackedEnv` | `exec.env.passThrough`, wildcards as `cache.env`
|
|
284
|
-
| `cache: false`, root `run.cache` `false` / `tasks: false` | no `cache`
|
|
285
|
-
| a `package.json` script | a task, uncached; with root `run.cache.scripts` a TODO for its cache
|
|
286
|
-
| `run.enablePrePostScripts` (default `true`) | a script's `pre<name>` / `post<name>` joined into its command; `false` keeps them tasks of their own
|
|
274
|
+
| Vite Task | vx |
|
|
275
|
+
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
276
|
+
| `command` (a string, or an array run in order) | `exec.command`, the array joined with `&&`; `command: []` is a group task |
|
|
277
|
+
| `cwd` | `cd <cwd> && ` before the command |
|
|
278
|
+
| `dependsOn: 'x'` / `'pkg#x'` | the same |
|
|
279
|
+
| `dependsOn: { task, from }` | `^task`; when a member listed under a field `from` leaves out also has the task, the explicit `pkg#task` edges, since `^` follows every field |
|
|
280
|
+
| `cache.input` / `cache.output` globs | `inputs.files` / `outputs.files`; `base: 'workspace'` → `workspaceFiles`, a root task's too; a `**` glued to text (`src/**.ts`), which wax refuses, widens an input to `**/*` and runs an output's task uncached, with a todo |
|
|
281
|
+
| omitted or `{ auto: true }` (traced files) | no `cache: true`, with a TODO: vx infers no inputs or outputs |
|
|
282
|
+
| `cache.env` | `inputs.env` **and** `exec.env.passThrough`; a `*` entry lists the names the package's tracked files and its workspace dependencies' spell (a TODO when none match); `!` entries take names back |
|
|
283
|
+
| `cache.untrackedEnv` | `exec.env.passThrough`, wildcards as `cache.env` |
|
|
284
|
+
| `cache: false`, root `run.cache` `false` / `tasks: false` | no `cache: true` |
|
|
285
|
+
| a `package.json` script | a task, uncached; with root `run.cache.scripts` a TODO for its cache |
|
|
286
|
+
| `run.enablePrePostScripts` (default `true`) | a script's `pre<name>` / `post<name>` joined into its command; `false` keeps them tasks of their own |
|
|
287
287
|
|
|
288
288
|
### package.json scripts
|
|
289
289
|
|
|
@@ -353,10 +353,10 @@ fails leaves the files as written and the note names its error.
|
|
|
353
353
|
Each script becomes a task with its command verbatim. `build` gets
|
|
354
354
|
`dependsOn: ['^build']` (a `build` that only delegates, `pnpm run
|
|
355
355
|
compile`, is a group, and the edge goes on the script that does the
|
|
356
|
-
work) and **no cache
|
|
356
|
+
work) and **no `cache: true`** — under a
|
|
357
357
|
`TODO(vx-migrate)` showing the block to add with the package's real
|
|
358
358
|
inputs and outputs, and the same block commented as the task's first
|
|
359
|
-
line: set the real globs and delete the `// `. A task without
|
|
359
|
+
line: set the real globs and delete the `// `. A task without `cache: true` always runs; a block
|
|
360
360
|
with EMPTY outputs is not "uncached" but a no-output task that hits on
|
|
361
361
|
unchanged inputs and skips the build with nothing to restore (a deleted
|
|
362
362
|
`dist` stays deleted under a green `up-to-date` run — what `vx init`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx-migrate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.652",
|
|
4
4
|
"license": "MIT",
|
|
5
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": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@vzn/vx": "^0.0.
|
|
43
|
+
"@vzn/vx": "^0.0.652"
|
|
44
44
|
},
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
package/src/migrate-scripts.ts
CHANGED
|
@@ -118,7 +118,7 @@ export function cacheTodo(command: string): string {
|
|
|
118
118
|
const words = ` ${command.replace(/[;&|()]/g, ' ')} `
|
|
119
119
|
const hit = BUILD_OUTPUTS.find(([re]) => re.test(words))
|
|
120
120
|
const outputs = (hit?.[1] ?? namedOutputs(command) ?? ['dist/**']).map((o) => `'${o}'`).join(', ')
|
|
121
|
-
return `cache: add \`inputs: { files: ['src/**'] },
|
|
121
|
+
return `cache: add \`inputs: { files: ['src/**'] }, outputs: { files: [${outputs}] }, cache: true\` with this package's real inputs and outputs — without it the task always runs, and so does every task that depends on it; a block with EMPTY outputs would be a cached no-op, not an uncached task`
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// `lint` is not here: a linter reads sources, and an edge to `build`
|
|
@@ -986,7 +986,7 @@ export function migrateScripts(
|
|
|
986
986
|
// Where to add one is the TODO's to say, when there is one: an Nx repo
|
|
987
987
|
// whose only script is `codegen` read "`build` carries a TODO" over
|
|
988
988
|
// "0 TODOs" (the first-five-minutes walk, 2026-09-28).
|
|
989
|
-
'each script became a task with its command verbatim; caching needs declared inputs and outputs, so no task got
|
|
989
|
+
'each script became a task with its command verbatim; caching needs declared inputs and outputs, so no task got `cache: true`',
|
|
990
990
|
],
|
|
991
991
|
projects,
|
|
992
992
|
extraFiles: [],
|
package/src/nx/nx-map.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
resolveSharedWorkspaceOutputs,
|
|
35
35
|
wildcardOutput,
|
|
36
36
|
wildcardTodo,
|
|
37
|
+
uncache,
|
|
37
38
|
} from '../shared-outputs.js'
|
|
38
39
|
import type { TrackedKinds } from '../tracked-outputs.js'
|
|
39
40
|
import { isRecord, packageScripts, relPosix } from '../paths.js'
|
|
@@ -596,7 +597,7 @@ export async function mapNxWorkspace(
|
|
|
596
597
|
dropUnheldDeps(t, meta.name, emitted, emittedIds)
|
|
597
598
|
const reader = dependentReaders.get(t)
|
|
598
599
|
if (reader !== undefined && !reachesProducer(t, meta.name, emittedIds, reached)) {
|
|
599
|
-
|
|
600
|
+
uncache(t.task!)
|
|
600
601
|
t.todos.push(orphanReaderTodo(reader.globs, reader.dependsOn))
|
|
601
602
|
}
|
|
602
603
|
}
|
|
@@ -663,10 +664,7 @@ function resolveAtomizedWorkspaceOutputs(
|
|
|
663
664
|
const cached = (ps: readonly { name: string; dir: string; tasks: GeneratedTask[] }[]): number => {
|
|
664
665
|
const copy = structuredClone(ps.map((p) => ({ name: p.name, dir: p.dir, tasks: p.tasks })))
|
|
665
666
|
resolveSharedWorkspaceOutputs(root, copy)
|
|
666
|
-
return copy.reduce(
|
|
667
|
-
(n, p) => n + p.tasks.filter((t) => t.task?.['cache'] !== undefined).length,
|
|
668
|
-
0,
|
|
669
|
-
)
|
|
667
|
+
return copy.reduce((n, p) => n + p.tasks.filter((t) => t.task?.['cache'] === true).length, 0)
|
|
670
668
|
}
|
|
671
669
|
const better = split.size > 0 && cached(last) > cached(projects)
|
|
672
670
|
resolveSharedWorkspaceOutputs(root, better ? last : projects)
|
|
@@ -1061,7 +1059,8 @@ function buildTask(
|
|
|
1061
1059
|
if (cacheEnabled) {
|
|
1062
1060
|
const outputs: Record<string, unknown> = { files: takingBack(outFiles) }
|
|
1063
1061
|
if (wsOutFiles.length > 0) outputs.workspaceFiles = takingBack(wsOutFiles)
|
|
1064
|
-
task.
|
|
1062
|
+
task.outputs = outputs
|
|
1063
|
+
task.cache = true
|
|
1065
1064
|
}
|
|
1066
1065
|
|
|
1067
1066
|
const generated: GeneratedTask = { name: variant.name, todos, task }
|
package/src/shared-outputs.ts
CHANGED
|
@@ -179,8 +179,8 @@ interface Cached {
|
|
|
179
179
|
function cachedOutputs(t: GeneratedTask, index: number): Cached | null {
|
|
180
180
|
const task = t.task
|
|
181
181
|
if (task === null) return null
|
|
182
|
-
const
|
|
183
|
-
const files =
|
|
182
|
+
const outputs = task['outputs'] as { files?: unknown } | undefined
|
|
183
|
+
const files = outputs?.files
|
|
184
184
|
if (!Array.isArray(files)) return null
|
|
185
185
|
// Where a task's outputs might land is its positive globs, as core reads
|
|
186
186
|
// them: a `!` compiled as a glob is true of every other path.
|
|
@@ -267,9 +267,7 @@ export function resolveSharedOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
267
267
|
for (const { k, gb } of pairs) {
|
|
268
268
|
if (k.back.has(gb)) continue
|
|
269
269
|
k.back.add(gb)
|
|
270
|
-
;(tasks[k.index]!.task!['
|
|
271
|
-
`!${gb}`,
|
|
272
|
-
)
|
|
270
|
+
;(tasks[k.index]!.task!['outputs'] as { files: string[] }).files.push(`!${gb}`)
|
|
273
271
|
}
|
|
274
272
|
kept.push(c)
|
|
275
273
|
continue
|
|
@@ -289,14 +287,14 @@ export function resolveSharedOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
289
287
|
sharedFiles.every((g) => isLiteralPattern(g) && TSBUILDINFO.test(g))
|
|
290
288
|
) {
|
|
291
289
|
c.files = c.files.filter((g) => !sharedFiles.includes(g))
|
|
292
|
-
const outputs =
|
|
290
|
+
const outputs = t.task!['outputs'] as { files: string[] }
|
|
293
291
|
outputs.files = outputs.files.filter((g) => !sharedFiles.includes(g))
|
|
294
292
|
kept.push(c)
|
|
295
293
|
continue
|
|
296
294
|
}
|
|
297
295
|
dropped.add(c.index)
|
|
298
296
|
const shared = c.files.find((gb) => clash.files.some((ga) => hits(clash, ga, gb)))!
|
|
299
|
-
|
|
297
|
+
uncache(t.task!)
|
|
300
298
|
t.todos.push(
|
|
301
299
|
`declares the output ${JSON.stringify(shared)} that ${JSON.stringify(clash.name)} also ` +
|
|
302
300
|
"declares — vx cleans a task's outputs before it runs and before a restore, so two " +
|
|
@@ -369,10 +367,18 @@ export const isKeyedGroup = (t: { readonly task: Record<string, unknown> | null
|
|
|
369
367
|
* alone, its incomplete key still let a dependant hit stale.
|
|
370
368
|
*/
|
|
371
369
|
export function unkey(task: Record<string, unknown>): void {
|
|
372
|
-
|
|
370
|
+
uncache(task)
|
|
373
371
|
delete task['inputs']
|
|
374
372
|
}
|
|
375
373
|
|
|
374
|
+
/** No cache: `outputs` and `inputs.stability` go with it, as they mean nothing without. */
|
|
375
|
+
export function uncache(task: Record<string, unknown>): void {
|
|
376
|
+
delete task['cache']
|
|
377
|
+
delete task['outputs']
|
|
378
|
+
const inputs = task['inputs'] as Record<string, unknown> | undefined
|
|
379
|
+
if (inputs !== undefined) delete inputs['stability']
|
|
380
|
+
}
|
|
381
|
+
|
|
376
382
|
/** A task's `inputs` block, when it has one with a `files` list. */
|
|
377
383
|
function inputsOf(t: GeneratedTask): { files: string[]; workspaceFiles?: unknown } | undefined {
|
|
378
384
|
const inputs = t.task?.['inputs'] as { files?: unknown } | undefined
|
|
@@ -457,11 +463,11 @@ export function resolveSharedWorkspaceOutputs(
|
|
|
457
463
|
for (const p of projects) {
|
|
458
464
|
const rel = relPosix(root, p.dir)
|
|
459
465
|
for (const t of p.tasks) {
|
|
460
|
-
const
|
|
461
|
-
| {
|
|
466
|
+
const outputs = t.task?.['outputs'] as
|
|
467
|
+
| { files?: unknown; workspaceFiles?: unknown }
|
|
462
468
|
| undefined
|
|
463
|
-
const ws = positive(
|
|
464
|
-
const own = positive(
|
|
469
|
+
const ws = positive(outputs?.workspaceFiles)
|
|
470
|
+
const own = positive(outputs?.files).map((g) =>
|
|
465
471
|
rel === '' ? g.replace(/^(\.\/)+/, '') : `${rel}/${g.replace(/^(\.\/)+/, '')}`,
|
|
466
472
|
)
|
|
467
473
|
if (ws.length === 0 && own.length === 0) continue
|
|
@@ -485,7 +491,7 @@ export function resolveSharedWorkspaceOutputs(
|
|
|
485
491
|
kept.push({ id: `${p.name}#${t.name}`, project: p.name, ws, own })
|
|
486
492
|
continue
|
|
487
493
|
}
|
|
488
|
-
|
|
494
|
+
uncache(t.task!)
|
|
489
495
|
t.todos.push(
|
|
490
496
|
`declares the workspace output ${JSON.stringify(shared)} that ${clash.id} also declares — ` +
|
|
491
497
|
"vx cleans a task's outputs before it runs and before a restore, so two cached tasks " +
|
|
@@ -525,9 +531,9 @@ export function excludeWorkspaceOutputs(
|
|
|
525
531
|
for (const p of projects) {
|
|
526
532
|
const rel = relPosix(root, p.dir)
|
|
527
533
|
for (const t of p.tasks) {
|
|
528
|
-
const outputs =
|
|
529
|
-
|
|
530
|
-
|
|
534
|
+
const outputs = t.task?.['outputs'] as
|
|
535
|
+
| { files?: unknown; workspaceFiles?: unknown }
|
|
536
|
+
| undefined
|
|
531
537
|
const globs = [
|
|
532
538
|
...positive(outputs?.workspaceFiles),
|
|
533
539
|
...positive(outputs?.files).map((g) =>
|
package/src/tracked-outputs.ts
CHANGED
|
@@ -266,7 +266,7 @@ export function spareTrackedOutputs(
|
|
|
266
266
|
const rel = relPosix(root, p.dir)
|
|
267
267
|
const own = rel === '' ? tracked : filesUnder(tracked, rel).map((f) => f.slice(rel.length + 1))
|
|
268
268
|
for (const t of p.tasks) {
|
|
269
|
-
const outputs =
|
|
269
|
+
const outputs = t.task?.['outputs'] as Outputs | undefined
|
|
270
270
|
if (outputs === undefined) continue
|
|
271
271
|
const hits: [key: 'files' | 'workspaceFiles', hit: string[]][] = []
|
|
272
272
|
for (const [key, base] of [
|
package/src/turbo/turbo-map.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
resolveSharedOutputs,
|
|
25
25
|
resolveSharedWorkspaceOutputs,
|
|
26
26
|
takingBack,
|
|
27
|
+
uncache,
|
|
27
28
|
unkey,
|
|
28
29
|
wildcardCleanTodo,
|
|
29
30
|
wildcardOutput,
|
|
@@ -1606,7 +1607,7 @@ function sortOrderFree(projects: readonly TurboMappedProject[]): void {
|
|
|
1606
1607
|
const task = t.task
|
|
1607
1608
|
if (task === null) continue
|
|
1608
1609
|
if (Array.isArray(task['dependsOn'])) task['dependsOn'] = [...task['dependsOn']].sort(byText)
|
|
1609
|
-
const outputs =
|
|
1610
|
+
const outputs = task['outputs'] as Record<string, unknown> | undefined
|
|
1610
1611
|
if (outputs === undefined) continue
|
|
1611
1612
|
for (const k of ['files', 'workspaceFiles'])
|
|
1612
1613
|
if (Array.isArray(outputs[k])) outputs[k] = [...outputs[k]].sort(negationsLast)
|
|
@@ -1822,16 +1823,12 @@ function keyOnlyGroup(t: TurboMappedTask): TurboMappedTask {
|
|
|
1822
1823
|
if (task === null) return t
|
|
1823
1824
|
const exec = task['exec'] as Record<string, unknown>
|
|
1824
1825
|
if (exec['persistent'] !== undefined || exec['interactive'] !== undefined) return t
|
|
1825
|
-
const
|
|
1826
|
-
| { outputs: { files: unknown[]; workspaceFiles?: unknown[] } }
|
|
1827
|
-
| undefined
|
|
1826
|
+
const outputs = task['outputs'] as { files: unknown[]; workspaceFiles?: unknown[] } | undefined
|
|
1828
1827
|
if (task['inputs'] === undefined && task['dependsOn'] === undefined) return t
|
|
1829
|
-
if (
|
|
1830
|
-
cache !== undefined &&
|
|
1831
|
-
(cache.outputs.files.length > 0 || cache.outputs.workspaceFiles !== undefined)
|
|
1832
|
-
)
|
|
1828
|
+
if (outputs !== undefined && (outputs.files.length > 0 || outputs.workspaceFiles !== undefined))
|
|
1833
1829
|
return t
|
|
1834
|
-
const { exec: _exec,
|
|
1830
|
+
const { exec: _exec, ...group } = task
|
|
1831
|
+
uncache(group)
|
|
1835
1832
|
return { ...t, task: group }
|
|
1836
1833
|
}
|
|
1837
1834
|
|
|
@@ -2216,7 +2213,7 @@ function buildTask(
|
|
|
2216
2213
|
} else if (o.includes('$TURBO_ROOT$')) {
|
|
2217
2214
|
todos.push(
|
|
2218
2215
|
`output ${JSON.stringify(o)}: $TURBO_ROOT$ only maps as a '$TURBO_ROOT$/<path>' ` +
|
|
2219
|
-
'prefix (→
|
|
2216
|
+
'prefix (→ outputs.workspaceFiles) — map manually',
|
|
2220
2217
|
)
|
|
2221
2218
|
} else outFiles.push(...signed(o))
|
|
2222
2219
|
}
|
|
@@ -2255,9 +2252,13 @@ function buildTask(
|
|
|
2255
2252
|
const outputs: Record<string, unknown> = { files: takingBack(outFiles).sort(negationsLast) }
|
|
2256
2253
|
const ws = takingBack(wsOutFiles).sort(negationsLast)
|
|
2257
2254
|
if (ws.length > 0) outputs.workspaceFiles = ws
|
|
2258
|
-
task.
|
|
2259
|
-
|
|
2260
|
-
|
|
2255
|
+
task.outputs = outputs
|
|
2256
|
+
task.cache = true
|
|
2257
|
+
if (rewritesManifest) {
|
|
2258
|
+
task.inputs = {
|
|
2259
|
+
...(task.inputs as Record<string, unknown>),
|
|
2260
|
+
stability: { strategy: 'mutable' },
|
|
2261
|
+
}
|
|
2261
2262
|
}
|
|
2262
2263
|
|
|
2263
2264
|
return { name, todos, task, uses }
|
|
@@ -396,7 +396,7 @@ function autoTodo(what: string, known?: Record<string, unknown>): string {
|
|
|
396
396
|
const have = known === undefined ? '' : ` (declared so far: \`${JSON.stringify(known)}\`)`
|
|
397
397
|
return (
|
|
398
398
|
`cache: Vite Task traced this task's ${what} and vx infers none — add ` +
|
|
399
|
-
`\`inputs: { files: [...] },
|
|
399
|
+
`\`inputs: { files: [...] }, outputs: { files: [...] }, cache: true\` with the real ones${have}; ` +
|
|
400
400
|
'without it the task always runs'
|
|
401
401
|
)
|
|
402
402
|
}
|
|
@@ -481,7 +481,8 @@ function mapTask(name: string, def: TaskDef, ctx: TaskCtx): GeneratedTask {
|
|
|
481
481
|
todos.push(ownFileTodo(ownFile))
|
|
482
482
|
return { name, todos, task }
|
|
483
483
|
}
|
|
484
|
-
task['
|
|
484
|
+
task['outputs'] = outputs.lists
|
|
485
|
+
task['cache'] = true
|
|
485
486
|
return { name, todos, task }
|
|
486
487
|
}
|
|
487
488
|
|