workspaces-filter
Version:
A companion for filtering monorepo workspaces, by package name or package dir. Because all package manager's are weird. Useful for running scripts on a subset of workspaces. The primary reason is because Bun's "--filter" feature is buggy, but it's also us
174 lines (171 loc) • 6.15 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const __tunnckocore_execa = __toESM(require("@tunnckocore/execa"));
const fast_glob = __toESM(require("fast-glob"));
const node_fs_promises = __toESM(require("node:fs/promises"));
const node_path = __toESM(require("node:path"));
const node_process = __toESM(require("node:process"));
const picomatch = __toESM(require("picomatch"));
//#region src/index.ts
/**
* Filters workspace packages based on provided glob patterns and search patterns.
*
* @example
* import { filter } from 'workspaces-filter';
*
* // Filter workspaces matching 'pkg-*' pattern
* const graph = await filter(['packages/*'], 'pkg-*');
*
* // Filter multiple patterns
* const graph = await filter(['packages/*'], ['pkg-1', 'pkg-2']);
*
* // Filter with package dirs
* const graph = await filter(['packages/*'], ['packages/foo']);
*
* // Filter with custom working directory
* const graph = await filter(['packages/*'], '*', '/path/to/project');
*
*
* @param {string|string[]} wsGlobs - Array of workspace glob patterns to search for package.json files.
* @param {string|string[]} pattern - String or array of strings to filter workspaces by name or directory.
* @param {string} [cwd] - Optional current working directory (defaults to `process.cwd()`).
*
* @throws {Error} When no workspace globs are provided.
* @throws {Error} When no pattern is provided.
*
* @returns {Promise<Graph>} Resolving to a Graph object containing filtered workspace metadata.
* @public
*/
async function filter(wsGlobs, pattern, cwd) {
if (!wsGlobs || wsGlobs && Array.isArray(wsGlobs) && wsGlobs.length === 0) throw new Error("No workspace globs provided.");
pattern = Array.isArray(pattern) ? pattern : [pattern];
pattern = pattern.filter(Boolean);
if (pattern.length === 0) throw new Error("No pattern provided.");
cwd = cwd || node_process.default.cwd();
const stream = fast_glob.default.stream(wsGlobs.map((workspaceGlob) => `${workspaceGlob}/package.json`), {
absolute: true,
cwd
});
const workspaces = {};
for await (const pkgJsonPath of stream) {
const pkgRoot = node_path.default.dirname(pkgJsonPath);
const pkgJsonStr = await node_fs_promises.default.readFile(pkgJsonPath, "utf8");
const { dependencies = {}, exports: exports$1 = {}, license = "", name, scripts = {}, version = "0.0.0" } = JSON.parse(pkgJsonStr);
const pkg = {
dependencies,
exports: exports$1,
license,
name,
scripts,
version
};
workspaces[pkg.name] = {
dir: node_path.default.relative(cwd, pkgRoot),
...pkg
};
}
if (pattern.find((p) => p.trim() === ".") || pattern.find((p) => p.trim() === "*")) return workspaces;
const isMatch = (0, picomatch.default)(pattern.map((p) => p.replaceAll("/", ".")));
const graph = Object.fromEntries(Object.entries(workspaces).filter(([name, meta]) => {
const hasMatch = pattern.some((p) => name.includes(p)) || pattern.some((p) => meta.dir.includes(p)) || isMatch(name.replaceAll("/", ".")) || isMatch(meta.dir.replaceAll("/", "."));
return hasMatch;
}));
return graph;
}
/**
* Executes a shell command or a package script in the context of each package in the graph.
*
* @example
* import { filter, runCommandOn } from 'workspaces-filter';
*
* const graph = await filter(['packages/*'], ['@scope/*']);
* console.log(graph);
*
* type RunCommandOnOptions = {
* cwd?: string;
* isShell?: boolean;
* packageManager?: string;
* onTestCallback?: (_err: any, _ok: any) => void | Promise<void>;
* };
*
* // Run a shell command in each package
* await runCommandOn(['echo', 'Hello, World!'], graph, { isShell: true } as RunCommandOnOptions);
*
* // Run a package script in each package
* await runCommandOn(['build'], graph);
*
* @param {string[]} args - Arguments to pass to the command.
* @param {Graph} graph - Graph object containing package metadata.
* @param {RunCommandOnOptions} options - Optional configuration for running the command.
* @returns {Promise<Graph>} Resolving to the input graph object.
* @public
*/
async function runCommandOn(args, graph, options) {
const opts = {
cwd: node_process.default.cwd(),
isShell: false,
onTestCallback: () => {},
onTestCallbackcwd: node_process.default.cwd(),
packageManager: "bun",
...options
};
await Promise.all(Object.values(graph).map(async (x) => {
const meta = x;
const pkgDir = node_path.default.join(opts.cwd, meta.dir);
if (opts.isShell) {
try {
await (0, __tunnckocore_execa.shell)(args.join(" "), {
cwd: pkgDir,
stdout: "inherit"
});
await opts.onTestCallback(null, true);
} catch (err) {
console.error(err.stack);
await opts.onTestCallback(err, false);
}
return;
}
const [script, ...argz] = args;
const isScript = Boolean(meta.scripts[script || "_____$$$__"]);
const cmd = [
opts.packageManager,
isScript ? "run" : "",
script,
...argz
].filter(Boolean);
try {
await (0, __tunnckocore_execa.shell)(cmd.join(" "), {
cwd: pkgDir,
stdout: "inherit"
});
await opts.onTestCallback(null, true);
} catch (err) {
console.error(err.stack);
await opts.onTestCallback(err, false);
}
}));
return graph;
}
//#endregion
exports.filter = filter;
exports.runCommandOn = runCommandOn;