workspace-version-resolver
v0.1.0
Published
A JavaScript/TypeScript package to resolve monorepo internal dependencies using workspace protocol
Maintainers
Readme
workspace-version-resolver
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
- Quick start
- CLI
- Programmatic API
- How it works
- Version resolution rules
- Requirements and behavior
- License
Installation
npm install workspace-version-resolver
# or
pnpm add workspace-version-resolver
# or
bun add workspace-version-resolverQuick 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/monorepoAPI — 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 --versionOn 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 apackage.jsonwith a"workspaces"array).options— Optional:warnOnCircular?: boolean— Iftrue(default),console.warnis 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
- Validate root — Checks that
rootDiris a directory and that itspackage.jsonhas a"workspaces"array of strings. - Resolve workspace paths — Expands each workspace entry (e.g.
packages/*) to absolute directory paths and keeps only directories that contain apackage.json. - Read manifests — Reads each package’s
package.jsonand extractsname,version,dependencies,devDependencies, andpeerDependencies. Duplicate package names cause an error. - Workspace deps — From each manifest, only entries whose value starts with
workspace:are considered (e.g.workspace:*,workspace:^,workspace:~,workspace:1.2.3). - Dependency graph — Builds a directed graph of packages based only on workspace dependencies.
- 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).
- 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).
- Write back — Updates each
package.jsonby replacing only theworkspace:...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.jsoncontains 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.jsonare included. - Duplicate package names (same
namein 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
