From 452f092ff0fc48cb833e080a1c9c77afe31c4dc8 Mon Sep 17 00:00:00 2001 From: Ruy Adorno Date: Wed, 10 Mar 2021 20:04:08 -0500 Subject: [PATCH] feat: add run-script workspaces Add workspaces support to `npm run-script` Related to: https://github.com/npm/rfcs/pull/117 Fixes: https://github.com/npm/statusboard/issues/276 --- lib/base-command.js | 1 + lib/npm.js | 10 ++++++ lib/run-script.js | 77 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/lib/base-command.js b/lib/base-command.js index c31e4a4d7f1b2..835a90e000a1f 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -1,5 +1,6 @@ // Base class for npm.commands[cmd] const usageUtil = require('./utils/usage.js') +const mapWorkspaces = require('@npmcli/map-workspaces') class BaseCommand { constructor (npm) { diff --git a/lib/npm.js b/lib/npm.js index 0534e630606e4..716c8837db122 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -105,9 +105,19 @@ const npm = module.exports = new class extends EventEmitter { }) } + + const workspacesEnabled = this.config.get('workspaces') + const workspacesFilters = this.config.get('workspace') + const filterByWorkspaces = workspacesEnabled || workspacesFilters.length > 0 + if (this.config.get('usage')) { console.log(impl.usage) cb() + } if (filterByWorkspaces) { + impl.execWorkspaces(args, this.config.get('workspace'), er => { + process.emit('timeEnd', `command:${cmd}`) + cb(er) + }) } else { impl.exec(args, er => { process.emit('timeEnd', `command:${cmd}`) diff --git a/lib/run-script.js b/lib/run-script.js index 3ea85b79ffd18..abfc5661722ec 100644 --- a/lib/run-script.js +++ b/lib/run-script.js @@ -1,6 +1,7 @@ const runScript = require('@npmcli/run-script') +const mapWorkspaces = require('@npmcli/map-workspaces') const { isServerPackage } = runScript -const readJson = require('read-package-json-fast') +const rpj = require('read-package-json-fast') const { resolve } = require('path') const log = require('npmlog') const didYouMean = require('./utils/did-you-mean.js') @@ -34,7 +35,7 @@ class RunScript extends BaseCommand { if (argv.length === 2) { // find the script name const json = resolve(this.npm.localPrefix, 'package.json') - const { scripts = {} } = await readJson(json).catch(er => ({})) + const { scripts = {} } = await rpj(json).catch(er => ({})) return Object.keys(scripts) } } @@ -46,12 +47,20 @@ class RunScript extends BaseCommand { this.list(args).then(() => cb()).catch(cb) } - async run (args) { + execWorkspaces (args, filters, cb) { + if (args.length) + this.runWorkspaces(args, filters).then(() => cb()).catch(cb) + else + this.listWorkspaces(args, filters).then(() => cb()).catch(cb) + } + + async run (args, topLevelPath) { const path = this.npm.localPrefix + topLevelPath = topLevelPath || this.npm.localPrefix const event = args.shift() const { scriptShell } = this.npm.flatOptions - const pkg = await readJson(`${path}/package.json`) + const pkg = await rpj(`${topLevelPath}/package.json`) const { scripts = {} } = pkg if (event === 'restart' && !scripts.restart) @@ -102,9 +111,10 @@ class RunScript extends BaseCommand { } } - async list () { - const path = this.npm.localPrefix - const { scripts, name } = await readJson(`${path}/package.json`) + async list (args, path) { + path = path || this.npm.localPrefix + const { scripts, name, _id } = await rpj(`${path}/package.json`) + const pkgid = _id || name if (!scripts) return [] @@ -135,13 +145,13 @@ class RunScript extends BaseCommand { } if (cmds.length) - this.npm.output(`Lifecycle scripts included in ${name}:`) + this.npm.output(`Lifecycle scripts included in ${pkgid}:`) for (const script of cmds) this.npm.output(prefix + script + indent + scripts[script]) if (!cmds.length && runScripts.length) - this.npm.output(`Scripts available in ${name} via \`npm run-script\`:`) + this.npm.output(`Scripts available in ${pkgid} via \`npm run-script\`:`) else if (runScripts.length) this.npm.output('\navailable via `npm run-script`:') @@ -150,5 +160,54 @@ class RunScript extends BaseCommand { return allScripts } + + async workspaces (filters) { + const cwd = this.npm.localPrefix + const pkg = await rpj(resolve(cwd, 'package.json')) + const workspaces = await mapWorkspaces({ cwd, pkg }) + + for (const w of filters) { + for (const [key, path] of workspaces.entries()) { + if (w !== key && resolve(cwd, w) !== path) + workspaces.delete(key) + } + } + + return workspaces + } + + async runWorkspaces (args, filters) { + for (const w of workspaces.values()) { + const pkg = await rpj(`${w}/package.json`) + } + } + + async listWorkspaces (args, filters) { + const workspaces = await this.workspaces(filters) + + if (this.npm.flatOptions.json) { + const res = {} + for (const w of workspaces.values()) { + const { scripts, name, _id } = await rpj(`${w}/package.json`) + const pkgid = _id || name + res[pkgid] = { ...scripts } + } + this.npm.output(JSON.stringify(res, null, 2)) + return + } + + if (this.npm.flatOptions.parseable) { + for (const w of workspaces.values()) { + const { scripts, name, _id } = await rpj(`${w}/package.json`) + const pkgid = _id || name + for (const [script, cmd] of Object.entries(scripts)) + this.npm.output(`${pkgid}:${script}:${cmd}`) + } + return + } + + for (const w of workspaces.values()) + await this.list(args, w) + } } module.exports = RunScript