You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@vltpkg/workspaces

Package Overview
Dependencies
Maintainers
6
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vltpkg/workspaces - npm Package Compare versions

Comparing version
1.0.0-rc.18
to
1.0.0-rc.22
+28
-24
package.json
{
"name": "@vltpkg/workspaces",
"description": "Utility for working with vlt workspaces",
"version": "1.0.0-rc.18",
"version": "1.0.0-rc.22",
"repository": {

@@ -10,13 +10,16 @@ "type": "git",

},
"author": "vlt technology inc. <support@vlt.sh> (http://vlt.sh)",
"author": {
"name": "vlt technology inc.",
"email": "support@vlt.sh"
},
"dependencies": {
"@vltpkg/dep-id": "1.0.0-rc.22",
"@vltpkg/error-cause": "1.0.0-rc.22",
"@vltpkg/graph-run": "1.0.0-rc.22",
"@vltpkg/package-json": "1.0.0-rc.22",
"@vltpkg/types": "1.0.0-rc.22",
"@vltpkg/vlt-json": "1.0.0-rc.22",
"glob": "^13.0.0",
"minimatch": "^10.1.1",
"path-scurry": "^2.0.1",
"@vltpkg/dep-id": "1.0.0-rc.18",
"@vltpkg/graph-run": "1.0.0-rc.18",
"@vltpkg/error-cause": "1.0.0-rc.18",
"@vltpkg/package-json": "1.0.0-rc.18",
"@vltpkg/types": "1.0.0-rc.18",
"@vltpkg/vlt-json": "1.0.0-rc.18"
"path-scurry": "^2.0.1"
},

@@ -35,4 +38,15 @@ "devDependencies": {

"engines": {
"node": ">=22.9.0"
"node": ">=22.22.0"
},
"scripts": {
"format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache",
"format:check": "prettier --check . --ignore-path ../../.prettierignore --cache",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"prepack": "tsc -p tsconfig.publish.json && ../../scripts/update-dist-exports.ts",
"snap": "tap",
"test": "tap",
"posttest": "tsc --noEmit",
"typecheck": "tsc --noEmit"
},
"tap": {

@@ -42,3 +56,3 @@ "extends": "../../tap-config.yaml"

"prettier": "../../.prettierrc.js",
"module": "./dist/index.js",
"module": "./src/index.ts",
"type": "module",

@@ -49,3 +63,3 @@ "exports": {

"import": {
"default": "./dist/index.js"
"default": "./src/index.ts"
}

@@ -56,13 +70,3 @@ }

"dist"
],
"scripts": {
"format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache",
"format:check": "prettier --check . --ignore-path ../../.prettierignore --cache",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"snap": "tap",
"test": "tap",
"posttest": "tsc --noEmit",
"typecheck": "tsc --noEmit"
}
}
]
}
import type { DepID } from '@vltpkg/dep-id';
import { PackageJson } from '@vltpkg/package-json';
import type { NormalizedManifest } from '@vltpkg/types';
import type { DepResults } from '@vltpkg/graph-run';
import { PathScurry } from 'path-scurry';
export type WorkspacesLoadedConfig = {
workspace?: string[];
'workspace-group'?: string[];
};
/**
* The object passed to the constructor or {@link Monorepo#load} to limit which
* {@link Workspace Workspaces} get loaded.
*/
export type LoadQuery = {
/**
* A glob pattern string, or an array of them. Only workspaces found
* in paths matched will be loaded.
*/
paths?: string[] | string;
/**
* A string, or an array of strings. If set, only workspaces in the
* specified groups named will be included, if set.
*/
groups?: string[] | string;
};
/**
* Canonical form of the {@link WorkspaceConfig}, used
* internally for consistency.
*/
export type WorkspaceConfigObject = Record<string, string[]>;
/**
* Allowed datatype in the `workspaces` field of the `vlt.json` file.
*/
export type WorkspaceConfig = string[] | WorkspaceConfigObject | string;
/**
* Turn a {@link WorkspaceConfig} into a
* {@link WorkspaceConfigObject}, or throw if it's not valid.
*/
export declare const asWSConfig: (conf: unknown, path?: string) => WorkspaceConfigObject;
/**
* Throw if the provided value is not a valid {@link WorkspaceConfig}
*/
export declare const assertWSConfig: (conf: unknown, path?: string) => asserts conf is WorkspaceConfig;
export type MonorepoOptions = {
/**
* A {@link PackageJson} object, for sharing manifest caches
*/
packageJson?: PackageJson;
/**
* A {@link PathScurry} object, for use in globs
*/
scurry?: PathScurry;
/**
* Parsed normalized contents of the workspaces from a `vlt.json`
* file
*/
config?: WorkspaceConfigObject;
/**
* If set, then {@link Monorepo#load} will be called immediately with
* this argument.
*/
load?: LoadQuery;
};
/**
* Class representing a Monorepo containing multiple workspaces.
*
* Does not automatically look up the root, but that can be provided by
* running `Config.load()`, since it stops seeking the route when a
* `vlt.json` file is encountered.
*/
export declare class Monorepo {
#private;
/** The project root where vlt.json is found */
projectRoot: string;
/** Scurry object to cache all filesystem calls (mostly globs) */
scurry: PathScurry;
packageJson: PackageJson;
/**
* Number of {@link Workspace} objects loaded in this Monorepo
*/
get size(): number;
constructor(projectRoot: string, options?: MonorepoOptions);
/**
* Load the workspace definitions from vlt.json,
* canonicalizing the result into the effective `{[group:string]:string[]}`
* form.
*
* Eg:
* - `"src/*"` => `{packages:["src/*"]}`
* - `{"apps": "src/*"}` => `{apps: ["src/*"]}`
*/
get config(): WorkspaceConfigObject;
/**
* Iterating the Monorepo object yields the workspace objects, in as close to
* topological dependency order as possible.
*/
[Symbol.iterator](): Generator<Workspace, void, void>;
/**
* Iterating the Monorepo object yields the workspace objects, in as close to
* topological dependency order as possible.
*/
[Symbol.asyncIterator](): AsyncGenerator<Workspace, void, void>;
/**
* By default, loads all workspaces reachable in the Monorepo.
*
* If provided with one (`string`)or more (`string[]`) group names in
* the {@link LoadQuery#groups} field, then only Workspaces in the named
* group(s) will be considered. Note that group names are unique string
* matches, not globs.
*
* If provided with a set of arbitrary path arguments, then only paths
* patching the provided pattern(s) will be included.
*
* These two options intersect, so
* `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces
* in the group `foo` that match the paths glob.
*/
load(query?: LoadQuery): this;
/**
* Return the array of workspace dependencies that are found in
* the loaded set, for use in calculating dependency graph order for
* build operations.
*
* This does *not* get the full set of dependencies, or expand any
* `workspace:` dependencies that are not loaded.
*
* Call with the `forceLoad` param set to `true` to attempt a full
* load if any deps are not currently loaded.
*/
getDeps(ws: Workspace, forceLoad?: boolean): Workspace[];
onCycle(_ws: Workspace, _cycle: Workspace[], _depPath: Workspace[]): void;
/**
* Return the set of workspaces in the named group.
* If the group is not one we know about, then undefined is returned.
*/
group(group: string): Set<Workspace> | undefined;
/**
* Get a loaded workspace by path or name.
*
* Note that this can only return workspaces that were ingested via a
* previous call to {@link Monorepo#load}.
*/
get(nameOrPath: string): Workspace | undefined;
/**
* get the list of all loaded workspace names used as keys
*/
names(): Generator<string, void, unknown>;
/**
* get the list of all loaded workspace paths used as keys
*/
paths(): Generator<string, void, unknown>;
/**
* get the workspace objects in no particular order.
* this is ever so slightly faster than iterating, because it doesn't
* explore the graph to yield results in topological dependency order,
* and should be used instead when order doesn't matter.
*/
values(): Generator<Workspace, void, unknown>;
/**
* Get all the keys (package names and paths) for loaded workspaces.
* Union of {@link Monorepo#names} and {@link Monorepo#paths}
*/
keys(): Generator<string, void, unknown>;
/**
* Filter the monorepo object yielding the workspace objects that matches
* either of the {@link WorkspacesLoadedConfig} options provided, in as close
* to topological dependency order as possible.
*/
filter({ workspace: namesOrPaths, 'workspace-group': groupName, }: WorkspacesLoadedConfig): Generator<Workspace, void, unknown>;
/**
* Run an operation asynchronously over all loaded workspaces
*
* If the `forceLoad` param is true, then it will attempt to do a full load
* when encountering a `workspace:` dependency that isn't loaded.
*
* Note that because the return type appears in the parameters of the
* operation function, it must be set explicitly either in the operation
* function signature or by calling `run<MyType>` or it'll fall back to
* `unknown`, similar to `Array.reduce()`, and for the same reason.
*/
run<R>(operation: (s: Workspace, signal: AbortSignal, depResults: DepResults<Workspace, R>) => Promise<R> | R, forceLoad?: boolean): Promise<Map<Workspace, R>>;
/**
* Run an operation synchronously over all loaded workspaces
*
* If the `forceLoad` param is true, then it will attempt to do a full load
* when encountering a `workspace:` dependency that isn't loaded.
*
* Note that because the return type appears in the parameters of the
* operation function, it must be set explicitly either in the operation
* function signature or by calling `runSync<MyType>` or it'll fall back to
* `unknown`, similar to `Array.reduce()`, and for the same reason.
*/
runSync<R>(operation: (s: Workspace, signal: AbortSignal, depResults: DepResults<Workspace, R>) => R, forceLoad?: boolean): Map<Workspace, R>;
/**
* Convenience method to instantiate and load in one call.
* Returns undefined if the project is not a monorepo workspaces
* root, otherwise returns the loaded Monorepo.
*/
static maybeLoad(projectRoot: string, options?: MonorepoOptions): Monorepo | undefined;
/**
* Convenience method to instantiate and load in one call.
* Throws if called on a directory that is not a workspaces root.
*/
static load(projectRoot: string, options?: MonorepoOptions): Monorepo;
}
export declare const workspaceCache: Map<string, Workspace>;
/**
* Class representing a single Workspace in a {@link Monorepo}
*/
export declare class Workspace {
#private;
id: DepID;
path: string;
fullpath: string;
manifest: NormalizedManifest;
groups: string[];
name: string;
constructor(path: string, manifest: NormalizedManifest, fullpath: string);
get keys(): string[];
}
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
import { joinDepIDTuple } from '@vltpkg/dep-id';
import { error } from '@vltpkg/error-cause';
import { PackageJson } from '@vltpkg/package-json';
import { load } from '@vltpkg/vlt-json';
import { globSync } from 'glob';
import { graphRun, graphRunSync } from '@vltpkg/graph-run';
import { minimatch } from 'minimatch';
import { basename, posix, resolve } from 'node:path';
import { PathScurry } from 'path-scurry';
/**
* Turn a {@link WorkspaceConfig} into a
* {@link WorkspaceConfigObject}, or throw if it's not valid.
*/
export const asWSConfig = (conf, path) => {
assertWSConfig(conf, path);
return (typeof conf === 'string' ? { packages: [conf] }
: Array.isArray(conf) ? { packages: conf }
: Object.fromEntries(Object.entries(conf).map(([k, v]) => [
k,
typeof v === 'string' ? [v] : v,
])));
};
/**
* Throw if the provided value is not a valid {@link WorkspaceConfig}
*/
export const assertWSConfig = (conf, path) => {
if (typeof conf === 'string')
return;
if (Array.isArray(conf)) {
for (const c of conf) {
if (typeof c !== 'string') {
throw error('Invalid workspace definition', {
path,
found: c,
wanted: 'string',
});
}
}
return;
}
if (conf && typeof conf === 'object') {
for (const [group, value] of Object.entries(conf)) {
if (typeof value === 'string')
continue;
if (Array.isArray(value)) {
for (const c of value) {
if (typeof c !== 'string') {
throw error('Invalid workspace definition', {
path,
name: group,
found: c,
wanted: 'string',
});
}
}
continue;
}
throw error('Invalid workspace definition', {
path,
name: group,
found: value,
wanted: 'string | string[]',
});
}
return;
}
throw error('Invalid workspace definition', {
path,
found: conf,
wanted: 'string | string[] | { [group: string]: string | string[] }',
});
};
/**
* Class representing a Monorepo containing multiple workspaces.
*
* Does not automatically look up the root, but that can be provided by
* running `Config.load()`, since it stops seeking the route when a
* `vlt.json` file is encountered.
*/
export class Monorepo {
/** The project root where vlt.json is found */
projectRoot;
/** Scurry object to cache all filesystem calls (mostly globs) */
scurry;
// maps both name and path to the workspace objects
#workspaces = new Map();
#groups = new Map();
#config;
packageJson;
/**
* Number of {@link Workspace} objects loaded in this Monorepo
*/
get size() {
return [...this.values()].length;
}
constructor(projectRoot, options = {}) {
this.projectRoot = resolve(projectRoot);
this.scurry = options.scurry ?? new PathScurry(projectRoot);
this.packageJson = options.packageJson ?? new PackageJson();
this.#config = options.config;
if (options.load)
this.load(options.load);
}
/**
* Load the workspace definitions from vlt.json,
* canonicalizing the result into the effective `{[group:string]:string[]}`
* form.
*
* Eg:
* - `"src/*"` => `{packages:["src/*"]}`
* - `{"apps": "src/*"}` => `{apps: ["src/*"]}`
*/
get config() {
if (this.#config)
return this.#config;
this.#config = asWSConfig(load('workspaces', assertWSConfig) ?? {});
return this.#config;
}
/**
* Iterating the Monorepo object yields the workspace objects, in as close to
* topological dependency order as possible.
*/
*[Symbol.iterator]() {
const [ws] = [...this.values()];
if (!ws)
return;
// leverage the fact that graphRun returns results in
// as close to topological order as possible.
for (const workspace of this.runSync(() => { }).keys()) {
yield workspace;
}
}
/**
* Iterating the Monorepo object yields the workspace objects, in as close to
* topological dependency order as possible.
*/
async *[Symbol.asyncIterator]() {
const [ws] = [...this.values()];
if (!ws)
return;
for (const workspace of (await this.run(() => { })).keys()) {
yield workspace;
}
}
/**
* By default, loads all workspaces reachable in the Monorepo.
*
* If provided with one (`string`)or more (`string[]`) group names in
* the {@link LoadQuery#groups} field, then only Workspaces in the named
* group(s) will be considered. Note that group names are unique string
* matches, not globs.
*
* If provided with a set of arbitrary path arguments, then only paths
* patching the provided pattern(s) will be included.
*
* These two options intersect, so
* `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces
* in the group `foo` that match the paths glob.
*/
load(query = {}) {
const paths = new Set(typeof query.paths === 'string' ?
[query.paths]
: (query.paths ?? []));
const groups = new Set(typeof query.groups === 'string' ?
[query.groups]
: (query.groups ?? []));
const groupsExpanded = {};
for (const [group, pattern] of Object.entries(this.config)) {
if (groups.size && !groups.has(group))
continue;
groupsExpanded[group] = this.#glob(pattern);
}
const filter = paths.size ? this.#glob([...paths]) : paths;
// if we specified paths, but none matched, nothing to do
if (paths.size && !filter.size)
return this;
for (const [group, matches] of Object.entries(groupsExpanded)) {
for (const path of matches) {
if (filter.size && !filter.has(path))
continue;
this.#loadWS(path, group);
}
}
return this;
}
// Either load a workspace from disk, or from our internal set,
// and assign it to the named group
#loadWS(path, group) {
const fullpath = resolve(this.projectRoot, path);
const loaded = this.#workspaces.get(fullpath);
if (loaded)
return loaded;
const fromCache = workspaceCache.get(fullpath);
const manifest = fromCache?.manifest ?? this.packageJson.read(fullpath);
const ws = fromCache ?? new Workspace(path, manifest, fullpath);
if (group)
ws.groups.push(group);
// Check for duplicate workspace names
const existingWS = this.#workspaces.get(ws.name);
if (existingWS && existingWS.fullpath !== ws.fullpath) {
throw error('Duplicate workspace name found', {
name: ws.name,
path: this.projectRoot,
wanted: ws.fullpath,
found: existingWS.fullpath,
});
}
this.#workspaces.set(ws.fullpath, ws);
this.#workspaces.set(ws.path, ws);
this.#workspaces.set(ws.name, ws);
for (const name of ws.groups) {
const group = this.#groups.get(name) ?? new Set();
group.add(ws);
this.#groups.set(name, group);
}
return ws;
}
// can't be cached, because it's dependent on the matches set
// but still worthwhile to have it defined in one place
#globOptions(matches) {
// if the entry or any of its parent dirs are already matched,
// then we should not explore further down that directory tree.
// if we hit the projectRoot then stop searching.
const inMatches = (p) => {
return (!!p?.relativePosix() &&
(matches.has(p.relativePosix()) || inMatches(p.parent)));
};
return {
root: this.projectRoot,
cwd: this.projectRoot,
posix: true,
scurry: this.scurry,
withFileTypes: false,
ignore: {
childrenIgnored: p => basename(p.relativePosix()) === 'node_modules' ||
inMatches(p),
// ignore if fails to load package.json
ignored: p => {
p.lstatSync();
const rel = p.relativePosix();
if (!rel)
return true;
const maybeDelete = [];
for (const m of matches) {
if (rel.startsWith(m + '/'))
return true;
if (m.startsWith(rel + '/')) {
maybeDelete.push(m);
}
}
if (!p.isDirectory())
return true;
const pj = p.resolve('package.json').lstatSync();
if (!pj?.isFile())
return true;
try {
this.packageJson.read(p.fullpath());
}
catch {
return true;
}
for (const m of maybeDelete) {
matches.delete(m);
}
matches.add(rel);
return false;
},
},
};
}
#glob(pattern) {
const matches = new Set();
globSync(pattern, this.#globOptions(matches));
return matches;
}
/**
* Return the array of workspace dependencies that are found in
* the loaded set, for use in calculating dependency graph order for
* build operations.
*
* This does *not* get the full set of dependencies, or expand any
* `workspace:` dependencies that are not loaded.
*
* Call with the `forceLoad` param set to `true` to attempt a full
* load if any deps are not currently loaded.
*/
getDeps(ws, forceLoad = false) {
// load manifest and find workspace: deps
// filter by those loaded
const { manifest } = ws;
const depWorkspaces = [];
let didForceLoad = false;
for (const depType of [
'dependencies',
'devDependencies',
'optionalDependencies',
'peerDependencies',
]) {
const deps = manifest[depType];
if (!deps)
continue;
for (const [dep, spec] of Object.entries(deps)) {
if (spec.startsWith('workspace:')) {
let depWS = this.#workspaces.get(dep);
if (!depWS) {
if (!forceLoad)
continue;
if (didForceLoad)
continue;
didForceLoad = true;
this.load();
depWS = this.#workspaces.get(dep);
if (!depWS)
continue;
}
depWorkspaces.push(depWS);
}
}
}
return depWorkspaces;
}
onCycle(_ws, _cycle, _depPath) {
// XXX - process logging? Need to say something like:
// Cyclical workspace dependency warning!
// When evaluating dependency ${ws.name} via ${
// path.map(ws => ws.name).join(' -> ')
// }, a dependency cycle was detected: ${
// cycle.map(ws => ws.name).join(' -> ')
// }. Operation will continue, but dependency order not guaranteed.`
}
/**
* Return the set of workspaces in the named group.
* If the group is not one we know about, then undefined is returned.
*/
group(group) {
return this.#groups.get(group);
}
/**
* Get a loaded workspace by path or name.
*
* Note that this can only return workspaces that were ingested via a
* previous call to {@link Monorepo#load}.
*/
get(nameOrPath) {
return this.#workspaces.get(nameOrPath);
}
/**
* get the list of all loaded workspace names used as keys
*/
*names() {
for (const [key, ws] of this.#workspaces) {
if (key === ws.name)
yield key;
}
}
/**
* get the list of all loaded workspace paths used as keys
*/
*paths() {
for (const [key, ws] of this.#workspaces) {
if (key === ws.path)
yield key;
}
}
/**
* get the workspace objects in no particular order.
* this is ever so slightly faster than iterating, because it doesn't
* explore the graph to yield results in topological dependency order,
* and should be used instead when order doesn't matter.
*/
*values() {
const seen = new Set();
for (const ws of this.#workspaces.values()) {
if (seen.has(ws.fullpath))
continue;
seen.add(ws.fullpath);
yield ws;
}
}
/**
* Get all the keys (package names and paths) for loaded workspaces.
* Union of {@link Monorepo#names} and {@link Monorepo#paths}
*/
*keys() {
for (const ws of this.values()) {
yield ws.path;
if (ws.name !== ws.path)
yield ws.name;
}
}
/**
* Filter the monorepo object yielding the workspace objects that matches
* either of the {@link WorkspacesLoadedConfig} options provided, in as close
* to topological dependency order as possible.
*/
*filter({ workspace: namesOrPaths, 'workspace-group': groupName, }) {
const globPatternChecks = namesOrPaths?.map(glob => minimatch.filter(posix.join(glob)));
for (const ws of this) {
// check if any group has any of the provided group names
if (groupName?.some(i => ws.groups.includes(i))) {
yield ws;
continue;
}
// check if any workspace-provided name directly matches any of the
// configured workspaces by either name or file path
if (namesOrPaths
?.map(i => posix.join(i))
.some(i => ws.keys.includes(i))) {
yield ws;
continue;
}
// check if one of the workspace values are matching glob patterns
if (ws.keys.some(key => globPatternChecks?.some(fn => fn(key)))) {
yield ws;
}
}
}
/**
* Run an operation asynchronously over all loaded workspaces
*
* If the `forceLoad` param is true, then it will attempt to do a full load
* when encountering a `workspace:` dependency that isn't loaded.
*
* Note that because the return type appears in the parameters of the
* operation function, it must be set explicitly either in the operation
* function signature or by calling `run<MyType>` or it'll fall back to
* `unknown`, similar to `Array.reduce()`, and for the same reason.
*/
async run(operation, forceLoad = false) {
const [ws, ...rest] = [...this.#workspaces.values()];
if (!ws) {
throw error('No workspaces loaded', undefined, this.run);
}
return graphRun({
graph: [ws, ...rest],
getDeps: ws => this.getDeps(ws, forceLoad),
visit: async (ws, signal, _, depResults) => await operation(ws, signal, depResults),
onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),
});
}
/**
* Run an operation synchronously over all loaded workspaces
*
* If the `forceLoad` param is true, then it will attempt to do a full load
* when encountering a `workspace:` dependency that isn't loaded.
*
* Note that because the return type appears in the parameters of the
* operation function, it must be set explicitly either in the operation
* function signature or by calling `runSync<MyType>` or it'll fall back to
* `unknown`, similar to `Array.reduce()`, and for the same reason.
*/
runSync(operation, forceLoad = false) {
const [ws, ...rest] = [...this.#workspaces.values()];
if (!ws) {
throw error('No workspaces loaded', undefined, this.run);
}
return graphRunSync({
graph: [ws, ...rest],
getDeps: ws => this.getDeps(ws, forceLoad),
visit: (ws, signal, _, depResults) => operation(ws, signal, depResults),
onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),
});
}
/**
* Convenience method to instantiate and load in one call.
* Returns undefined if the project is not a monorepo workspaces
* root, otherwise returns the loaded Monorepo.
*/
static maybeLoad(projectRoot, options = { load: {} }) {
const config = load('workspaces', assertWSConfig);
if (!config)
return;
return new Monorepo(projectRoot, { load: {}, ...options });
}
/**
* Convenience method to instantiate and load in one call.
* Throws if called on a directory that is not a workspaces root.
*/
static load(projectRoot, options = { load: {} }) {
const { load = {} } = options;
return new Monorepo(projectRoot, { ...options, load });
}
}
export const workspaceCache = new Map();
/**
* Class representing a single Workspace in a {@link Monorepo}
*/
export class Workspace {
id;
path;
fullpath;
manifest;
groups = [];
name;
#keys;
constructor(path, manifest, fullpath) {
this.id = joinDepIDTuple(['workspace', path]);
workspaceCache.set(fullpath, this);
this.path = path;
this.fullpath = fullpath;
this.manifest = manifest;
this.name = manifest.name ?? path;
}
get keys() {
if (this.#keys) {
return this.#keys;
}
this.#keys = [this.name, this.path, this.fullpath];
return this.#keys;
}
}
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DepID } from '@vltpkg/dep-id'\nimport { joinDepIDTuple } from '@vltpkg/dep-id'\nimport { error } from '@vltpkg/error-cause'\nimport { PackageJson } from '@vltpkg/package-json'\nimport type { NormalizedManifest } from '@vltpkg/types'\nimport { load } from '@vltpkg/vlt-json'\nimport type { GlobOptionsWithFileTypesFalse } from 'glob'\nimport { globSync } from 'glob'\nimport type { DepResults } from '@vltpkg/graph-run'\nimport { graphRun, graphRunSync } from '@vltpkg/graph-run'\nimport { minimatch } from 'minimatch'\nimport { basename, posix, resolve } from 'node:path'\nimport type { Path } from 'path-scurry'\nimport { PathScurry } from 'path-scurry'\n\nexport type WorkspacesLoadedConfig = {\n workspace?: string[]\n 'workspace-group'?: string[]\n}\n\n/**\n * The object passed to the constructor or {@link Monorepo#load} to limit which\n * {@link Workspace Workspaces} get loaded.\n */\nexport type LoadQuery = {\n /**\n * A glob pattern string, or an array of them. Only workspaces found\n * in paths matched will be loaded.\n */\n paths?: string[] | string\n /**\n * A string, or an array of strings. If set, only workspaces in the\n * specified groups named will be included, if set.\n */\n groups?: string[] | string\n}\n\n/**\n * Canonical form of the {@link WorkspaceConfig}, used\n * internally for consistency.\n */\nexport type WorkspaceConfigObject = Record<string, string[]>\n\n/**\n * Allowed datatype in the `workspaces` field of the `vlt.json` file.\n */\nexport type WorkspaceConfig =\n | string[]\n | WorkspaceConfigObject\n | string\n\n/**\n * Turn a {@link WorkspaceConfig} into a\n * {@link WorkspaceConfigObject}, or throw if it's not valid.\n */\nexport const asWSConfig = (\n conf: unknown,\n path?: string,\n): WorkspaceConfigObject => {\n assertWSConfig(conf, path)\n return (\n typeof conf === 'string' ? { packages: [conf] }\n : Array.isArray(conf) ? { packages: conf }\n : Object.fromEntries(\n Object.entries(conf).map(([k, v]) => [\n k,\n typeof v === 'string' ? [v] : v,\n ]),\n )\n )\n}\n\n/**\n * Throw if the provided value is not a valid {@link WorkspaceConfig}\n */\nexport const assertWSConfig: (\n conf: unknown,\n path?: string,\n) => asserts conf is WorkspaceConfig = (\n conf: unknown,\n path?: string,\n) => {\n if (typeof conf === 'string') return\n\n if (Array.isArray(conf)) {\n for (const c of conf) {\n if (typeof c !== 'string') {\n throw error('Invalid workspace definition', {\n path,\n found: c as unknown,\n wanted: 'string',\n })\n }\n }\n return\n }\n\n if (conf && typeof conf === 'object') {\n for (const [group, value] of Object.entries(conf)) {\n if (typeof value === 'string') continue\n if (Array.isArray(value)) {\n for (const c of value) {\n if (typeof c !== 'string') {\n throw error('Invalid workspace definition', {\n path,\n name: group,\n found: c as unknown,\n wanted: 'string',\n })\n }\n }\n continue\n }\n throw error('Invalid workspace definition', {\n path,\n name: group,\n found: value as unknown,\n wanted: 'string | string[]',\n })\n }\n return\n }\n\n throw error('Invalid workspace definition', {\n path,\n found: conf,\n wanted:\n 'string | string[] | { [group: string]: string | string[] }',\n })\n}\n\nexport type MonorepoOptions = {\n /**\n * A {@link PackageJson} object, for sharing manifest caches\n */\n packageJson?: PackageJson\n /**\n * A {@link PathScurry} object, for use in globs\n */\n scurry?: PathScurry\n /**\n * Parsed normalized contents of the workspaces from a `vlt.json`\n * file\n */\n config?: WorkspaceConfigObject\n /**\n * If set, then {@link Monorepo#load} will be called immediately with\n * this argument.\n */\n load?: LoadQuery\n}\n\n/**\n * Class representing a Monorepo containing multiple workspaces.\n *\n * Does not automatically look up the root, but that can be provided by\n * running `Config.load()`, since it stops seeking the route when a\n * `vlt.json` file is encountered.\n */\nexport class Monorepo {\n /** The project root where vlt.json is found */\n projectRoot: string\n /** Scurry object to cache all filesystem calls (mostly globs) */\n scurry: PathScurry\n\n // maps both name and path to the workspace objects\n #workspaces = new Map<string, Workspace>()\n #groups = new Map<string, Set<Workspace>>()\n #config?: WorkspaceConfigObject\n packageJson: PackageJson\n\n /**\n * Number of {@link Workspace} objects loaded in this Monorepo\n */\n get size(): number {\n return [...this.values()].length\n }\n\n constructor(projectRoot: string, options: MonorepoOptions = {}) {\n this.projectRoot = resolve(projectRoot)\n this.scurry = options.scurry ?? new PathScurry(projectRoot)\n this.packageJson = options.packageJson ?? new PackageJson()\n this.#config = options.config\n if (options.load) this.load(options.load)\n }\n\n /**\n * Load the workspace definitions from vlt.json,\n * canonicalizing the result into the effective `{[group:string]:string[]}`\n * form.\n *\n * Eg:\n * - `\"src/*\"` => `{packages:[\"src/*\"]}`\n * - `{\"apps\": \"src/*\"}` => `{apps: [\"src/*\"]}`\n */\n get config(): WorkspaceConfigObject {\n if (this.#config) return this.#config\n this.#config = asWSConfig(\n load('workspaces', assertWSConfig) ?? {},\n )\n return this.#config\n }\n\n /**\n * Iterating the Monorepo object yields the workspace objects, in as close to\n * topological dependency order as possible.\n */\n *[Symbol.iterator](): Generator<Workspace, void, void> {\n const [ws] = [...this.values()]\n if (!ws) return\n // leverage the fact that graphRun returns results in\n // as close to topological order as possible.\n for (const workspace of this.runSync(() => {}).keys()) {\n yield workspace\n }\n }\n\n /**\n * Iterating the Monorepo object yields the workspace objects, in as close to\n * topological dependency order as possible.\n */\n async *[Symbol.asyncIterator](): AsyncGenerator<\n Workspace,\n void,\n void\n > {\n const [ws] = [...this.values()]\n if (!ws) return\n for (const workspace of (await this.run(() => {})).keys()) {\n yield workspace\n }\n }\n\n /**\n * By default, loads all workspaces reachable in the Monorepo.\n *\n * If provided with one (`string`)or more (`string[]`) group names in\n * the {@link LoadQuery#groups} field, then only Workspaces in the named\n * group(s) will be considered. Note that group names are unique string\n * matches, not globs.\n *\n * If provided with a set of arbitrary path arguments, then only paths\n * patching the provided pattern(s) will be included.\n *\n * These two options intersect, so\n * `load({groups:'foo', paths:'./foo/[xy]*'})` will only load the workspaces\n * in the group `foo` that match the paths glob.\n */\n load(query: LoadQuery = {}): this {\n const paths = new Set(\n typeof query.paths === 'string' ?\n [query.paths]\n : (query.paths ?? []),\n )\n const groups = new Set(\n typeof query.groups === 'string' ?\n [query.groups]\n : (query.groups ?? []),\n )\n\n const groupsExpanded: Record<string, Set<string>> = {}\n for (const [group, pattern] of Object.entries(this.config)) {\n if (groups.size && !groups.has(group)) continue\n groupsExpanded[group] = this.#glob(pattern)\n }\n const filter = paths.size ? this.#glob([...paths]) : paths\n\n // if we specified paths, but none matched, nothing to do\n if (paths.size && !filter.size) return this\n\n for (const [group, matches] of Object.entries(groupsExpanded)) {\n for (const path of matches) {\n if (filter.size && !filter.has(path)) continue\n this.#loadWS(path, group)\n }\n }\n\n return this\n }\n\n // Either load a workspace from disk, or from our internal set,\n // and assign it to the named group\n #loadWS(path: string, group?: string): Workspace {\n const fullpath = resolve(this.projectRoot, path)\n const loaded = this.#workspaces.get(fullpath)\n if (loaded) return loaded\n const fromCache = workspaceCache.get(fullpath)\n const manifest =\n fromCache?.manifest ?? this.packageJson.read(fullpath)\n const ws = fromCache ?? new Workspace(path, manifest, fullpath)\n if (group) ws.groups.push(group)\n\n // Check for duplicate workspace names\n const existingWS = this.#workspaces.get(ws.name)\n if (existingWS && existingWS.fullpath !== ws.fullpath) {\n throw error('Duplicate workspace name found', {\n name: ws.name,\n path: this.projectRoot,\n wanted: ws.fullpath,\n found: existingWS.fullpath,\n })\n }\n\n this.#workspaces.set(ws.fullpath, ws)\n this.#workspaces.set(ws.path, ws)\n this.#workspaces.set(ws.name, ws)\n for (const name of ws.groups) {\n const group = this.#groups.get(name) ?? new Set()\n group.add(ws)\n this.#groups.set(name, group)\n }\n return ws\n }\n\n // can't be cached, because it's dependent on the matches set\n // but still worthwhile to have it defined in one place\n #globOptions(matches: Set<string>): GlobOptionsWithFileTypesFalse {\n // if the entry or any of its parent dirs are already matched,\n // then we should not explore further down that directory tree.\n // if we hit the projectRoot then stop searching.\n const inMatches = (p?: Path): boolean => {\n return (\n !!p?.relativePosix() &&\n (matches.has(p.relativePosix()) || inMatches(p.parent))\n )\n }\n\n return {\n root: this.projectRoot,\n cwd: this.projectRoot,\n posix: true,\n scurry: this.scurry,\n withFileTypes: false,\n ignore: {\n childrenIgnored: p =>\n basename(p.relativePosix()) === 'node_modules' ||\n inMatches(p),\n // ignore if fails to load package.json\n ignored: p => {\n p.lstatSync()\n const rel = p.relativePosix()\n if (!rel) return true\n const maybeDelete: string[] = []\n for (const m of matches) {\n if (rel.startsWith(m + '/')) return true\n if (m.startsWith(rel + '/')) {\n maybeDelete.push(m)\n }\n }\n if (!p.isDirectory()) return true\n const pj = p.resolve('package.json').lstatSync()\n if (!pj?.isFile()) return true\n try {\n this.packageJson.read(p.fullpath())\n } catch {\n return true\n }\n for (const m of maybeDelete) {\n matches.delete(m)\n }\n matches.add(rel)\n return false\n },\n },\n }\n }\n\n #glob(pattern: string[] | string) {\n const matches = new Set<string>()\n globSync(pattern, this.#globOptions(matches))\n return matches\n }\n\n /**\n * Return the array of workspace dependencies that are found in\n * the loaded set, for use in calculating dependency graph order for\n * build operations.\n *\n * This does *not* get the full set of dependencies, or expand any\n * `workspace:` dependencies that are not loaded.\n *\n * Call with the `forceLoad` param set to `true` to attempt a full\n * load if any deps are not currently loaded.\n */\n getDeps(ws: Workspace, forceLoad = false): Workspace[] {\n // load manifest and find workspace: deps\n // filter by those loaded\n const { manifest } = ws\n const depWorkspaces: Workspace[] = []\n let didForceLoad = false\n for (const depType of [\n 'dependencies',\n 'devDependencies',\n 'optionalDependencies',\n 'peerDependencies',\n ] as const) {\n const deps = manifest[depType]\n if (!deps) continue\n for (const [dep, spec] of Object.entries(deps)) {\n if (spec.startsWith('workspace:')) {\n let depWS = this.#workspaces.get(dep)\n if (!depWS) {\n if (!forceLoad) continue\n if (didForceLoad) continue\n didForceLoad = true\n this.load()\n depWS = this.#workspaces.get(dep)\n if (!depWS) continue\n }\n depWorkspaces.push(depWS)\n }\n }\n }\n return depWorkspaces\n }\n\n onCycle(\n _ws: Workspace,\n _cycle: Workspace[],\n _depPath: Workspace[],\n ) {\n // XXX - process logging? Need to say something like:\n // Cyclical workspace dependency warning!\n // When evaluating dependency ${ws.name} via ${\n // path.map(ws => ws.name).join(' -> ')\n // }, a dependency cycle was detected: ${\n // cycle.map(ws => ws.name).join(' -> ')\n // }. Operation will continue, but dependency order not guaranteed.`\n }\n\n /**\n * Return the set of workspaces in the named group.\n * If the group is not one we know about, then undefined is returned.\n */\n group(group: string) {\n return this.#groups.get(group)\n }\n\n /**\n * Get a loaded workspace by path or name.\n *\n * Note that this can only return workspaces that were ingested via a\n * previous call to {@link Monorepo#load}.\n */\n get(nameOrPath: string) {\n return this.#workspaces.get(nameOrPath)\n }\n\n /**\n * get the list of all loaded workspace names used as keys\n */\n *names() {\n for (const [key, ws] of this.#workspaces) {\n if (key === ws.name) yield key\n }\n }\n\n /**\n * get the list of all loaded workspace paths used as keys\n */\n *paths() {\n for (const [key, ws] of this.#workspaces) {\n if (key === ws.path) yield key\n }\n }\n\n /**\n * get the workspace objects in no particular order.\n * this is ever so slightly faster than iterating, because it doesn't\n * explore the graph to yield results in topological dependency order,\n * and should be used instead when order doesn't matter.\n */\n *values() {\n const seen = new Set<string>()\n for (const ws of this.#workspaces.values()) {\n if (seen.has(ws.fullpath)) continue\n seen.add(ws.fullpath)\n yield ws\n }\n }\n\n /**\n * Get all the keys (package names and paths) for loaded workspaces.\n * Union of {@link Monorepo#names} and {@link Monorepo#paths}\n */\n *keys() {\n for (const ws of this.values()) {\n yield ws.path\n if (ws.name !== ws.path) yield ws.name\n }\n }\n\n /**\n * Filter the monorepo object yielding the workspace objects that matches\n * either of the {@link WorkspacesLoadedConfig} options provided, in as close\n * to topological dependency order as possible.\n */\n *filter({\n workspace: namesOrPaths,\n 'workspace-group': groupName,\n }: WorkspacesLoadedConfig) {\n const globPatternChecks = namesOrPaths?.map(glob =>\n minimatch.filter(posix.join(glob)),\n )\n for (const ws of this) {\n // check if any group has any of the provided group names\n if (groupName?.some(i => ws.groups.includes(i))) {\n yield ws\n continue\n }\n // check if any workspace-provided name directly matches any of the\n // configured workspaces by either name or file path\n if (\n namesOrPaths\n ?.map(i => posix.join(i))\n .some(i => ws.keys.includes(i))\n ) {\n yield ws\n continue\n }\n // check if one of the workspace values are matching glob patterns\n if (\n ws.keys.some(key => globPatternChecks?.some(fn => fn(key)))\n ) {\n yield ws\n }\n }\n }\n\n /**\n * Run an operation asynchronously over all loaded workspaces\n *\n * If the `forceLoad` param is true, then it will attempt to do a full load\n * when encountering a `workspace:` dependency that isn't loaded.\n *\n * Note that because the return type appears in the parameters of the\n * operation function, it must be set explicitly either in the operation\n * function signature or by calling `run<MyType>` or it'll fall back to\n * `unknown`, similar to `Array.reduce()`, and for the same reason.\n */\n async run<R>(\n operation: (\n s: Workspace,\n signal: AbortSignal,\n depResults: DepResults<Workspace, R>,\n ) => Promise<R> | R,\n forceLoad = false,\n ) {\n const [ws, ...rest] = [...this.#workspaces.values()]\n if (!ws) {\n throw error('No workspaces loaded', undefined, this.run)\n }\n\n return graphRun<Workspace, R>({\n graph: [ws, ...rest],\n getDeps: ws => this.getDeps(ws, forceLoad),\n visit: async (ws, signal, _, depResults) =>\n await operation(ws, signal, depResults),\n onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),\n })\n }\n\n /**\n * Run an operation synchronously over all loaded workspaces\n *\n * If the `forceLoad` param is true, then it will attempt to do a full load\n * when encountering a `workspace:` dependency that isn't loaded.\n *\n * Note that because the return type appears in the parameters of the\n * operation function, it must be set explicitly either in the operation\n * function signature or by calling `runSync<MyType>` or it'll fall back to\n * `unknown`, similar to `Array.reduce()`, and for the same reason.\n */\n runSync<R>(\n operation: (\n s: Workspace,\n signal: AbortSignal,\n depResults: DepResults<Workspace, R>,\n ) => R,\n forceLoad = false,\n ) {\n const [ws, ...rest] = [...this.#workspaces.values()]\n if (!ws) {\n throw error('No workspaces loaded', undefined, this.run)\n }\n\n return graphRunSync<Workspace, R>({\n graph: [ws, ...rest],\n getDeps: ws => this.getDeps(ws, forceLoad),\n visit: (ws, signal, _, depResults) =>\n operation(ws, signal, depResults),\n onCycle: (ws, cycle, path) => this.onCycle(ws, cycle, path),\n })\n }\n\n /**\n * Convenience method to instantiate and load in one call.\n * Returns undefined if the project is not a monorepo workspaces\n * root, otherwise returns the loaded Monorepo.\n */\n static maybeLoad(\n projectRoot: string,\n options: MonorepoOptions = { load: {} },\n ) {\n const config = load('workspaces', assertWSConfig)\n if (!config) return\n return new Monorepo(projectRoot, { load: {}, ...options })\n }\n\n /**\n * Convenience method to instantiate and load in one call.\n * Throws if called on a directory that is not a workspaces root.\n */\n static load(\n projectRoot: string,\n options: MonorepoOptions = { load: {} },\n ) {\n const { load = {} } = options\n return new Monorepo(projectRoot, { ...options, load })\n }\n}\n\nexport const workspaceCache = new Map<string, Workspace>()\n\n/**\n * Class representing a single Workspace in a {@link Monorepo}\n */\nexport class Workspace {\n id: DepID\n path: string\n fullpath: string\n manifest: NormalizedManifest\n groups: string[] = []\n name: string\n #keys?: string[]\n\n constructor(\n path: string,\n manifest: NormalizedManifest,\n fullpath: string,\n ) {\n this.id = joinDepIDTuple(['workspace', path])\n workspaceCache.set(fullpath, this)\n this.path = path\n this.fullpath = fullpath\n this.manifest = manifest\n this.name = manifest.name ?? path\n }\n\n get keys(): string[] {\n if (this.#keys) {\n return this.#keys\n }\n this.#keys = [this.name, this.path, this.fullpath]\n return this.#keys\n }\n}\n"]}