Skip to content

Commit

Permalink
feat: add run-script workspaces
Browse files Browse the repository at this point in the history
Add workspaces support to `npm run-script`

Related to: npm/rfcs#117
Fixes: npm/statusboard#276
  • Loading branch information
ruyadorno committed Mar 13, 2021
1 parent 8a06134 commit d74d5a9
Show file tree
Hide file tree
Showing 4 changed files with 469 additions and 15 deletions.
9 changes: 9 additions & 0 deletions lib/npm.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,18 @@ 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}`)
Expand Down
123 changes: 112 additions & 11 deletions lib/run-script.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
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 minimatch = require('minimatch')
const didYouMean = require('./utils/did-you-mean.js')
const isWindowsShell = require('./utils/is-windows-shell.js')

Expand Down Expand Up @@ -34,7 +36,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)
}
}
Expand All @@ -46,12 +48,18 @@ class RunScript extends BaseCommand {
this.list(args).then(() => cb()).catch(cb)
}

async run (args) {
const path = this.npm.localPrefix
const event = args.shift()
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, { path = this.npm.localPrefix, pkg } = {}) {
const [event, ...args] = _args
const { scriptShell } = this.npm.flatOptions

const pkg = await readJson(`${path}/package.json`)
pkg = pkg || (await rpj(`${path}/package.json`))
const { scripts = {} } = pkg

if (event === 'restart' && !scripts.restart)
Expand Down Expand Up @@ -102,9 +110,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 []
Expand Down Expand Up @@ -135,13 +144,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`:')

Expand All @@ -150,5 +159,97 @@ 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 })
const res = filters.length ? new Map() : workspaces

for (const filterArg of filters) {
for (const [key, path] of workspaces.entries()) {
if (filterArg === key
|| resolve(cwd, filterArg) === path
|| minimatch(path, `${resolve(cwd, filterArg)}/*`))
res.set(key, path)
}
}

if (!res.size) {
let msg = '!'
if (filters.length) {
msg = `:\n ${filters.reduce(
(res, filterArg) => `${res} --workspace=${filterArg}`, '')}`
}

throw new Error(`No workspaces found${msg}`)
}

return res
}

async runWorkspaces (args, filters) {
log.disableProgress()

const res = []
const workspaces = await this.workspaces(filters)

for (const workspacePath of workspaces.values()) {
const pkg = await rpj(`${workspacePath}/package.json`)
const runResult = await this.run(args, {
path: workspacePath,
pkg,
}).catch(err => {
log.error(`Lifecycle script \`${args[0]}\` failed with error:`)
log.error(err)
log.error(` for workspace: ${pkg._id || pkg.name}`)
log.error(` at location: ${workspacePath}`)

// avoids exiting with error code in case
// there's scripts missing in some workspaces
if (!err.message.startsWith('missing script'))
process.exitCode = 1

// keeps track of failed runs
return 1
})
res.push(runResult)
}

// in case **all** tests are failing, then it should exit with an error
// code, this handles the scenario in which all scripts are missing
if (res.every(Boolean))
process.exitCode = 1
}

async listWorkspaces (args, filters) {
const workspaces = await this.workspaces(filters)

if (log.level === 'silent')
return

if (this.npm.flatOptions.json) {
const res = {}
for (const workspacePath of workspaces.values()) {
const { scripts, name } = await rpj(`${workspacePath}/package.json`)
res[name] = { ...scripts }
}
this.npm.output(JSON.stringify(res, null, 2))
return
}

if (this.npm.flatOptions.parseable) {
for (const workspacePath of workspaces.values()) {
const { scripts, name } = await rpj(`${workspacePath}/package.json`)
for (const [script, cmd] of Object.entries(scripts || {}))
this.npm.output(`${name}:${script}:${cmd}`)
}
return
}

for (const workspacePath of workspaces.values())
await this.list(args, workspacePath)
}
}

module.exports = RunScript
4 changes: 4 additions & 0 deletions lib/utils/lifecycle-cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ class LifecycleCmd extends BaseCommand {
exec (args, cb) {
this.npm.commands['run-script']([this.constructor.name, ...args], cb)
}

execWorkspaces (args, filters, cb) {
this.npm.commands['run-script']([this.constructor.name, ...args], cb)
}
}
module.exports = LifecycleCmd
Loading

0 comments on commit d74d5a9

Please sign in to comment.