diff options
author | Ruy Adorno <ruyadorno@hotmail.com> | 2021-03-07 20:13:58 +0300 |
---|---|---|
committer | Ruy Adorno <ruyadorno@hotmail.com> | 2021-03-19 01:14:46 +0300 |
commit | 33c4189f939aebdfaf85ea419e6ea01d0977b79d (patch) | |
tree | d819ff7f8738a216911fe7a2d8ffdc9e18b5353b /test | |
parent | 8a38afe779ce71a10178ed62b13709d06adf7a66 (diff) |
feat: add run-script workspaces
- Add workspaces-related configs:
- workspace: list of workspaces names/dir to filter for
- workspaces: boolean value to enable/disable workspaces awareness
- adds the proposed note in the docs of each of the commands
that are not affected by these configs.
- Add workspaces support to `npm run-script`
- add ability to serially run lifecycle scripts in workspaces
- add ability to list scripts for all workspaces
- add colors to `npm run` (no args) output
Relates to: https://github.com/npm/rfcs/pull/117
Fixes: https://github.com/npm/statusboard/issues/276
Fixes: https://github.com/npm/statusboard/issues/283
Fixes: https://github.com/npm/statusboard/issues/284
Fixes: https://github.com/npm/statusboard/issues/285
Fixes: https://github.com/npm/statusboard/issues/286
PR-URL: https://github.com/npm/cli/pull/2864
Credit: @ruyadorno
Close: #2864
Reviewed-by: @wraithgar
Diffstat (limited to 'test')
-rw-r--r-- | test/lib/npm.js | 78 | ||||
-rw-r--r-- | test/lib/run-script.js | 466 | ||||
-rw-r--r-- | test/lib/utils/did-you-mean.js | 20 | ||||
-rw-r--r-- | test/lib/utils/lifecycle-cmd.js | 6 |
4 files changed, 560 insertions, 10 deletions
diff --git a/test/lib/npm.js b/test/lib/npm.js index eb0f8ab27..de0dcaa1c 100644 --- a/test/lib/npm.js +++ b/test/lib/npm.js @@ -348,6 +348,84 @@ t.test('npm.load', t => { await new Promise((res) => setTimeout(res)) }) + t.test('workpaces-aware configs and commands', async t => { + const dir = t.testdir({ + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { test: 'echo test a' }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + scripts: { test: 'echo test b' }, + }), + }, + }, + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./packages/*'], + }), + '.npmrc': '', + }) + + const { log } = console + const consoleLogs = [] + console.log = (...msg) => consoleLogs.push(msg) + + const { execPath } = process + t.teardown(() => { + console.log = log + }) + + freshConfig({ + argv: [ + execPath, + process.argv[1], + '--userconfig', + resolve(dir, '.npmrc'), + '--color', + 'false', + '--workspaces', + 'true', + ], + }) + + await npm.load(er => { + if (er) + throw er + }) + + npm.localPrefix = dir + + await new Promise((res, rej) => { + npm.commands['run-script']([], er => { + if (er) + rej(er) + + t.match( + consoleLogs, + [ + ['Lifecycle scripts included in a@1.0.0:'], + [' test\n echo test a'], + [''], + ['Lifecycle scripts included in b@1.0.0:'], + [' test\n echo test b'], + [''], + ], + 'should exec workspaces version of commands' + ) + + res() + }) + }) + }) + t.end() }) diff --git a/test/lib/run-script.js b/test/lib/run-script.js index d2cac2f42..db1fc4b5c 100644 --- a/test/lib/run-script.js +++ b/test/lib/run-script.js @@ -1,7 +1,15 @@ +const { resolve } = require('path') const t = require('tap') const requireInject = require('require-inject') const mockNpm = require('../fixtures/mock-npm') +const normalizePath = p => p + .replace(/\\+/g, '/') + .replace(/\r\n/g, '\n') + +const cleanOutput = (str) => normalizePath(str) + .replace(normalizePath(process.cwd()), '{CWD}') + const RUN_SCRIPTS = [] const flatOptions = { scriptShell: undefined, @@ -20,21 +28,33 @@ const npm = mockNpm({ help: { description: 'test help description', }, + test: { + description: 'test test description', + }, }, output: (...msg) => output.push(msg), }) const output = [] +const npmlog = { + disableProgress: () => null, + level: 'warn', + error: () => null, +} + t.afterEach(cb => { + npm.color = false + npmlog.level = 'warn' + npmlog.error = () => null output.length = 0 RUN_SCRIPTS.length = 0 + config['if-present'] = false config.json = false config.parseable = false cb() }) -const npmlog = { level: 'warn' } const getRS = windows => { const RunScript = requireInject('../../lib/run-script.js', { '@npmcli/run-script': Object.assign(async opts => { @@ -298,7 +318,7 @@ t.test('try to run missing script', t => { }) }) t.test('with --if-present', t => { - npm.config.set('if-present', true) + config['if-present'] = true runScript.exec(['goodbye'], er => { if (er) throw er @@ -461,13 +481,14 @@ t.test('list scripts', t => { if (er) throw er t.strictSame(output, [ - ['Lifecycle scripts included in x:'], + ['Lifecycle scripts included in x@1.2.3:'], [' test\n exit 2'], [' start\n node server.js'], [' stop\n node kill-server.js'], ['\navailable via `npm run-script`:'], [' preenv\n echo before the env'], [' postenv\n echo after the env'], + [''], ], 'basic report') t.end() }) @@ -540,8 +561,9 @@ t.test('list scripts, only commands', t => { if (er) throw er t.strictSame(output, [ - ['Lifecycle scripts included in x:'], + ['Lifecycle scripts included in x@1.2.3:'], [' preversion\n echo doing the version dance'], + [''], ]) t.end() }) @@ -560,9 +582,443 @@ t.test('list scripts, only non-commands', t => { if (er) throw er t.strictSame(output, [ - ['Scripts available in x via `npm run-script`:'], + ['Scripts available in x@1.2.3 via `npm run-script`:'], [' glorp\n echo doing the glerp glop'], + [''], ]) t.end() }) }) + +t.test('workspaces', t => { + npm.localPrefix = t.testdir({ + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { glorp: 'echo a doing the glerp glop' }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '2.0.0', + scripts: { glorp: 'echo b doing the glerp glop' }, + }), + }, + c: { + 'package.json': JSON.stringify({ + name: 'c', + version: '1.0.0', + scripts: { + test: 'exit 0', + posttest: 'echo posttest', + lorem: 'echo c lorem', + }, + }), + }, + d: { + 'package.json': JSON.stringify({ + name: 'd', + version: '1.0.0', + scripts: { + test: 'exit 0', + posttest: 'echo posttest', + }, + }), + }, + e: { + 'package.json': JSON.stringify({ + name: 'e', + scripts: { test: 'exit 0', start: 'echo start something' }, + }), + }, + noscripts: { + 'package.json': JSON.stringify({ + name: 'noscripts', + version: '1.0.0', + }), + }, + }, + 'package.json': JSON.stringify({ + name: 'x', + version: '1.2.3', + workspaces: ['packages/*'], + }), + }) + + t.test('list all scripts', t => { + runScript.execWorkspaces([], [], er => { + if (er) + throw er + t.strictSame(output, [ + ['Scripts available in a@1.0.0 via `npm run-script`:'], + [' glorp\n echo a doing the glerp glop'], + [''], + ['Scripts available in b@2.0.0 via `npm run-script`:'], + [' glorp\n echo b doing the glerp glop'], + [''], + ['Lifecycle scripts included in c@1.0.0:'], + [' test\n exit 0'], + [' posttest\n echo posttest'], + ['\navailable via `npm run-script`:'], + [' lorem\n echo c lorem'], + [''], + ['Lifecycle scripts included in d@1.0.0:'], + [' test\n exit 0'], + [' posttest\n echo posttest'], + [''], + ['Lifecycle scripts included in e:'], + [' test\n exit 0'], + [' start\n echo start something'], + [''], + ]) + t.end() + }) + }) + + t.test('list regular scripts, filtered by name', t => { + runScript.execWorkspaces([], ['a', 'b'], er => { + if (er) + throw er + t.strictSame(output, [ + ['Scripts available in a@1.0.0 via `npm run-script`:'], + [' glorp\n echo a doing the glerp glop'], + [''], + ['Scripts available in b@2.0.0 via `npm run-script`:'], + [' glorp\n echo b doing the glerp glop'], + [''], + ]) + t.end() + }) + }) + + t.test('list regular scripts, filtered by path', t => { + runScript.execWorkspaces([], ['./packages/a'], er => { + if (er) + throw er + t.strictSame(output, [ + ['Scripts available in a@1.0.0 via `npm run-script`:'], + [' glorp\n echo a doing the glerp glop'], + [''], + ]) + t.end() + }) + }) + + t.test('list regular scripts, filtered by parent folder', t => { + runScript.execWorkspaces([], ['./packages'], er => { + if (er) + throw er + t.strictSame(output, [ + ['Scripts available in a@1.0.0 via `npm run-script`:'], + [' glorp\n echo a doing the glerp glop'], + [''], + ['Scripts available in b@2.0.0 via `npm run-script`:'], + [' glorp\n echo b doing the glerp glop'], + [''], + ['Lifecycle scripts included in c@1.0.0:'], + [' test\n exit 0'], + [' posttest\n echo posttest'], + ['\navailable via `npm run-script`:'], + [' lorem\n echo c lorem'], + [''], + ['Lifecycle scripts included in d@1.0.0:'], + [' test\n exit 0'], + [' posttest\n echo posttest'], + [''], + ['Lifecycle scripts included in e:'], + [' test\n exit 0'], + [' start\n echo start something'], + [''], + ]) + t.end() + }) + }) + + t.test('list all scripts with colors', t => { + npm.color = true + runScript.execWorkspaces([], [], er => { + if (er) + throw er + t.strictSame(output, [ + [ + '\u001b[1mScripts\u001b[22m available in \x1B[32ma@1.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:', + ], + [' glorp\n \x1B[2mecho a doing the glerp glop\x1B[22m'], + [''], + [ + '\u001b[1mScripts\u001b[22m available in \x1B[32mb@2.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:', + ], + [' glorp\n \x1B[2mecho b doing the glerp glop\x1B[22m'], + [''], + [ + '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32mc@1.0.0\x1B[39m:', + ], + [' test\n \x1B[2mexit 0\x1B[22m'], + [' posttest\n \x1B[2mecho posttest\x1B[22m'], + ['\navailable via `\x1B[34mnpm run-script\x1B[39m`:'], + [' lorem\n \x1B[2mecho c lorem\x1B[22m'], + [''], + [ + '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32md@1.0.0\x1B[39m:', + ], + [' test\n \x1B[2mexit 0\x1B[22m'], + [' posttest\n \x1B[2mecho posttest\x1B[22m'], + [''], + [ + '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32me\x1B[39m:', + ], + [' test\n \x1B[2mexit 0\x1B[22m'], + [' start\n \x1B[2mecho start something\x1B[22m'], + [''], + ]) + t.end() + }) + }) + + t.test('list all scripts --json', t => { + config.json = true + runScript.execWorkspaces([], [], er => { + if (er) + throw er + t.strictSame(output, [ + [ + '{\n' + + ' "a": {\n' + + ' "glorp": "echo a doing the glerp glop"\n' + + ' },\n' + + ' "b": {\n' + + ' "glorp": "echo b doing the glerp glop"\n' + + ' },\n' + + ' "c": {\n' + + ' "test": "exit 0",\n' + + ' "posttest": "echo posttest",\n' + + ' "lorem": "echo c lorem"\n' + + ' },\n' + + ' "d": {\n' + + ' "test": "exit 0",\n' + + ' "posttest": "echo posttest"\n' + + ' },\n' + + ' "e": {\n' + + ' "test": "exit 0",\n' + + ' "start": "echo start something"\n' + + ' },\n' + + ' "noscripts": {}\n' + + '}', + ], + ]) + t.end() + }) + }) + + t.test('list all scripts --parseable', t => { + config.parseable = true + runScript.execWorkspaces([], [], er => { + if (er) + throw er + t.strictSame(output, [ + ['a:glorp:echo a doing the glerp glop'], + ['b:glorp:echo b doing the glerp glop'], + ['c:test:exit 0'], + ['c:posttest:echo posttest'], + ['c:lorem:echo c lorem'], + ['d:test:exit 0'], + ['d:posttest:echo posttest'], + ['e:test:exit 0'], + ['e:start:echo start something'], + ]) + t.end() + }) + }) + + t.test('list no scripts --loglevel=silent', t => { + npmlog.level = 'silent' + runScript.execWorkspaces([], [], er => { + if (er) + throw er + t.strictSame(output, []) + t.end() + }) + }) + + t.test('run scripts across all workspaces', t => { + runScript.execWorkspaces(['test'], [], er => { + if (er) + throw er + + t.match(RUN_SCRIPTS, [ + { + path: resolve(npm.localPrefix, 'packages/c'), + pkg: { name: 'c', version: '1.0.0' }, + event: 'test', + }, + { + path: resolve(npm.localPrefix, 'packages/c'), + pkg: { name: 'c', version: '1.0.0' }, + event: 'posttest', + }, + { + path: resolve(npm.localPrefix, 'packages/d'), + pkg: { name: 'd', version: '1.0.0' }, + event: 'test', + }, + { + path: resolve(npm.localPrefix, 'packages/d'), + pkg: { name: 'd', version: '1.0.0' }, + event: 'posttest', + }, + { + path: resolve(npm.localPrefix, 'packages/e'), + pkg: { name: 'e' }, + event: 'test', + }, + ]) + t.end() + }) + }) + + t.test('missing scripts in all workspaces', t => { + const LOG = [] + npmlog.error = (err) => { + LOG.push(String(err)) + } + runScript.execWorkspaces(['missing-script'], [], er => { + t.match( + er, + /Missing script: missing-script/, + 'should throw missing script error' + ) + + process.exitCode = 0 // clean exit code + + t.match(RUN_SCRIPTS, []) + t.strictSame(LOG.map(cleanOutput), [ + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: a@1.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/a', + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: b@2.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/b', + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: c@1.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/c', + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: d@1.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/d', + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: e', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/e', + 'Lifecycle script `missing-script` failed with error:', + 'Error: Unknown command: "missing-script"', + ' in workspace: noscripts@1.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/noscripts', + ], 'should log error msgs for each workspace script') + + t.end() + }) + }) + + t.test('missing scripts in some workspaces', t => { + const LOG = [] + npmlog.error = (err) => { + LOG.push(String(err)) + } + runScript.execWorkspaces(['test'], ['a', 'b', 'c', 'd'], er => { + if (er) + throw er + + t.match(RUN_SCRIPTS, []) + t.strictSame(LOG.map(cleanOutput), [ + 'Lifecycle script `test` failed with error:', + 'Error: Unknown command: "test"', + ' in workspace: a@1.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/a', + 'Lifecycle script `test` failed with error:', + 'Error: Unknown command: "test"', + ' in workspace: b@2.0.0', + ' at location: {CWD}/test/lib/run-script-workspaces/packages/b', + ], 'should log error msgs for each workspace script') + t.end() + }) + }) + + t.test('no workspaces when filtering by user args', t => { + runScript.execWorkspaces([], ['foo', 'bar'], er => { + t.equal( + er.message, + 'No workspaces found:\n --workspace=foo --workspace=bar', + 'should throw error msg' + ) + t.end() + }) + }) + + t.test('no workspaces', t => { + const _prevPrefix = npm.localPrefix + npm.localPrefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + }), + }) + + runScript.execWorkspaces([], [], er => { + t.match(er, /No workspaces found!/, 'should throw error msg') + npm.localPrefix = _prevPrefix + t.end() + }) + }) + + t.test('single failed workspace run', t => { + const RunScript = requireInject('../../lib/run-script.js', { + '@npmcli/run-script': () => { + throw new Error('err') + }, + npmlog, + '../../lib/utils/is-windows-shell.js': false, + }) + const runScript = new RunScript(npm) + + runScript.execWorkspaces(['test'], ['c'], er => { + t.ok('should complete running all targets') + process.exitCode = 0 // clean up exit code + t.end() + }) + }) + + t.test('failed workspace run with succeeded runs', t => { + const RunScript = requireInject('../../lib/run-script.js', { + '@npmcli/run-script': async opts => { + if (opts.pkg.name === 'a') + throw new Error('ERR') + + RUN_SCRIPTS.push(opts) + }, + npmlog, + '../../lib/utils/is-windows-shell.js': false, + }) + const runScript = new RunScript(npm) + + runScript.execWorkspaces(['glorp'], ['a', 'b'], er => { + t.match(RUN_SCRIPTS, [ + { + path: resolve(npm.localPrefix, 'packages/b'), + pkg: { name: 'b', version: '2.0.0' }, + event: 'glorp', + }, + ]) + + process.exitCode = 0 // clean up exit code + t.end() + }) + }) + + t.end() +}) diff --git a/test/lib/utils/did-you-mean.js b/test/lib/utils/did-you-mean.js index 48b6d4027..898806aa1 100644 --- a/test/lib/utils/did-you-mean.js +++ b/test/lib/utils/did-you-mean.js @@ -7,25 +7,37 @@ t.test('did-you-mean', t => { npm.load(err => { t.notOk(err) t.test('nistall', async t => { - const result = await dym(npm, 'nistall') + const result = await dym(npm, npm.localPrefix, 'nistall') t.match(result, 'Unknown command') t.match(result, 'npm install') }) t.test('sttest', async t => { - const result = await dym(npm, 'sttest') + const result = await dym(npm, npm.localPrefix, 'sttest') t.match(result, 'Unknown command') t.match(result, 'npm test') t.match(result, 'npm run posttest') }) t.test('npz', async t => { - const result = await dym(npm, 'npxx') + const result = await dym(npm, npm.localPrefix, 'npxx') t.match(result, 'Unknown command') t.match(result, 'npm exec npx') }) t.test('qwuijbo', async t => { - const result = await dym(npm, 'qwuijbo') + const result = await dym(npm, npm.localPrefix, 'qwuijbo') t.match(result, 'Unknown command') }) t.end() }) }) + +t.test('missing bin and script properties', async t => { + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'missing-bin', + }), + }) + + const result = await dym(npm, path, 'nistall') + t.match(result, 'Unknown command') + t.match(result, 'npm install') +}) diff --git a/test/lib/utils/lifecycle-cmd.js b/test/lib/utils/lifecycle-cmd.js index 3e3a7da43..862c87a8e 100644 --- a/test/lib/utils/lifecycle-cmd.js +++ b/test/lib/utils/lifecycle-cmd.js @@ -10,6 +10,7 @@ const npm = { }, } t.test('create a lifecycle command', t => { + t.plan(5) class TestStage extends LifecycleCmd { static get name () { return 'test-stage' @@ -20,6 +21,9 @@ t.test('create a lifecycle command', t => { cmd.exec(['some', 'args'], (er, result) => { t.same(runArgs, ['test-stage', 'some', 'args']) t.strictSame(result, 'called npm.commands.run') - t.end() + }) + cmd.execWorkspaces(['some', 'args'], [], (er, result) => { + t.same(runArgs, ['test-stage', 'some', 'args']) + t.strictSame(result, 'called npm.commands.run') }) }) |