UNPKG

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
//#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;