• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const { load: _loadMockNpm } = require('../../fixtures/mock-npm')
3const MockRegistry = require('@npmcli/mock-registry')
4
5const path = require('path')
6const fs = require('fs')
7
8// t.cleanSnapshot = str => str.replace(/ in [0-9ms]+/g, ' in {TIME}')
9
10const loadMockNpm = async (t, opts) => {
11  const mock = await _loadMockNpm(t, opts)
12  const registry = new MockRegistry({
13    tap: t,
14    registry: mock.npm.config.get('registry'),
15  })
16  return { registry, ...mock }
17}
18
19const packageJson = {
20  name: 'test-package',
21  version: '1.0.0',
22  dependencies: {
23    abbrev: '^1.0.0',
24  },
25}
26const packageLock = {
27  name: 'test-package',
28  version: '1.0.0',
29  lockfileVersion: 2,
30  requires: true,
31  packages: {
32    '': {
33      name: 'test-package',
34      version: '1.0.0',
35      dependencies: {
36        abbrev: '^1.0.0',
37      },
38    },
39    'node_modules/abbrev': {
40      version: '1.0.0',
41      resolved: 'https://registry.npmjs.org/abbrev/-/abbrev-1.0.0.tgz',
42      // integrity changes w/ each test cause the path is different?
43    },
44  },
45  dependencies: {
46    abbrev: {
47      version: '1.0.0',
48      resolved: 'https://registry.npmjs.org/abbrev/-/abbrev-1.0.0.tgz',
49      // integrity changes w/ each test cause the path is different?
50    },
51  },
52}
53
54const abbrev = {
55  'package.json': '{"name": "abbrev", "version": "1.0.0"}',
56  test: 'test file',
57}
58
59t.test('reifies, audits, removes node_modules', async t => {
60  const { npm, joinedOutput, registry } = await loadMockNpm(t, {
61    prefixDir: {
62      abbrev: abbrev,
63      'package.json': JSON.stringify(packageJson),
64      'package-lock.json': JSON.stringify(packageLock),
65      node_modules: { test: 'test file that will be removed' },
66    },
67  })
68  const manifest = registry.manifest({ name: 'abbrev' })
69  await registry.tarball({
70    manifest: manifest.versions['1.0.0'],
71    tarball: path.join(npm.prefix, 'abbrev'),
72  })
73  registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
74  await npm.exec('ci', [])
75  t.match(joinedOutput(), 'added 1 package, and audited 2 packages in')
76  const nmTest = path.join(npm.prefix, 'node_modules', 'test')
77  t.equal(fs.existsSync(nmTest), false, 'existing node_modules is removed')
78  const nmAbbrev = path.join(npm.prefix, 'node_modules', 'abbrev')
79  t.equal(fs.existsSync(nmAbbrev), true, 'installs abbrev')
80})
81
82t.test('reifies, audits, removes node_modules on repeat run', async t => {
83  const { npm, joinedOutput, registry } = await loadMockNpm(t, {
84    prefixDir: {
85      abbrev: abbrev,
86      'package.json': JSON.stringify(packageJson),
87      'package-lock.json': JSON.stringify(packageLock),
88      node_modules: { test: 'test file that will be removed' },
89    },
90  })
91  const manifest = registry.manifest({ name: 'abbrev' })
92  await registry.tarball({
93    manifest: manifest.versions['1.0.0'],
94    tarball: path.join(npm.prefix, 'abbrev'),
95  })
96  registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
97  await npm.exec('ci', [])
98  await npm.exec('ci', [])
99  t.match(joinedOutput(), 'added 1 package, and audited 2 packages in')
100  const nmTest = path.join(npm.prefix, 'node_modules', 'test')
101  t.equal(fs.existsSync(nmTest), false, 'existing node_modules is removed')
102  const nmAbbrev = path.join(npm.prefix, 'node_modules', 'abbrev')
103  t.equal(fs.existsSync(nmAbbrev), true, 'installs abbrev')
104})
105
106t.test('--no-audit and --ignore-scripts', async t => {
107  const { npm, joinedOutput, registry } = await loadMockNpm(t, {
108    config: {
109      'ignore-scripts': true,
110      audit: false,
111    },
112    prefixDir: {
113      abbrev: {
114        'package.json': '{"name": "abbrev", "version": "1.0.0"}',
115        test: 'test-file',
116      },
117      'package.json': JSON.stringify({
118        ...packageJson,
119        // Would make install fail
120        scripts: { install: 'echo "SHOULD NOT RUN" && exit 1' },
121      }),
122      'package-lock.json': JSON.stringify(packageLock),
123    },
124  })
125  require('nock').emitter.on('no match', req => {
126    t.fail('Should not audit')
127  })
128  const manifest = registry.manifest({ name: 'abbrev' })
129  await registry.tarball({
130    manifest: manifest.versions['1.0.0'],
131    tarball: path.join(npm.prefix, 'abbrev'),
132  })
133  await npm.exec('ci', [])
134  t.match(joinedOutput(), 'added 1 package in', 'would fail if install script ran')
135})
136
137t.test('lifecycle scripts', async t => {
138  const scripts = []
139  const { npm, registry } = await loadMockNpm(t, {
140    prefixDir: {
141      abbrev: abbrev,
142      'package.json': JSON.stringify(packageJson),
143      'package-lock.json': JSON.stringify(packageLock),
144    },
145    mocks: {
146      '@npmcli/run-script': (opts) => {
147        t.ok(opts.banner)
148        scripts.push(opts.event)
149      },
150    },
151  })
152  const manifest = registry.manifest({ name: 'abbrev' })
153  await registry.tarball({
154    manifest: manifest.versions['1.0.0'],
155    tarball: path.join(npm.prefix, 'abbrev'),
156  })
157  registry.nock.post('/-/npm/v1/security/advisories/bulk').reply(200, {})
158  await npm.exec('ci', [])
159  t.same(scripts, [
160    'preinstall',
161    'install',
162    'postinstall',
163    'prepublish',
164    'preprepare',
165    'prepare',
166    'postprepare',
167  ], 'runs appropriate scripts, in order')
168})
169
170t.test('should throw if package-lock.json or npm-shrinkwrap missing', async t => {
171  const { npm } = await loadMockNpm(t, {
172    prefixDir: {
173      'package.json': JSON.stringify(packageJson),
174      node_modules: {
175        'test-file': 'should not be removed',
176      },
177    },
178  })
179  await t.rejects(npm.exec('ci', []), { code: 'EUSAGE', message: /package-lock.json/ })
180  const nmTestFile = path.join(npm.prefix, 'node_modules', 'test-file')
181  t.equal(fs.existsSync(nmTestFile), true, 'does not remove node_modules')
182})
183
184t.test('should throw ECIGLOBAL', async t => {
185  const { npm } = await loadMockNpm(t, {
186    config: { global: true },
187  })
188  await t.rejects(npm.exec('ci', []), { code: 'ECIGLOBAL' })
189})
190
191t.test('should throw error when ideal inventory mismatches virtual', async t => {
192  const { npm, registry } = await loadMockNpm(t, {
193    prefixDir: {
194      abbrev: abbrev,
195      'package.json': JSON.stringify({
196        ...packageJson,
197        dependencies: { notabbrev: '^1.0.0' },
198      }),
199      'package-lock.json': JSON.stringify(packageLock),
200      node_modules: {
201        'test-file': 'should not be removed',
202      },
203    },
204  })
205  const manifest = registry.manifest({ name: 'notabbrev' })
206  await registry.package({ manifest })
207  await t.rejects(
208    npm.exec('ci', []),
209    { code: 'EUSAGE', message: /in sync/ }
210  )
211  const nmTestFile = path.join(npm.prefix, 'node_modules', 'test-file')
212  t.equal(fs.existsSync(nmTestFile), true, 'does not remove node_modules')
213})
214