Create a graph from an array of packages in workspace environments
npx @tessl/cli install tessl/npm-pnpm--workspace-pkgs-graph@1000.0.0@pnpm/workspace.pkgs-graph creates dependency graphs from arrays of workspace packages in monorepo environments. It analyzes package manifests and their dependencies to build directed graph structures that represent relationships between packages, supporting various dependency types and workspace-specific dependency references.
pnpm add @pnpm/workspace.pkgs-graphimport { createPkgGraph, type Package, type PackageNode } from "@pnpm/workspace.pkgs-graph";For CommonJS:
const { createPkgGraph } = require("@pnpm/workspace.pkgs-graph");import { createPkgGraph } from "@pnpm/workspace.pkgs-graph";
const packages = [
{
rootDir: "/workspace/foo",
manifest: {
name: "foo",
version: "1.0.0",
dependencies: {
bar: "^1.0.0",
},
},
},
{
rootDir: "/workspace/bar",
manifest: {
name: "bar",
version: "1.1.0",
},
}
];
const { graph, unmatched } = createPkgGraph(packages);
console.log(graph);
// {
// "/workspace/foo": {
// dependencies: ["/workspace/bar"],
// package: { rootDir: "/workspace/foo", manifest: { ... } }
// },
// "/workspace/bar": {
// dependencies: [],
// package: { rootDir: "/workspace/bar", manifest: { ... } }
// }
// }
console.log(unmatched);
// [] - empty array means all dependencies were matchedThe package is built around several key concepts:
workspace: protocol dependencies with version range resolutionCreates a dependency graph from an array of packages with configurable options for dependency processing.
/**
* Creates a dependency graph from an array of packages
* @param pkgs - Array of packages to analyze
* @param opts - Optional configuration for graph creation
* @returns Object containing the dependency graph and unmatched dependencies
*/
function createPkgGraph<Pkg extends Package>(
pkgs: Pkg[],
opts?: {
ignoreDevDeps?: boolean;
linkWorkspacePackages?: boolean;
}
): {
graph: Record<ProjectRootDir, PackageNode<Pkg>>;
unmatched: Array<{ pkgName: string; range: string }>;
};Usage Examples:
// Basic graph creation
const result = createPkgGraph(packages);
// Ignore dev dependencies
const prodGraph = createPkgGraph(packages, {
ignoreDevDeps: true
});
// Disable workspace package linking
const strictGraph = createPkgGraph(packages, {
linkWorkspacePackages: false
});The library fully supports pnpm's workspace protocol syntax for referencing workspace packages:
// Supported workspace dependency formats
const packagesWithWorkspaceDeps = [
{
rootDir: "/workspace/app",
manifest: {
name: "app",
version: "1.0.0",
dependencies: {
"utils": "workspace:^1.0.0", // Version range
"config": "workspace:*", // Any version
"shared": "workspace:~", // Compatible version
"ui-alias": "workspace:ui@*", // Aliased package reference
},
},
}
];Handles local directory dependencies using relative paths and file: protocol:
const packagesWithLocalDeps = [
{
rootDir: "/workspace/app",
manifest: {
name: "app",
dependencies: {
"local-utils": "../utils", // Relative path
"shared-lib": "file:../shared", // File protocol
},
},
}
];/**
* Represents a package with manifest and root directory
*/
interface Package {
/** Package manifest containing metadata and dependencies */
manifest: BaseManifest;
/** Absolute path to the package root directory */
rootDir: ProjectRootDir;
}
/**
* Represents a node in the dependency graph
*/
interface PackageNode<Pkg extends Package> {
/** The package data */
package: Pkg;
/** Array of root directories of packages this package depends on */
dependencies: ProjectRootDir[];
}
/**
* Project root directory path (from @pnpm/types)
*/
type ProjectRootDir = string;
/**
* Base manifest interface (from @pnpm/types)
* Contains standard package.json fields including:
* - name: string
* - version: string
* - dependencies: Record<string, string>
* - devDependencies: Record<string, string>
* - peerDependencies: Record<string, string>
* - optionalDependencies: Record<string, string>
*/
interface BaseManifest {
name?: string;
version?: string;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
}When set to true, development dependencies are excluded from the dependency graph:
const result = createPkgGraph(packages, { ignoreDevDeps: true });
// Only includes dependencies, peerDependencies, and optionalDependenciesControls whether non-workspace dependencies should be linked to workspace packages. When set to false, only explicit workspace: dependencies are linked:
const result = createPkgGraph(packages, { linkWorkspacePackages: false });
// Regular version ranges won't match workspace packages
// Only workspace: prefixed dependencies will be linkedThe graph object maps each package's root directory to its PackageNode:
const { graph } = createPkgGraph(packages);
// Access specific package node
const fooNode = graph["/workspace/foo"];
console.log(fooNode.dependencies); // Array of dependency root dirs
console.log(fooNode.package); // Original package dataThe unmatched array contains dependencies that couldn't be resolved to workspace packages:
const { unmatched } = createPkgGraph(packages);
unmatched.forEach(({ pkgName, range }) => {
console.log(`Could not resolve ${pkgName}@${range} to workspace package`);
});Common reasons for unmatched dependencies:
The library gracefully handles packages without version fields:
const packagesWithoutVersions = [
{
rootDir: "/workspace/utils",
manifest: {
name: "utils",
// No version field
},
}
];
// Still creates valid graph structure
const result = createPkgGraph(packagesWithoutVersions);Malformed or invalid dependency specifications are silently ignored:
const packagesWithInvalidDeps = [
{
rootDir: "/workspace/app",
manifest: {
name: "app",
dependencies: {
"weird-dep": ":aaaaa", // Invalid spec - ignored
"valid-dep": "^1.0.0",
},
},
}
];The library handles case mismatches on case-insensitive filesystems by using both fast and slow path resolution strategies.
Correctly matches prerelease versions when using wildcard ranges:
const prereleasePackages = [
{
rootDir: "/workspace/beta",
manifest: {
name: "beta",
version: "1.0.0-beta.1", // Prerelease version
},
}
];
// "*" range will match prerelease versions
const result = createPkgGraph([
{
rootDir: "/workspace/app",
manifest: {
dependencies: { "beta": "*" }
}
},
...prereleasePackages
]);