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

145 lines (122 loc) 5.25 kB
#!/usr/bin/env node // SPDX-License-Identifier: MIT import cac from 'cac'; import dargs from 'dargs'; import fs from 'node:fs/promises'; import path from 'node:path'; import proc from 'node:process'; import { filter, runCommandOn } from './index.ts'; const cli = cac('workspaces-filter').version('0.8.2'); cli .command('<pattern> [...command]', 'Select by package name or workspace directory', { allowUnknownOptions: true, }) .example('workspaces-filter . build # run in all packages of all workspaces') .example('workspaces-filter _ build # because the "*" would not work if raw') .example('workspaces-filter \'*\' build # should be quoted to avoid shell globbing') .example('') .example('workspaces-filter "*preset*" build') .example('workspaces-filter "*preset*" add foo-pkg barry-pkg') .example('workspaces-filter "*preset*" add --dev typescript') .example('') .example('workspaces-filter "./packages/foo" -- echo "Hello, World!"') .example('workspaces-filter "./packages/*preset*" -- pwd') .example('') .example('workspaces-filter "*preset*" --print names') .example('workspaces-filter "*preset*" --print json') .example('workspaces-filter "*preset*" --print dirs') .example('') .option('--print <mode>', 'Print the names/folders of selected packages, without running command') .option('--cwd <dir>', 'Current working directory', { default: proc.cwd() }) .option( '--pm, --package-manager <pm>', 'The package manager to use. Defaults to packageManager from root package.json, or Bun', ) .action(async (pattern, command, options) => { const opts = { ...options }; if (opts['--'].length === 0 && command.length === 0 && !opts.print) { cli.outputHelp(); return; } const flags = { ...options }; delete flags.print; delete flags.cwd; delete flags.pm; delete flags['package-manager']; delete flags.packageManager; const flagged = dargs(flags, { useEquals: false }); if (flagged[0] === '--') { flagged.shift(); } const rootPkgJson = JSON.parse(await fs.readFile(path.join(opts.cwd, 'package.json'), 'utf8')); opts.packageManager = opts.packageManager || rootPkgJson.packageManager?.split('@')?.[0] || 'bun'; opts.pm = opts.packageManager; opts.isShell = command.length === 0; // // console.log({ opts }); let workspaces = rootPkgJson.workspaces; // handle bun workspaces workspaces = rootPkgJson.workspaces?.packages || rootPkgJson.workspace?.packages || workspaces; if (opts.packageManager === 'pnpm') { const yamlParse = await import('yaml').then(({ parse }) => parse); const wsfileStr = await fs.readFile(path.join(opts.cwd, 'pnpm-workspace.yaml'), 'utf8'); const wsfile = yamlParse(wsfileStr); workspaces = wsfile.packages; } if (!workspaces || workspaces.length === 0) { console.log( 'No workspaces found! Make sure you have \'workspaces\' field in your package.json or \'packages\' field in your pnpm-workspace.yaml', ); return proc.exit(0); } if (opts.help) { cli.outputHelp(); return; } pattern = pattern === '.' || pattern === '_' || pattern === '*' ? workspaces : pattern; const selected = await filter(workspaces, pattern, opts.cwd); if (opts.print) { if (opts.print === 'json') { console.log(JSON.stringify(selected)); return; } if (opts.print === 'names') { console.log(Object.keys(selected).join('\n')); return; } if (opts.print === 'dirs') { console.log( Object.values(selected) // @ts-expect-error bruh, fvck off .reduce((acc, x) => acc.concat(x.dir), []) .join('\n'), ); } return; } if (Object.keys(selected).length === 0) { console.log('No packages matching the filter.'); proc.exit(0); } else { await runCommandOn(opts.isShell ? flagged : command.concat(flagged).flat(), selected, opts); // ? TODO: automatic handling of catalogs? Could be too expensive (slow), // ? because after the install from `runCommandOn` we need to run one more to see what versions // ? were installed for the given packages, and then extract them and return them from the `runCommandOn` or a hook/callback, // ? and then write them to the default catalog in the rootPkgJson. // ? Or, we can just assume `latest` and fetch from here and put it in the catalog, // ? but we still need to update the workspace's package.json to be `catalog:` for these packages. // ? It's too much. // if (opts.catalog && command[0] && command[0] === 'add') { // const pkgsToInstall = command.slice(1); // if (rootPkgJson.workspaces) { // rootPkgJson.workspaces = rootPkgJson.workspaces || {}; // } // if (rootPkgJson.workspace) { // rootPkgJson.workspace = rootPkgJson.workspace || {}; // rootPkgJson.workspace.packages = rootPkgJson.workspace.packages || []; // rootPkgJson.workspace.catalog = rootPkgJson.workspace.catalog || {}; // } // } } }); cli.help(); cli.parse();