• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const { resolve } = require('path')
3const realRunScript = require('@npmcli/run-script')
4const mockNpm = require('../../fixtures/mock-npm')
5const { cleanCwd } = require('../../fixtures/clean-snapshot')
6
7const mockRs = async (t, { windows = false, runScript, ...opts } = {}) => {
8  let RUN_SCRIPTS = []
9
10  t.afterEach(() => RUN_SCRIPTS = [])
11
12  const mock = await mockNpm(t, {
13    ...opts,
14    command: 'run-script',
15    mocks: {
16      '@npmcli/run-script': Object.assign(
17        async rs => {
18          if (runScript) {
19            await runScript(rs)
20          }
21          RUN_SCRIPTS.push(rs)
22        },
23        realRunScript
24      ),
25      '{LIB}/utils/is-windows.js': { isWindowsShell: windows },
26    },
27  })
28
29  return {
30    ...mock,
31    RUN_SCRIPTS: () => RUN_SCRIPTS,
32    runScript: mock['run-script'],
33    cleanLogs: () => mock.logs.error.flat().map(v => v.toString()).map(cleanCwd),
34  }
35}
36
37t.test('completion', async t => {
38  const completion = async (t, remain, pkg, isFish = false) => {
39    const { runScript } = await mockRs(t,
40      pkg ? { prefixDir: { 'package.json': JSON.stringify(pkg) } } : {}
41    )
42    return runScript.completion({ conf: { argv: { remain } }, isFish })
43  }
44
45  t.test('already have a script name', async t => {
46    const res = await completion(t, ['npm', 'run', 'x'])
47    t.equal(res, undefined)
48  })
49  t.test('no package.json', async t => {
50    const res = await completion(t, ['npm', 'run'])
51    t.strictSame(res, [])
52  })
53  t.test('has package.json, no scripts', async t => {
54    const res = await completion(t, ['npm', 'run'], {})
55    t.strictSame(res, [])
56  })
57  t.test('has package.json, with scripts', async t => {
58    const res = await completion(t, ['npm', 'run'], {
59      scripts: { hello: 'echo hello', world: 'echo world' },
60    })
61    t.strictSame(res, ['hello', 'world'])
62  })
63
64  t.test('fish shell', async t => {
65    const res = await completion(t, ['npm', 'run'], {
66      scripts: { hello: 'echo hello', world: 'echo world' },
67    }, true)
68    t.strictSame(res, ['hello\techo hello', 'world\techo world'])
69  })
70})
71
72t.test('fail if no package.json', async t => {
73  const { runScript } = await mockRs(t)
74  await t.rejects(runScript.exec([]), { code: 'ENOENT' })
75  await t.rejects(runScript.exec(['test']), { code: 'ENOENT' })
76})
77
78t.test('default env, start, and restart scripts', async t => {
79  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
80    prefixDir: {
81      'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }),
82      'server.js': 'console.log("hello, world")',
83    },
84  })
85
86  t.test('start', async t => {
87    await runScript.exec(['start'])
88    t.match(RUN_SCRIPTS(), [
89      {
90        path: npm.localPrefix,
91        args: [],
92        scriptShell: undefined,
93        stdio: 'inherit',
94        pkg: { name: 'x', version: '1.2.3', _id: 'x@1.2.3', scripts: {} },
95        event: 'start',
96      },
97    ])
98  })
99
100  t.test('env', async t => {
101    await runScript.exec(['env'])
102    t.match(RUN_SCRIPTS(), [
103      {
104        path: npm.localPrefix,
105        args: [],
106        scriptShell: undefined,
107        stdio: 'inherit',
108        pkg: {
109          name: 'x',
110          version: '1.2.3',
111          _id: 'x@1.2.3',
112          scripts: {
113            env: 'env',
114          },
115        },
116        event: 'env',
117      },
118    ])
119  })
120
121  t.test('restart', async t => {
122    await runScript.exec(['restart'])
123
124    t.match(RUN_SCRIPTS(), [
125      {
126        path: npm.localPrefix,
127        args: [],
128        scriptShell: undefined,
129        stdio: 'inherit',
130        pkg: {
131          name: 'x',
132          version: '1.2.3',
133          _id: 'x@1.2.3',
134          scripts: {
135            restart: 'npm stop --if-present && npm start',
136          },
137        },
138        event: 'restart',
139      },
140    ])
141  })
142})
143
144t.test('default windows env', async t => {
145  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
146    windows: true,
147    prefixDir: {
148      'package.json': JSON.stringify({ name: 'x', version: '1.2.3' }),
149      'server.js': 'console.log("hello, world")',
150    },
151  })
152  await runScript.exec(['env'])
153  t.match(RUN_SCRIPTS(), [
154    {
155      path: npm.localPrefix,
156      args: [],
157      scriptShell: undefined,
158      stdio: 'inherit',
159      pkg: {
160        name: 'x',
161        version: '1.2.3',
162        _id: 'x@1.2.3',
163        scripts: {
164          env: 'SET',
165        },
166      },
167      event: 'env',
168    },
169  ])
170})
171
172t.test('non-default env script', async t => {
173  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
174    prefixDir: {
175      'package.json': JSON.stringify({
176        name: 'x',
177        version: '1.2.3',
178        scripts: {
179          env: 'hello',
180        },
181      }),
182    },
183  })
184
185  t.test('env', async t => {
186    await runScript.exec(['env'])
187    t.match(RUN_SCRIPTS(), [
188      {
189        path: npm.localPrefix,
190        args: [],
191        scriptShell: undefined,
192        stdio: 'inherit',
193        pkg: {
194          name: 'x',
195          version: '1.2.3',
196          _id: 'x@1.2.3',
197          scripts: {
198            env: 'hello',
199          },
200        },
201        event: 'env',
202      },
203    ])
204  })
205})
206
207t.test('non-default env script windows', async t => {
208  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
209    windows: true,
210    prefixDir: {
211      'package.json': JSON.stringify({
212        name: 'x',
213        version: '1.2.3',
214        scripts: {
215          env: 'hello',
216        },
217      }),
218    },
219  })
220
221  await runScript.exec(['env'])
222
223  t.match(RUN_SCRIPTS(), [
224    {
225      path: npm.localPrefix,
226      args: [],
227      scriptShell: undefined,
228      stdio: 'inherit',
229      pkg: {
230        name: 'x',
231        version: '1.2.3',
232        _id: 'x@1.2.3',
233        scripts: {
234          env: 'hello',
235        },
236      },
237      event: 'env',
238    },
239  ])
240})
241
242t.test('try to run missing script', async t => {
243  t.test('errors', async t => {
244    const { runScript } = await mockRs(t, {
245      prefixDir: {
246        'package.json': JSON.stringify({
247          scripts: { hello: 'world' },
248          bin: { goodnight: 'moon' },
249        }),
250      },
251    })
252    t.test('no suggestions', async t => {
253      await t.rejects(runScript.exec(['notevenclose']), 'Missing script: "notevenclose"')
254    })
255    t.test('script suggestions', async t => {
256      await t.rejects(runScript.exec(['helo']), /Missing script: "helo"/)
257      await t.rejects(runScript.exec(['helo']), /npm run hello/)
258    })
259    t.test('bin suggestions', async t => {
260      await t.rejects(runScript.exec(['goodneght']), /Missing script: "goodneght"/)
261      await t.rejects(runScript.exec(['goodneght']), /npm exec goodnight/)
262    })
263  })
264
265  t.test('with --if-present', async t => {
266    const { runScript, RUN_SCRIPTS } = await mockRs(t, {
267      config: { 'if-present': true },
268      prefixDir: {
269        'package.json': JSON.stringify({
270          scripts: { hello: 'world' },
271          bin: { goodnight: 'moon' },
272        }),
273      },
274    })
275    await runScript.exec(['goodbye'])
276    t.strictSame(RUN_SCRIPTS(), [], 'did not try to run anything')
277  })
278})
279
280t.test('run pre/post hooks', async t => {
281  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
282    prefixDir: {
283      'package.json': JSON.stringify({
284        name: 'x',
285        version: '1.2.3',
286        scripts: {
287          preenv: 'echo before the env',
288          postenv: 'echo after the env',
289        },
290      }),
291    },
292  })
293
294  await runScript.exec(['env'])
295
296  t.match(RUN_SCRIPTS(), [
297    { event: 'preenv' },
298    {
299      path: npm.localPrefix,
300      args: [],
301      scriptShell: undefined,
302      stdio: 'inherit',
303      pkg: {
304        name: 'x',
305        version: '1.2.3',
306        _id: 'x@1.2.3',
307        scripts: {
308          env: 'env',
309        },
310      },
311      event: 'env',
312    },
313    { event: 'postenv' },
314  ])
315})
316
317t.test('skip pre/post hooks when using ignoreScripts', async t => {
318  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
319    prefixDir: {
320      'package.json': JSON.stringify({
321        name: 'x',
322        version: '1.2.3',
323        scripts: {
324          preenv: 'echo before the env',
325          postenv: 'echo after the env',
326        },
327      }),
328    },
329    config: { 'ignore-scripts': true },
330  })
331
332  await runScript.exec(['env'])
333
334  t.same(RUN_SCRIPTS(), [
335    {
336      path: npm.localPrefix,
337      args: [],
338      scriptShell: undefined,
339      stdio: 'inherit',
340      pkg: {
341        name: 'x',
342        version: '1.2.3',
343        _id: 'x@1.2.3',
344        scripts: {
345          preenv: 'echo before the env',
346          postenv: 'echo after the env',
347          env: 'env',
348        },
349      },
350      banner: true,
351      event: 'env',
352    },
353  ])
354})
355
356t.test('run silent', async t => {
357  const { npm, runScript, RUN_SCRIPTS } = await mockRs(t, {
358    prefixDir: {
359      'package.json': JSON.stringify({
360        name: 'x',
361        version: '1.2.3',
362        scripts: {
363          preenv: 'echo before the env',
364          postenv: 'echo after the env',
365        },
366      }),
367    },
368    config: { silent: true },
369  })
370
371  await runScript.exec(['env'])
372  t.match(RUN_SCRIPTS(), [
373    {
374      event: 'preenv',
375      stdio: 'inherit',
376    },
377    {
378      path: npm.localPrefix,
379      args: [],
380      scriptShell: undefined,
381      stdio: 'inherit',
382      pkg: {
383        name: 'x',
384        version: '1.2.3',
385        _id: 'x@1.2.3',
386        scripts: {
387          env: 'env',
388        },
389      },
390      event: 'env',
391      banner: false,
392    },
393    {
394      event: 'postenv',
395      stdio: 'inherit',
396    },
397  ])
398})
399
400t.test('list scripts', async t => {
401  const scripts = {
402    test: 'exit 2',
403    start: 'node server.js',
404    stop: 'node kill-server.js',
405    preenv: 'echo before the env',
406    postenv: 'echo after the env',
407  }
408
409  const mockList = async (t, config = {}) => {
410    const mock = await mockRs(t, {
411      prefixDir: {
412        'package.json': JSON.stringify({
413          name: 'x',
414          version: '1.2.3',
415          scripts,
416        }),
417      },
418      config,
419    })
420
421    await mock.runScript.exec([])
422
423    return mock.outputs
424  }
425
426  t.test('no args', async t => {
427    const output = await mockList(t)
428    t.strictSame(
429      output,
430      [
431        ['Lifecycle scripts included in x@1.2.3:'],
432        ['  test\n    exit 2'],
433        ['  start\n    node server.js'],
434        ['  stop\n    node kill-server.js'],
435        ['\navailable via `npm run-script`:'],
436        ['  preenv\n    echo before the env'],
437        ['  postenv\n    echo after the env'],
438        [''],
439      ],
440      'basic report'
441    )
442  })
443
444  t.test('silent', async t => {
445    const outputs = await mockList(t, { silent: true })
446    t.strictSame(outputs, [])
447  })
448  t.test('warn json', async t => {
449    const outputs = await mockList(t, { json: true })
450    t.strictSame(outputs, [[JSON.stringify(scripts, 0, 2)]], 'json report')
451  })
452
453  t.test('parseable', async t => {
454    const outputs = await mockList(t, { parseable: true })
455    t.strictSame(outputs, [
456      ['test:exit 2'],
457      ['start:node server.js'],
458      ['stop:node kill-server.js'],
459      ['preenv:echo before the env'],
460      ['postenv:echo after the env'],
461    ])
462  })
463})
464
465t.test('list scripts when no scripts', async t => {
466  const { runScript, outputs } = await mockRs(t, {
467    prefixDir: {
468      'package.json': JSON.stringify({
469        name: 'x',
470        version: '1.2.3',
471      }),
472    },
473  })
474
475  await runScript.exec([])
476  t.strictSame(outputs, [], 'nothing to report')
477})
478
479t.test('list scripts, only commands', async t => {
480  const { runScript, outputs } = await mockRs(t, {
481    prefixDir: {
482      'package.json': JSON.stringify({
483        name: 'x',
484        version: '1.2.3',
485        scripts: { preversion: 'echo doing the version dance' },
486      }),
487    },
488  })
489
490  await runScript.exec([])
491  t.strictSame(outputs, [
492    ['Lifecycle scripts included in x@1.2.3:'],
493    ['  preversion\n    echo doing the version dance'],
494    [''],
495  ])
496})
497
498t.test('list scripts, only non-commands', async t => {
499  const { runScript, outputs } = await mockRs(t, {
500    prefixDir: {
501      'package.json': JSON.stringify({
502        name: 'x',
503        version: '1.2.3',
504        scripts: { glorp: 'echo doing the glerp glop' },
505      }),
506    },
507  })
508
509  await runScript.exec([])
510  t.strictSame(outputs, [
511    ['Scripts available in x@1.2.3 via `npm run-script`:'],
512    ['  glorp\n    echo doing the glerp glop'],
513    [''],
514  ])
515})
516
517t.test('workspaces', async t => {
518  const mockWorkspaces = async (t, {
519    runScript,
520    prefixDir,
521    workspaces = true,
522    exec = [],
523    ...config
524  } = {}) => {
525    const mock = await mockRs(t, {
526      prefixDir: prefixDir || {
527        packages: {
528          a: {
529            'package.json': JSON.stringify({
530              name: 'a',
531              version: '1.0.0',
532              scripts: { glorp: 'echo a doing the glerp glop' },
533            }),
534          },
535          b: {
536            'package.json': JSON.stringify({
537              name: 'b',
538              version: '2.0.0',
539              scripts: { glorp: 'echo b doing the glerp glop' },
540            }),
541          },
542          c: {
543            'package.json': JSON.stringify({
544              name: 'c',
545              version: '1.0.0',
546              scripts: {
547                test: 'exit 0',
548                posttest: 'echo posttest',
549                lorem: 'echo c lorem',
550              },
551            }),
552          },
553          d: {
554            'package.json': JSON.stringify({
555              name: 'd',
556              version: '1.0.0',
557              scripts: {
558                test: 'exit 0',
559                posttest: 'echo posttest',
560              },
561            }),
562          },
563          e: {
564            'package.json': JSON.stringify({
565              name: 'e',
566              scripts: { test: 'exit 0', start: 'echo start something' },
567            }),
568          },
569          noscripts: {
570            'package.json': JSON.stringify({
571              name: 'noscripts',
572              version: '1.0.0',
573            }),
574          },
575        },
576        'package.json': JSON.stringify({
577          name: 'x',
578          version: '1.2.3',
579          workspaces: ['packages/*'],
580        }),
581      },
582      config: {
583        ...Array.isArray(workspaces) ? { workspace: workspaces } : { workspaces },
584        ...config,
585      },
586      runScript,
587    })
588    if (exec) {
589      await mock.runScript.exec(exec)
590    }
591    return mock
592  }
593
594  t.test('list all scripts', async t => {
595    const { outputs } = await mockWorkspaces(t)
596    t.strictSame(outputs, [
597      ['Scripts available in a@1.0.0 via `npm run-script`:'],
598      ['  glorp\n    echo a doing the glerp glop'],
599      [''],
600      ['Scripts available in b@2.0.0 via `npm run-script`:'],
601      ['  glorp\n    echo b doing the glerp glop'],
602      [''],
603      ['Lifecycle scripts included in c@1.0.0:'],
604      ['  test\n    exit 0'],
605      ['  posttest\n    echo posttest'],
606      ['\navailable via `npm run-script`:'],
607      ['  lorem\n    echo c lorem'],
608      [''],
609      ['Lifecycle scripts included in d@1.0.0:'],
610      ['  test\n    exit 0'],
611      ['  posttest\n    echo posttest'],
612      [''],
613      ['Lifecycle scripts included in e:'],
614      ['  test\n    exit 0'],
615      ['  start\n    echo start something'],
616      [''],
617    ])
618  })
619
620  t.test('list regular scripts, filtered by name', async t => {
621    const { outputs } = await mockWorkspaces(t, { workspaces: ['a', 'b'] })
622    t.strictSame(outputs, [
623      ['Scripts available in a@1.0.0 via `npm run-script`:'],
624      ['  glorp\n    echo a doing the glerp glop'],
625      [''],
626      ['Scripts available in b@2.0.0 via `npm run-script`:'],
627      ['  glorp\n    echo b doing the glerp glop'],
628      [''],
629    ])
630  })
631
632  t.test('list regular scripts, filtered by path', async t => {
633    const { outputs } = await mockWorkspaces(t, { workspaces: ['./packages/a'] })
634    t.strictSame(outputs, [
635      ['Scripts available in a@1.0.0 via `npm run-script`:'],
636      ['  glorp\n    echo a doing the glerp glop'],
637      [''],
638    ])
639  })
640
641  t.test('list regular scripts, filtered by parent folder', async t => {
642    const { outputs } = await mockWorkspaces(t, { workspaces: ['./packages'] })
643    t.strictSame(outputs, [
644      ['Scripts available in a@1.0.0 via `npm run-script`:'],
645      ['  glorp\n    echo a doing the glerp glop'],
646      [''],
647      ['Scripts available in b@2.0.0 via `npm run-script`:'],
648      ['  glorp\n    echo b doing the glerp glop'],
649      [''],
650      ['Lifecycle scripts included in c@1.0.0:'],
651      ['  test\n    exit 0'],
652      ['  posttest\n    echo posttest'],
653      ['\navailable via `npm run-script`:'],
654      ['  lorem\n    echo c lorem'],
655      [''],
656      ['Lifecycle scripts included in d@1.0.0:'],
657      ['  test\n    exit 0'],
658      ['  posttest\n    echo posttest'],
659      [''],
660      ['Lifecycle scripts included in e:'],
661      ['  test\n    exit 0'],
662      ['  start\n    echo start something'],
663      [''],
664    ])
665  })
666
667  t.test('list all scripts with colors', async t => {
668    const { outputs } = await mockWorkspaces(t, { color: 'always' })
669    t.strictSame(outputs, [
670      [
671        /* eslint-disable-next-line max-len */
672        '\u001b[1mScripts\u001b[22m available in \x1B[32ma@1.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:',
673      ],
674      ['  glorp\n    \x1B[2mecho a doing the glerp glop\x1B[22m'],
675      [''],
676      [
677        /* eslint-disable-next-line max-len */
678        '\u001b[1mScripts\u001b[22m available in \x1B[32mb@2.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:',
679      ],
680      ['  glorp\n    \x1B[2mecho b doing the glerp glop\x1B[22m'],
681      [''],
682      ['\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32mc@1.0.0\x1B[39m:'],
683      ['  test\n    \x1B[2mexit 0\x1B[22m'],
684      ['  posttest\n    \x1B[2mecho posttest\x1B[22m'],
685      ['\navailable via `\x1B[34mnpm run-script\x1B[39m`:'],
686      ['  lorem\n    \x1B[2mecho c lorem\x1B[22m'],
687      [''],
688      ['\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32md@1.0.0\x1B[39m:'],
689      ['  test\n    \x1B[2mexit 0\x1B[22m'],
690      ['  posttest\n    \x1B[2mecho posttest\x1B[22m'],
691      [''],
692      ['\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32me\x1B[39m:'],
693      ['  test\n    \x1B[2mexit 0\x1B[22m'],
694      ['  start\n    \x1B[2mecho start something\x1B[22m'],
695      [''],
696    ])
697  })
698
699  t.test('list all scripts --json', async t => {
700    const { outputs } = await mockWorkspaces(t, { json: true })
701    t.strictSame(outputs, [
702      [
703        '{\n' +
704          '  "a": {\n' +
705          '    "glorp": "echo a doing the glerp glop"\n' +
706          '  },\n' +
707          '  "b": {\n' +
708          '    "glorp": "echo b doing the glerp glop"\n' +
709          '  },\n' +
710          '  "c": {\n' +
711          '    "test": "exit 0",\n' +
712          '    "posttest": "echo posttest",\n' +
713          '    "lorem": "echo c lorem"\n' +
714          '  },\n' +
715          '  "d": {\n' +
716          '    "test": "exit 0",\n' +
717          '    "posttest": "echo posttest"\n' +
718          '  },\n' +
719          '  "e": {\n' +
720          '    "test": "exit 0",\n' +
721          '    "start": "echo start something"\n' +
722          '  },\n' +
723          '  "noscripts": {}\n' +
724          '}',
725      ],
726    ])
727  })
728
729  t.test('list all scripts --parseable', async t => {
730    const { outputs } = await mockWorkspaces(t, { parseable: true })
731    t.strictSame(outputs, [
732      ['a:glorp:echo a doing the glerp glop'],
733      ['b:glorp:echo b doing the glerp glop'],
734      ['c:test:exit 0'],
735      ['c:posttest:echo posttest'],
736      ['c:lorem:echo c lorem'],
737      ['d:test:exit 0'],
738      ['d:posttest:echo posttest'],
739      ['e:test:exit 0'],
740      ['e:start:echo start something'],
741    ])
742  })
743
744  t.test('list no scripts --loglevel=silent', async t => {
745    const { outputs } = await mockWorkspaces(t, { silent: true })
746    t.strictSame(outputs, [])
747  })
748
749  t.test('run scripts across all workspaces', async t => {
750    const { npm, RUN_SCRIPTS } = await mockWorkspaces(t, { exec: ['test'] })
751
752    t.match(RUN_SCRIPTS(), [
753      {
754        path: resolve(npm.localPrefix, 'packages/c'),
755        pkg: { name: 'c', version: '1.0.0' },
756        event: 'test',
757      },
758      {
759        path: resolve(npm.localPrefix, 'packages/c'),
760        pkg: { name: 'c', version: '1.0.0' },
761        event: 'posttest',
762      },
763      {
764        path: resolve(npm.localPrefix, 'packages/d'),
765        pkg: { name: 'd', version: '1.0.0' },
766        event: 'test',
767      },
768      {
769        path: resolve(npm.localPrefix, 'packages/d'),
770        pkg: { name: 'd', version: '1.0.0' },
771        event: 'posttest',
772      },
773      {
774        path: resolve(npm.localPrefix, 'packages/e'),
775        pkg: { name: 'e' },
776        event: 'test',
777      },
778    ])
779  })
780
781  t.test('missing scripts in all workspaces', async t => {
782    const { runScript, RUN_SCRIPTS, cleanLogs } = await mockWorkspaces(t, { exec: null })
783
784    await runScript.exec(['missing-script'])
785    t.match(RUN_SCRIPTS(), [])
786    t.strictSame(
787      cleanLogs(),
788      [
789        'Lifecycle script `missing-script` failed with error:',
790        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
791        '  in workspace: a@1.0.0',
792        '  at location: {CWD}/prefix/packages/a',
793        'Lifecycle script `missing-script` failed with error:',
794        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
795        '  in workspace: b@2.0.0',
796        '  at location: {CWD}/prefix/packages/b',
797        'Lifecycle script `missing-script` failed with error:',
798        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
799        '  in workspace: c@1.0.0',
800        '  at location: {CWD}/prefix/packages/c',
801        'Lifecycle script `missing-script` failed with error:',
802        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
803        '  in workspace: d@1.0.0',
804        '  at location: {CWD}/prefix/packages/d',
805        'Lifecycle script `missing-script` failed with error:',
806        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
807        '  in workspace: e',
808        '  at location: {CWD}/prefix/packages/e',
809        'Lifecycle script `missing-script` failed with error:',
810        'Error: Missing script: "missing-script"\n\nTo see a list of scripts, run:\n  npm run',
811        '  in workspace: noscripts@1.0.0',
812        '  at location: {CWD}/prefix/packages/noscripts',
813      ],
814      'should log error msgs for each workspace script'
815    )
816  })
817
818  t.test('missing scripts in some workspaces', async t => {
819    const { RUN_SCRIPTS, cleanLogs } = await mockWorkspaces(t, {
820      exec: ['test'],
821      workspaces: ['a', 'b', 'c', 'd'],
822    })
823
824    t.match(RUN_SCRIPTS(), [])
825    t.strictSame(
826      cleanLogs(),
827      [
828        'Lifecycle script `test` failed with error:',
829        'Error: Missing script: "test"\n\nTo see a list of scripts, run:\n  npm run',
830        '  in workspace: a@1.0.0',
831        '  at location: {CWD}/prefix/packages/a',
832        'Lifecycle script `test` failed with error:',
833        'Error: Missing script: "test"\n\nTo see a list of scripts, run:\n  npm run',
834        '  in workspace: b@2.0.0',
835        '  at location: {CWD}/prefix/packages/b',
836      ],
837      'should log error msgs for each workspace script'
838    )
839  })
840
841  t.test('no workspaces when filtering by user args', async t => {
842    await t.rejects(
843      mockWorkspaces(t, { workspaces: ['foo', 'bar'] }),
844      'No workspaces found:\n  --workspace=foo --workspace=bar',
845      'should throw error msg'
846    )
847  })
848
849  t.test('no workspaces', async t => {
850    await t.rejects(
851      mockWorkspaces(t, {
852        prefixDir: {
853          'package.json': JSON.stringify({
854            name: 'foo',
855            version: '1.0.0',
856          }),
857        },
858      }),
859      /No workspaces found!/,
860      'should throw error msg'
861    )
862  })
863
864  t.test('single failed workspace run', async t => {
865    const { cleanLogs } = await mockWorkspaces(t, {
866      runScript: () => {
867        throw new Error('err')
868      },
869      exec: ['test'],
870      workspaces: ['c'],
871    })
872
873    t.strictSame(
874      cleanLogs(),
875      [
876        'Lifecycle script `test` failed with error:',
877        'Error: err',
878        '  in workspace: c@1.0.0',
879        '  at location: {CWD}/prefix/packages/c',
880      ],
881      'should log error msgs for each workspace script'
882    )
883  })
884
885  t.test('failed workspace run with succeeded runs', async t => {
886    const { cleanLogs, RUN_SCRIPTS, prefix } = await mockWorkspaces(t, {
887      runScript: (opts) => {
888        if (opts.pkg.name === 'a') {
889          throw new Error('ERR')
890        }
891      },
892      exec: ['glorp'],
893      workspaces: ['a', 'b'],
894    })
895
896    t.strictSame(
897      cleanLogs(),
898      [
899        'Lifecycle script `glorp` failed with error:',
900        'Error: ERR',
901        '  in workspace: a@1.0.0',
902        '  at location: {CWD}/prefix/packages/a',
903      ],
904      'should log error msgs for each workspace script'
905    )
906
907    t.match(RUN_SCRIPTS(), [
908      {
909        path: resolve(prefix, 'packages/b'),
910        pkg: { name: 'b', version: '2.0.0' },
911        event: 'glorp',
912      },
913    ])
914  })
915})
916