Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorRuy Adorno <ruyadorno@hotmail.com>2021-03-07 20:13:58 +0300
committerGar <gar+gh@danger.computer>2021-03-22 20:22:38 +0300
commite1b3b318f095a7e1a7cc4b131907de4955275d9d (patch)
treecb2cf7f6fa70a6eb546431f10bbe8124febe1db5 /test
parentb876442241b9d366a0541714bbee1ae50d6746fd (diff)
feat: add exec workspaces
Add workspaces support to `npm exec` - Refactored logic to read and filter workspaces into `lib/workspaces/get-workspaces.js` - Added location context message when entering interactive shell using `npm exec` (with no args) - Add ability to execute a package in the context of each configured workspace Fixes: https://github.com/npm/statusboard/issues/288 PR-URL: https://github.com/npm/cli/pull/2886 Credit: @ruyadorno Close: #2886 Reviewed-by: @wraithgar
Diffstat (limited to 'test')
-rw-r--r--test/lib/exec.js110
-rw-r--r--test/lib/workspaces/get-workspaces.js199
2 files changed, 306 insertions, 3 deletions
diff --git a/test/lib/exec.js b/test/lib/exec.js
index 5e859a57a..7104795f6 100644
--- a/test/lib/exec.js
+++ b/test/lib/exec.js
@@ -108,6 +108,7 @@ t.afterEach(cb => {
LOG_WARN.length = 0
PROGRESS_IGNORED = false
flatOptions.legacyPeerDeps = false
+ config.color = false
config.package = []
flatOptions.package = []
config.call = ''
@@ -241,14 +242,27 @@ t.test('npm exec <noargs>, run interactive shell', t => {
cb()
})
}
-
t.test('print message when tty and not in CI', t => {
CI_NAME = null
process.stdin.isTTY = true
run(t, true, () => {
t.strictSame(LOG_WARN, [])
t.strictSame(OUTPUT, [
- ['\nEntering npm script environment\nType \'exit\' or ^D when finished\n'],
+ [`\nEntering npm script environment at location:\n${process.cwd()}\nType 'exit' or ^D when finished\n`],
+ ], 'printed message about interactive shell')
+ t.end()
+ })
+ })
+
+ t.test('print message with color when tty and not in CI', t => {
+ CI_NAME = null
+ process.stdin.isTTY = true
+ config.color = true
+
+ run(t, true, () => {
+ t.strictSame(LOG_WARN, [])
+ t.strictSame(OUTPUT, [
+ [`\u001b[0m\u001b[0m\n\u001b[0mEntering npm script environment\u001b[0m\u001b[0m at location:\u001b[0m\n\u001b[0m\u001b[2m${process.cwd()}\u001b[22m\u001b[0m\u001b[1m\u001b[22m\n\u001b[1mType 'exit' or ^D when finished\u001b[22m\n\u001b[1m\u001b[22m`],
], 'printed message about interactive shell')
t.end()
})
@@ -419,7 +433,7 @@ t.test('npm exec --package=foo bar', t => {
if (er)
throw er
t.strictSame(MKDIRPS, [], 'no need to make any dirs')
- t.match(ARB_CTOR, [{ package: ['foo'], path }])
+ t.match(ARB_CTOR, [{ path }])
t.strictSame(ARB_REIFY, [], 'no need to reify anything')
t.equal(PROGRESS_ENABLED, true, 'progress re-enabled')
t.match(RUN_SCRIPTS, [{
@@ -1084,3 +1098,93 @@ t.test('forward legacyPeerDeps opt', t => {
t.done()
})
})
+
+t.test('workspaces', t => {
+ npm.localPrefix = t.testdir({
+ node_modules: {
+ '.bin': {
+ foo: '',
+ },
+ },
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ bin: 'cli.js',
+ }),
+ 'cli.js': '',
+ },
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package.json': JSON.stringify({
+ name: 'root',
+ version: '1.0.0',
+ workspaces: ['packages/*'],
+ }),
+ })
+
+ PROGRESS_IGNORED = true
+ npm.localBin = resolve(npm.localPrefix, 'node_modules/.bin')
+
+ t.test('with args, run scripts in the context of a workspace', t => {
+ exec.execWorkspaces(['foo', 'one arg', 'two arg'], ['a', 'b'], er => {
+ if (er)
+ throw er
+
+ t.match(RUN_SCRIPTS, [{
+ pkg: { scripts: { npx: 'foo' }},
+ args: ['one arg', 'two arg'],
+ banner: false,
+ path: process.cwd(),
+ stdioString: true,
+ event: 'npx',
+ env: {
+ PATH: [npm.localBin, ...PATH].join(delimiter),
+ },
+ stdio: 'inherit',
+ }])
+ t.end()
+ })
+ })
+
+ t.test('no args, spawn interactive shell', async t => {
+ CI_NAME = null
+ process.stdin.isTTY = true
+
+ await new Promise((res, rej) => {
+ exec.execWorkspaces([], ['a'], er => {
+ if (er)
+ return rej(er)
+
+ t.strictSame(LOG_WARN, [])
+ t.strictSame(OUTPUT, [
+ [`\nEntering npm script environment in workspace a@1.0.0 at location:\n${resolve(npm.localPrefix, 'packages/a')}\nType 'exit' or ^D when finished\n`],
+ ], 'printed message about interactive shell')
+ res()
+ })
+ })
+
+ config.color = true
+ OUTPUT.length = 0
+ await new Promise((res, rej) => {
+ exec.execWorkspaces([], ['a'], er => {
+ if (er)
+ return rej(er)
+
+ t.strictSame(LOG_WARN, [])
+ t.strictSame(OUTPUT, [
+ [`\u001b[0m\u001b[0m\n\u001b[0mEntering npm script environment\u001b[0m\u001b[0m in workspace \u001b[32ma@1.0.0\u001b[39m at location:\u001b[0m\n\u001b[0m\u001b[2m${resolve(npm.localPrefix, 'packages/a')}\u001b[22m\u001b[0m\u001b[1m\u001b[22m\n\u001b[1mType 'exit' or ^D when finished\u001b[22m\n\u001b[1m\u001b[22m`],
+ ], 'printed message about interactive shell')
+ res()
+ })
+ })
+ })
+
+ t.end()
+})
diff --git a/test/lib/workspaces/get-workspaces.js b/test/lib/workspaces/get-workspaces.js
new file mode 100644
index 000000000..ebed9dd35
--- /dev/null
+++ b/test/lib/workspaces/get-workspaces.js
@@ -0,0 +1,199 @@
+const { resolve } = require('path')
+const t = require('tap')
+const getWorkspaces = require('../../../lib/workspaces/get-workspaces.js')
+
+const normalizePath = p => p
+ .replace(/\\+/g, '/')
+ .replace(/\r\n/g, '\n')
+
+const cleanOutput = (str, path) => normalizePath(str)
+ .replace(normalizePath(path), '{PATH}')
+
+const clean = (res, path) => {
+ const cleaned = new Map()
+ for (const [key, value] of res.entries())
+ cleaned.set(key, cleanOutput(value, path))
+ return cleaned
+}
+
+t.test('get-workspaces', async t => {
+ const path = 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/*'],
+ }),
+ })
+
+ let workspaces
+
+ workspaces = await getWorkspaces(['a', 'b'], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ a: '{PATH}/packages/a',
+ b: '{PATH}/packages/b',
+ })),
+ 'should filter by package name'
+ )
+
+ workspaces = await getWorkspaces(['./packages/c'], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ c: '{PATH}/packages/c',
+ })),
+ 'should filter by package directory'
+ )
+
+ workspaces = await getWorkspaces(['packages/c'], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ c: '{PATH}/packages/c',
+ })),
+ 'should filter by rel package directory'
+ )
+
+ workspaces = await getWorkspaces([resolve(path, 'packages/c')], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ c: '{PATH}/packages/c',
+ })),
+ 'should filter by absolute package directory'
+ )
+
+ workspaces = await getWorkspaces(['packages'], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ a: '{PATH}/packages/a',
+ b: '{PATH}/packages/b',
+ c: '{PATH}/packages/c',
+ d: '{PATH}/packages/d',
+ e: '{PATH}/packages/e',
+ noscripts: '{PATH}/packages/noscripts',
+ })),
+ 'should filter by parent directory name'
+ )
+
+ workspaces = await getWorkspaces(['./packages/'], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ a: '{PATH}/packages/a',
+ b: '{PATH}/packages/b',
+ c: '{PATH}/packages/c',
+ d: '{PATH}/packages/d',
+ e: '{PATH}/packages/e',
+ noscripts: '{PATH}/packages/noscripts',
+ })),
+ 'should filter by parent directory path'
+ )
+
+ workspaces = await getWorkspaces([resolve(path, './packages')], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ a: '{PATH}/packages/a',
+ b: '{PATH}/packages/b',
+ c: '{PATH}/packages/c',
+ d: '{PATH}/packages/d',
+ e: '{PATH}/packages/e',
+ noscripts: '{PATH}/packages/noscripts',
+ })),
+ 'should filter by absolute parent directory path'
+ )
+
+ workspaces = await getWorkspaces([], { path })
+ t.deepEqual(
+ clean(workspaces, path),
+ new Map(Object.entries({
+ a: '{PATH}/packages/a',
+ b: '{PATH}/packages/b',
+ c: '{PATH}/packages/c',
+ d: '{PATH}/packages/d',
+ e: '{PATH}/packages/e',
+ noscripts: '{PATH}/packages/noscripts',
+ })),
+ 'should return all workspaces if no filter set'
+ )
+
+ try {
+ await getWorkspaces(['missing'], { path })
+ throw new Error('missed throw')
+ } catch (err) {
+ t.match(
+ err,
+ /No workspaces found/,
+ 'should throw no workspaces found error'
+ )
+ }
+
+ const unconfiguredWorkspaces = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'no-configured-workspaces',
+ version: '1.0.0',
+ }),
+ })
+ try {
+ await getWorkspaces([], { path: unconfiguredWorkspaces })
+ throw new Error('missed throw')
+ } catch (err) {
+ t.match(
+ err,
+ /No workspaces found/,
+ 'should throw no workspaces found error'
+ )
+ }
+})