Scoped Releases: New version, pack, and publish commands

Release subsets of your monorepo with the new version, pack, and publish commands using powerful selectors and a publish directory.

Scoped Releases: Release with precision using scoped version, pack, and publish commands
Luke Karrys

Luke Karrys

vltfeaturesclient

Manage releases across workspaces with --scope: version, pack, publish

Releasing changes in a growing project or monorepo shouldn't require bespoke scripts. vlt now supports graph-aware release operations with version, vlt pack, and vlt publish. Each command supports the full Dependency Selector Syntax via --scope. You can bump versions, create tarballs, and publish only the workspaces you intend. We also added --publish-directory to both pack and publish so you can bundle into a directory (e.g. dist/) before packaging or publishing.

tl;dr

  • vlt version <inc> with --scope="<selector>" bumps the version in matched packages.
  • vlt pack --scope="<selector>" creates tarballs in each matched package.
  • vlt publish --scope="<selector>" publishes matched workspaces.
  • vlt pack and vlt publish use --publish-directory=<path> to publish from a different output directory.
  • --scope accepts the full selector syntax and works great with workspaces.

Why this is useful

  • Target only what matters: release a subset of workspaces (e.g., apps, services) without touching the rest.
  • Bundle first, then ship: build to a different directory during prepack, then pack/publish only the artifacts using --publish-directory.
  • Safer, repeatable flows: version, then pack to verify artifacts, then publish. All of this is driven by selectors.

How it works

vlt evaluates the selector passed with --scope against your project's dependency graph. For version, pack, and publish, matched items are typically your configured workspaces. version applies the increment to each matched workspace. pack produces npm-compatible tarballs, and publish sends packages to your registry. Both pack and publish respect --publish-directory to run from a different working directory.

For the complete selector reference, see the docs: Dependency Selector Syntax.

Examples

Version a package by name and all its workspace dependencies

$ vlt version patch --scope="#package-a, #package-a :workspace"
package-a: v1.4.1
package-b: v1.2.1 # dependent on package-a
package-c: v3.2.1 # dependent on package-b

Version a subset of workspaces that start with app-

$ vlt version patch --scope=":workspace[name^=app-]"
app-web: v1.2.4
app-admin: v0.9.1

Version prereleases for apps found in ./apps

$ vlt version prerelease --scope=':path("apps/*")'
apps/web: v1.4.1-pre.0
apps/api: v2.8.4-pre.0
apps/admin: v0.9.1-pre.0

Version and pack workspaces by name

Use this flow to validate artifacts before publishing.

$ vlt version minor --scope=':workspace:is(#web, #api)'
web: v1.4.0
api: v2.9.0

$ vlt pack --scope=':workspace:is(#web, #api)'
📦 Package: web@1.4.0
📄 File: web-1.4.0.tgz
📁 52 Files
... (truncated)
📊 Package Size: 1.2 MB
📂 Unpacked Size: 1.1 MB
🔒 Shasum: ...
🔐 Integrity: ...

📦 Package: api@2.9.0
📄 File: api-2.9.0.tgz
📁 108 Files
... (truncated)
📊 Package Size: 2.9 MB
📂 Unpacked Size: 2.8 MB
🔒 Shasum: ...
🔐 Integrity: ...

Version and publish all public workspaces

$ vlt version patch ":workspace:not(:private)"
app-web: v1.4.1
app-server: v2.9.1

$ vlt publish ":workspace:not(:private)" --tag=latest
📦 Package: app-web@1.4.1
🏷️ Tag: latest
📡 Registry: https://registry.npmjs.org/
📁 52 Files
... (truncated)
📊 Package Size: 1.2 MB
📂 Unpacked Size: 1.1 MB
🔒 Shasum: ...
🔐 Integrity: ...

📦 Package: app-server@2.9.1
📄 File: app-server-2.9.1.tgz
📁 108 Files
... (truncated)
📊 Package Size: 2.9 MB
📂 Unpacked Size: 2.8 MB
🔒 Shasum: ...
🔐 Integrity: ...

Build to --publish-directory during prepack and then pack and publish

Bundle to dist/ during prepack, then package/publish from that directory. This allows you to publish a set of files that is completely different than what is in the source directory. This is helpful when bundling or compiling your project before publishing.

package.json:

{
  "name": "app-web",
  "version": "1.4.1",
  "private": false,
  "scripts": {
    "prepack": "vite build && cp package.json dist/ && cp README.md dist/"
  }
}

Pack from dist/:

$ vlt pack --scope="#app-web" --publish-directory=dist

Publish from dist/:

$ vlt publish --scope="#app-web" --publish-directory=dist --tag=latest

Publish all packages that have a prepack script

You can combine this with selectors to publish many packages that follow the same build layout.

$ vlt publish --scope=':workspace:path("packages/*"):attr(scripts, [prepack])' --publish-directory=dist --tag=latest
📦 Package: @org/foo@0.7.0
📄 File: @org/foo-0.7.0.tgz
📁 52 Files
... (truncated)
📊 Package Size: 1.2 MB
📂 Unpacked Size: 1.1 MB
🔒 Shasum: ...
🔐 Integrity: ...

📦 Package: @org/bar@3.2.1
📄 File: @org/bar-3.2.1.tgz
📁 108 Files
... (truncated)
📊 Package Size: 2.9 MB
📂 Unpacked Size: 2.8 MB
🔒 Shasum: ...
🔐 Integrity: ...

Learn more

Have questions or feedback about --scope, --publish-directory, or releasing with vlt? Join us on Discord or open an issue on GitHub.