Skip to content
vxvx
GitHubBlueskydev.toRSS

vx init

vx init sets up vx in a repository: it installs @vzn/vx, writes .vx/workspace.ts and a root vx.config.ts, and writes the lock. In a Turborepo or Nx repo it maps their config instead.

vx init [--dry [--format pretty|json]] [--force] [--mjs] [--native|--keep] [--plugin <seam>]
$ vx init --dry
─ vx init 0.0.0 ──────────────────────────────────────────────
from no turbo.json or nx.json: one project, the root
dry run nothing written
files .vx/workspace.ts
vx.config.ts
.vx/.gitignore
.vx/.gitattributes
next declare a task in vx.config.ts, then vx lock && vx run <task>
Option Meaning
--dry print the files; write nothing
--format json with --dry, the plan as one document
--force replace a file already there
--mjs write .mjs files instead of .ts
--native, --keep in a Turbo or Nx repo: a vx.config.ts per package, or keep their config
--plugin <seam> write a plugin for that seam instead, below

vx init first installs @vzn/vx globally (the package manager that ran it, else npm; skipped when the vx on PATH is already that version) and, under the topmost package.json, as a root devDependency with that repo’s own manager (M, 2026-10-20). A failed install stops init.

When the directory it runs in, or one above it up to the git top-level, holds a turbo.json, turbo.jsonc or nx.json, it runs @vzn/vx-migrate there, so vx init is the one command (owner, 2026-10-08): the latest release through bun x, never an installed one, so init always installs the latest @vzn/vx and moves every @vzn/vx-* plugin the root lists to it (M, 2026-10-16; a source checkout runs the nearest installed node_modules/@vzn/vx-migrate instead) (BUN_BE_BUN=1 makes the compiled binary that runtime). --dry, --format, --force, --mjs, --native and --keep pass through, and the exit is vx-migrate’s. vx-migrate maps the Turbo or Nx repo, one vx.config.ts per package; writes the workspace file with the plugins the repo calls for (the lockfile’s, a remote cache the repo shows); installs them; and ends with vx lock. Its README holds the rules; bunx @vzn/vx-migrate below sums them up.

With none of those files, it writes a workspace whose one project is the root, at the nearest workspace root up from the working directory, else at the topmost package.json, else there:

.vx/workspace.ts
import { defineWorkspace } from '@vzn/vx'
export default defineWorkspace({ plugins: [] })
vx.config.ts
import { defineProject } from '@vzn/vx'
export default defineProject({ tasks: {} })

Beside a lockfile at that package.json (pnpm-lock.yaml, bun.lock, yarn.lock, package-lock.json), the workspace declares its @vzn/vx-lockfile plugin (plugins: [bun()]), installed beside @vzn/vx, so a package’s ^build has project edges.

plus .vx/.gitignore and .vx/.gitattributes. It then runs vx lock’s step, and next: says to declare a task and run vx lock && vx run &lt;task&gt;, preceded by git init outside a git work tree. The @vzn/vx import needs no install: the running vx serves its own core to that specifier (bin.ts registers the alias). A file already there is kept and named under kept; --force replaces it, a file of another extension included (replaced). --mjs writes .vx/workspace.mjs and vx.config.mjs. --dry prints the files and writes nothing; --dry --format json prints the plan as one JSON document (schemas/init.json): files (each path and its whole text), kept, replaced, todos, notes, next and installs (the install commands the real run runs). --native and --keep answer a Turbo or Nx repo’s question, so they are refused here. --format json without --dry, or with --plugin, is refused (VX_E_USAGE).

vx init --plugin <seam> writes a plugin instead: plugins/<seam>.ts, a small runnable plugin for that seam (executor, cache, telemetry, schedule, admit, commands, project, graph, key), and plugins/<seam>.test.ts, which drives it through run() (bun test, with @vzn/vx installed). It prints the line that declares it in the workspace file a run reads (.vx/workspace.ts when there is none). The templates are TypeScript, so --mjs with --plugin is refused. An existing file is refused without --force; --dry names the files and writes nothing. The two files are private/vx-plugin-examples/plugins, which the gate runs, copied into core (src/cli/plugin-templates.ts) and held equal by tests/plugin-templates.unsafe.test.ts (H-21).

Code When
0 the files written (or, with --dry, printed)
1 a refused flag, a parse error (--mjs with --plugin among them), or a plugin file already there without --force. Handed to @vzn/vx-migrate, the exit is its own

Mapping a repo’s own config to vx is @vzn/vx-migrate, its own package, run before a workspace file exists (it writes one); vx init runs it in any repo with a package.json —

bunx @vzn/vx-migrate # turbo.json, an Nx graph, vite-plus run.tasks, else package.json scripts → vx.config.ts (vx init hands it only Turbo and Nx)
bunx @vzn/vx-migrate --dry # print the generated files instead of writing
bunx @vzn/vx-migrate --force # overwrite existing vx.config.* / vx-preset.ts
bunx @vzn/vx-migrate --from nx # disambiguate when several sources are checked in (turbo, nx, vite-task, scripts)

— In a Turbo or Nx repo it asks a terminal native (a vx.config.ts per package, the default) or keep (a workspace file declaring turbo() or nx(), which read the repo’s config live); --native or --keep answer it, and without a terminal it is native. Both declare turboCache() / nxCache() when the repo shows a remote cache (turbo.json’s remoteCache, a turbo linked .turbo/config.json, or a CI file setting TURBO_TOKEN / NX_SELF_HOSTED_REMOTE_CACHE_SERVER), and name Nx Cloud, whose wire vx does not speak. Elsewhere each package’s package.json scripts become its tasks, and the workspace file declares the @vzn/vx-lockfile plugin for the root’s lockfile. What the package writes goes through core’s migration seam (applyMigration, exported from @vzn/vx), which renders, guards against overwriting, writes and reports. The mapping rules live in the package’s README.

Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.