fix npx for non-interactive shells · npm/cli@e859fba (original) (raw)
`@@ -19,6 +19,7 @@ class Arborist {
`
19
19
`}
`
20
20
``
21
21
`let PROGRESS_ENABLED = true
`
``
22
`+
const LOG_WARN = []
`
22
23
`const npm = {
`
23
24
`flatOptions: {
`
24
25
`yes: true,
`
`@@ -41,6 +42,9 @@ const npm = {
`
41
42
`},
`
42
43
`enableProgress: () => {
`
43
44
`PROGRESS_ENABLED = true
`
``
45
`+
},
`
``
46
`+
warn: (...args) => {
`
``
47
`+
LOG_WARN.push(args)
`
44
48
`}
`
45
49
`}
`
46
50
`}
`
`@@ -88,6 +92,7 @@ t.afterEach(cb => {
`
88
92
`READ.length = 0
`
89
93
`READ_RESULT = ''
`
90
94
`READ_ERROR = null
`
``
95
`+
LOG_WARN.length = 0
`
91
96
`npm.flatOptions.legacyPeerDeps = false
`
92
97
`npm.flatOptions.package = []
`
93
98
`npm.flatOptions.call = ''
`
`@@ -464,7 +469,16 @@ t.test('positional args and --call together is an error', t => {
`
464
469
`return exec(['foo'], er => t.equal(er, exec.usage))
`
465
470
`})
`
466
471
``
467
``
`-
t.test('prompt when installs are needed if not already present', async t => {
`
``
472
`+
t.test('prompt when installs are needed if not already present and shell is a TTY', async t => {
`
``
473
`+
const stdoutTTY = process.stdout.isTTY
`
``
474
`+
const stdinTTY = process.stdin.isTTY
`
``
475
`+
t.teardown(() => {
`
``
476
`+
process.stdout.isTTY = stdoutTTY
`
``
477
`+
process.stdin.isTTY = stdinTTY
`
``
478
`+
})
`
``
479
`+
process.stdout.isTTY = true
`
``
480
`+
process.stdin.isTTY = true
`
``
481
+
468
482
`const packages = ['foo', 'bar']
`
469
483
`READ_RESULT = 'yolo'
`
470
484
``
`@@ -522,7 +536,138 @@ t.test('prompt when installs are needed if not already present', async t => {
`
522
536
`}])
`
523
537
`})
`
524
538
``
``
539
`+
t.test('skip prompt when installs are needed if not already present and shell is not a tty (multiple packages)', async t => {
`
``
540
`+
const stdoutTTY = process.stdout.isTTY
`
``
541
`+
const stdinTTY = process.stdin.isTTY
`
``
542
`+
t.teardown(() => {
`
``
543
`+
process.stdout.isTTY = stdoutTTY
`
``
544
`+
process.stdin.isTTY = stdinTTY
`
``
545
`+
})
`
``
546
`+
process.stdout.isTTY = false
`
``
547
`+
process.stdin.isTTY = false
`
``
548
+
``
549
`+
const packages = ['foo', 'bar']
`
``
550
`+
READ_RESULT = 'yolo'
`
``
551
+
``
552
`+
npm.flatOptions.package = packages
`
``
553
`+
npm.flatOptions.yes = undefined
`
``
554
+
``
555
`` +
const add = packages.map(p => ${p}@).sort((a, b) => a.localeCompare(b))
``
``
556
`+
const path = t.testdir()
`
``
557
`+
const installDir = resolve('cache-dir/_npx/07de77790e5f40f2')
`
``
558
`+
npm.localPrefix = path
`
``
559
`+
ARB_ACTUAL_TREE[path] = {
`
``
560
`+
children: new Map()
`
``
561
`+
}
`
``
562
`+
ARB_ACTUAL_TREE[installDir] = {
`
``
563
`+
children: new Map()
`
``
564
`+
}
`
``
565
`+
MANIFESTS.foo = {
`
``
566
`+
name: 'foo',
`
``
567
`+
version: '1.2.3',
`
``
568
`+
bin: {
`
``
569
`+
foo: 'foo'
`
``
570
`+
},
`
``
571
`+
_from: 'foo@'
`
``
572
`+
}
`
``
573
`+
MANIFESTS.bar = {
`
``
574
`+
name: 'bar',
`
``
575
`+
version: '1.2.3',
`
``
576
`+
bin: {
`
``
577
`+
bar: 'bar'
`
``
578
`+
},
`
``
579
`+
_from: 'bar@'
`
``
580
`+
}
`
``
581
`+
await exec(['foobar'], er => {
`
``
582
`+
if (er) {
`
``
583
`+
throw er
`
``
584
`+
}
`
``
585
`+
})
`
``
586
`+
t.strictSame(MKDIRPS, [installDir], 'need to make install dir')
`
``
587
`+
t.match(ARB_CTOR, [ { package: packages, path } ])
`
``
588
`+
t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install both packages')
`
``
589
`+
t.equal(PROGRESS_ENABLED, true, 'progress re-enabled')
`
``
590
`` +
const PATH = ${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}
``
``
591
`+
t.match(RUN_SCRIPTS, [{
`
``
592
`+
pkg: { scripts: { npx: 'foobar' } },
`
``
593
`+
banner: false,
`
``
594
`+
path: process.cwd(),
`
``
595
`+
stdioString: true,
`
``
596
`+
event: 'npx',
`
``
597
`+
env: { PATH },
`
``
598
`+
stdio: 'inherit'
`
``
599
`+
}])
`
``
600
`+
t.strictSame(READ, [], 'should not have prompted')
`
``
601
`+
t.strictSame(LOG_WARN, [['exec', 'The following packages were not found and will be installed: bar, foo']], 'should have printed a warning')
`
``
602
`+
})
`
``
603
+
``
604
`+
t.test('skip prompt when installs are needed if not already present and shell is not a tty (single package)', async t => {
`
``
605
`+
const stdoutTTY = process.stdout.isTTY
`
``
606
`+
const stdinTTY = process.stdin.isTTY
`
``
607
`+
t.teardown(() => {
`
``
608
`+
process.stdout.isTTY = stdoutTTY
`
``
609
`+
process.stdin.isTTY = stdinTTY
`
``
610
`+
})
`
``
611
`+
process.stdout.isTTY = false
`
``
612
`+
process.stdin.isTTY = false
`
``
613
+
``
614
`+
const packages = ['foo']
`
``
615
`+
READ_RESULT = 'yolo'
`
``
616
+
``
617
`+
npm.flatOptions.package = packages
`
``
618
`+
npm.flatOptions.yes = undefined
`
``
619
+
``
620
`` +
const add = packages.map(p => ${p}@).sort((a, b) => a.localeCompare(b))
``
``
621
`+
const path = t.testdir()
`
``
622
`+
const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890')
`
``
623
`+
npm.localPrefix = path
`
``
624
`+
ARB_ACTUAL_TREE[path] = {
`
``
625
`+
children: new Map()
`
``
626
`+
}
`
``
627
`+
ARB_ACTUAL_TREE[installDir] = {
`
``
628
`+
children: new Map()
`
``
629
`+
}
`
``
630
`+
MANIFESTS.foo = {
`
``
631
`+
name: 'foo',
`
``
632
`+
version: '1.2.3',
`
``
633
`+
bin: {
`
``
634
`+
foo: 'foo'
`
``
635
`+
},
`
``
636
`+
_from: 'foo@'
`
``
637
`+
}
`
``
638
`+
await exec(['foobar'], er => {
`
``
639
`+
if (er) {
`
``
640
`+
throw er
`
``
641
`+
}
`
``
642
`+
})
`
``
643
`+
t.strictSame(MKDIRPS, [installDir], 'need to make install dir')
`
``
644
`+
t.match(ARB_CTOR, [ { package: packages, path } ])
`
``
645
`+
t.match(ARB_REIFY, [{add, legacyPeerDeps: false}], 'need to install the package')
`
``
646
`+
t.equal(PROGRESS_ENABLED, true, 'progress re-enabled')
`
``
647
`` +
const PATH = ${resolve(installDir, 'node_modules', '.bin')}${delimiter}${process.env.PATH}
``
``
648
`+
t.match(RUN_SCRIPTS, [{
`
``
649
`+
pkg: { scripts: { npx: 'foobar' } },
`
``
650
`+
banner: false,
`
``
651
`+
path: process.cwd(),
`
``
652
`+
stdioString: true,
`
``
653
`+
event: 'npx',
`
``
654
`+
env: { PATH },
`
``
655
`+
stdio: 'inherit'
`
``
656
`+
}])
`
``
657
`+
t.strictSame(READ, [], 'should not have prompted')
`
``
658
`+
t.strictSame(LOG_WARN, [['exec', 'The following package was not found and will be installed: foo']], 'should have printed a warning')
`
``
659
`+
})
`
``
660
+
525
661
`t.test('abort if prompt rejected', async t => {
`
``
662
`+
const stdoutTTY = process.stdout.isTTY
`
``
663
`+
const stdinTTY = process.stdin.isTTY
`
``
664
`+
t.teardown(() => {
`
``
665
`+
process.stdout.isTTY = stdoutTTY
`
``
666
`+
process.stdin.isTTY = stdinTTY
`
``
667
`+
})
`
``
668
`+
process.stdout.isTTY = true
`
``
669
`+
process.stdin.isTTY = true
`
``
670
+
526
671
`const packages = ['foo', 'bar']
`
527
672
`READ_RESULT = 'no, why would I want such a thing??'
`
528
673
``
`@@ -570,6 +715,15 @@ t.test('abort if prompt rejected', async t => {
`
570
715
`})
`
571
716
``
572
717
`t.test('abort if prompt false', async t => {
`
``
718
`+
const stdoutTTY = process.stdout.isTTY
`
``
719
`+
const stdinTTY = process.stdin.isTTY
`
``
720
`+
t.teardown(() => {
`
``
721
`+
process.stdout.isTTY = stdoutTTY
`
``
722
`+
process.stdin.isTTY = stdinTTY
`
``
723
`+
})
`
``
724
`+
process.stdout.isTTY = true
`
``
725
`+
process.stdin.isTTY = true
`
``
726
+
573
727
`const packages = ['foo', 'bar']
`
574
728
`READ_ERROR = 'canceled'
`
575
729
``
`@@ -617,6 +771,15 @@ t.test('abort if prompt false', async t => {
`
617
771
`})
`
618
772
``
619
773
`t.test('abort if -n provided', async t => {
`
``
774
`+
const stdoutTTY = process.stdout.isTTY
`
``
775
`+
const stdinTTY = process.stdin.isTTY
`
``
776
`+
t.teardown(() => {
`
``
777
`+
process.stdout.isTTY = stdoutTTY
`
``
778
`+
process.stdin.isTTY = stdinTTY
`
``
779
`+
})
`
``
780
`+
process.stdout.isTTY = true
`
``
781
`+
process.stdin.isTTY = true
`
``
782
+
620
783
`const packages = ['foo', 'bar']
`
621
784
``
622
785
`npm.flatOptions.package = packages
`