Skip to content

LacusSolutions/workspace-version-resolver-js

workspace-version-resolver

NPM Latest Version Downloads Count Project License

A small Node.js library and CLI that resolves workspace protocol versions in a monorepo. It reads each workspace package’s package.json, finds dependencies that use the workspace: protocol (e.g. workspace:*, workspace:^), replaces them with the actual package versions, and writes the changes back without reordering or reformatting the rest of the file.

Useful for pre-publish steps or tooling that needs concrete versions instead of workspace:* in dependency maps.


Table of contents


Installation

npm install workspace-version-resolver
# or
pnpm add workspace-version-resolver
# or
bun add workspace-version-resolver

Quick start

CLI — run from the monorepo root (or pass a path). The binary is also available under the alias wvr:

npx workspace-version-resolver
# or
npx workspace-version-resolver /path/to/monorepo

API — call the default export with the root directory:

import resolveWorkspaceVersions from 'workspace-version-resolver';

await resolveWorkspaceVersions('/path/to/monorepo');

CLI

The package ships a binary: workspace-version-resolver (alias wvr). Run via npx, bunx, or after npm install -g.

Usage

workspace-version-resolver [path]
  • [path] — Optional path to the monorepo root. If omitted, the current working directory is used.

Options

Option Alias Type Default Description
--warn-on-circular boolean true When true, print a warning for each circular workspace dependency. Use --no-warn-on-circular to disable.
--help -h boolean Show help and exit.
--version -v boolean Show package version and exit.

Examples

# Use current directory as monorepo root
npx workspace-version-resolver

# Explicit path
npx workspace-version-resolver path/to/monorepo/

# Disable circular dependency warnings
npx workspace-version-resolver --no-warn-on-circular

# Help and version
npx workspace-version-resolver --help
npx workspace-version-resolver --version

On validation or runtime errors, the process exits with code 1 and prints the error message to stderr.


Programmatic API

The package exports a default function and the individual steps + types so you can reuse or test them.

Default export: resolveWorkspaceVersions

Runs the full pipeline: validate root → resolve workspace paths → read manifests → build graph → topological sort → resolve versions → write package.json files.

function resolveWorkspaceVersions(
  rootDir: string,
  options?: ResolveWorkspaceVersionsOptions
): Promise<void>
  • rootDir — Absolute or relative path to the monorepo root (directory that contains a package.json with a "workspaces" array).
  • options — Optional:
    • warnOnCircular?: boolean — If true (default), console.warn is used when circular workspace dependencies are detected. Does not throw.

Example

import resolveWorkspaceVersions from 'workspace-version-resolver';

await resolveWorkspaceVersions(process.cwd(), { warnOnCircular: true });

Options type

interface ResolveWorkspaceVersionsOptions {
  /** If true (default), emit console.warn when circular dependencies are detected. */
  warnOnCircular?: boolean;
}

Named exports (modular steps)

You can call each step yourself for custom workflows or tests.

Export Description
validateRoot(rootDir: string) Ensures rootDir is a directory and has a package.json with a "workspaces" array of strings. Returns { workspaces: string[] }. Throws on invalid root.
resolveWorkspacePaths(rootPath, workspaces) Expands workspace globs (e.g. packages/*) to absolute directory paths and keeps only directories that contain a package.json. Returns string[].
readManifests(dirPaths) Reads each directory’s package.json, extracts name, version, dependencies, devDependencies, peerDependencies, and workspace-only deps. Returns PackageInfo[]. Throws on duplicate package names.
buildDependencyGraph(packages) Builds a directed graph of workspace packages (by name, edges = “depends on”). Returns DependencyGraph.
topologicalSort(graph) Returns a topological order (packages with no workspace deps first) and any cycles found. Returns TopoResult ({ ordered: string[], cycles: string[][] }). Does not throw on cycles.
resolveVersions(ordered, packages) Resolves each workspace specifier to the depended-on package’s version. Returns ResolvedUpdates ({ byPackage: Map<string, Map<string, string>> }).
resolveVersionSpec(specifier, targetVersion) Resolves a single specifier string (e.g. "workspace:*") given the target package version. Returns the resolved version string.
writePackageJson(dirPath, updates, workspaceDeps) Writes back only the resolved version values into the given directory’s package.json, preserving formatting and key order.

Types

type DependencyMap = Record<string, string>;

interface PackageInfo {
  dirPath: string;
  name: string;
  version: string;
  dependencies: DependencyMap;
  devDependencies: DependencyMap;
  peerDependencies: DependencyMap;
  workspaceDeps: DependencyMap;  // workspace: entries only
}

type DependencyGraph = Map<string, Set<string>>;

interface TopoResult {
  ordered: string[];
  cycles: string[][];
}

Example: custom pipeline

import path from 'node:path';
import {
  validateRoot,
  resolveWorkspacePaths,
  readManifests,
  buildDependencyGraph,
  topologicalSort,
  resolveVersions,
  writePackageJson,
} from 'workspace-version-resolver';

const rootDir = path.resolve('./my-monorepo');
const { workspaces } = validateRoot(rootDir);
const dirPaths = resolveWorkspacePaths(rootDir, workspaces);
const packages = readManifests(dirPaths);
const graph = buildDependencyGraph(packages);
const { ordered, cycles } = topologicalSort(graph);
if (cycles.length > 0) console.warn('Cycles:', cycles);
const { byPackage } = resolveVersions(ordered, packages);
for (const pkg of packages) {
  const updates = byPackage.get(pkg.dirPath);
  if (updates?.size) writePackageJson(pkg.dirPath, updates, pkg.workspaceDeps);
}

How it works

  1. Validate root — Checks that rootDir is a directory and that its package.json has a "workspaces" array of strings.
  2. Resolve workspace paths — Expands each workspace entry (e.g. packages/*) to absolute directory paths and keeps only directories that contain a package.json.
  3. Read manifests — Reads each package’s package.json and extracts name, version, dependencies, devDependencies, and peerDependencies. Duplicate package names cause an error.
  4. Workspace deps — From each manifest, only entries whose value starts with workspace: are considered (e.g. workspace:*, workspace:^, workspace:~, workspace:1.2.3).
  5. Dependency graph — Builds a directed graph of packages based only on workspace dependencies.
  6. Topological sort — Orders packages so that those with no workspace deps come first, then their dependents. Cycles are detected and reported (warning only, no throw).
  7. Resolve versions — For each workspace dependency, computes the resolved version string from the depended-on package’s version and the specifier (see Version resolution rules).
  8. Write back — Updates each package.json by replacing only the workspace:... values with the resolved versions, using a surgical string replace so that key order and formatting are preserved.

Version resolution rules

For each dependency that uses the workspace: protocol, the resolved value is derived from the depended-on package’s version and the specifier:

Specifier Resolved value Example (target version 1.2.3)
workspace:* Exact version 1.2.3
workspace:^ Caret + version ^1.2.3
workspace:~ Tilde + version ~1.2.3
workspace:1.2.3 Strip workspace: 1.2.3
workspace:^1.2.3 Strip workspace: ^1.2.3
workspace:~1.0.0 Strip workspace: ~1.0.0

Only dependency entries whose value is a string starting with workspace: are modified; all other keys and values are left unchanged.


Requirements and behavior

  • Root must be a directory whose package.json contains a "workspaces" property set to an array of strings (e.g. ["packages/*"]).
  • Workspace entries are resolved relative to the root and expanded (e.g. globs). Only directories that contain a package.json are included.
  • Duplicate package names (same name in more than one workspace package) cause the run to throw (or the CLI to exit with code 1).
  • Circular workspace dependencies are detected and reported with a warning only; the run continues and versions are still resolved where possible.
  • File writes change only the version strings for workspace-resolved dependencies; property order and formatting (whitespace, newlines) are preserved.

License

MIT. See LICENSE in the repository.


Made with ❤️ by Lacus Solutions

About

A JavaScript/TypeScript package to resolve monorepo internal dependencies using "workspace" protocol in `package.json`.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors