Skip to content
This repository was archived by the owner on Aug 18, 2024. It is now read-only.

Commit e138424

Browse files
committed
fix: rely on npm shrinkwrap to read the installed contents
`npm ls` does not have all the necessary structures.
1 parent d167ca5 commit e138424

File tree

7 files changed

+73
-35
lines changed

7 files changed

+73
-35
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Only the explicitly allowed `[pre|post]install` scripts will be executed.
1717
$ npx allow-scripts [--dry-run]
1818
```
1919

20-
Running the command will scan the list of installed dependencies using `npm ls --json`. It will then execute the scripts for allowed dependencies that have them in the following order:
20+
Running the command will scan the list of installed dependencies (using an existing `package-lock.json` or `npm-shrinkwrap.json` or by creating one on the fly). It will then execute the scripts for allowed dependencies that have them in the following order:
2121

2222
- `preinstall` in the main package
2323
- `preinstall` in dependencies

lib/index.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const Cp = require('child_process');
4+
const Fs = require('fs');
45
const Npm = require('libnpm');
56
const Path = require('path');
67
const Semver = require('semver');
@@ -73,22 +74,23 @@ internals.runScript = (stage, { pkg, path, cwd, unsafePerm }, options) => {
7374
});
7475
};
7576

76-
internals.getInstalledContents = (cwd) => {
77+
internals.getLockFile = (cwd) => {
7778

78-
let output;
79-
try {
80-
output = Cp.execSync('npm ls --json', { cwd });
81-
}
82-
catch (err) {
83-
output = err.output[1]; // npm will exit with an error when e.g. there's peer deps missing - attempt to ignore that
79+
if (Fs.existsSync(Path.join(cwd, 'npm-shrinkwrap.json'))) {
80+
return require(Path.join(cwd, 'npm-shrinkwrap.json'));
8481
}
8582

86-
try {
87-
return JSON.parse(output.toString());
88-
}
89-
catch (err) {
90-
throw new Error(`Failed to read the contents of node_modules. \`npm ls --json\` returned: ${output}`);
83+
if (Fs.existsSync(Path.join(cwd, 'package-lock.json'))) {
84+
return require(Path.join(cwd, 'package-lock.json'));
9185
}
86+
87+
Cp.execSync('npm shrinkwrap');
88+
89+
const lockFilePath = Path.join(cwd, 'npm-shrinkwrap.json');
90+
const lockFileContents = require(lockFilePath);
91+
92+
Fs.unlinkSync(lockFilePath);
93+
return lockFileContents;
9294
};
9395

9496
exports.run = async (options) => {
@@ -98,7 +100,13 @@ exports.run = async (options) => {
98100

99101
pkg._id = `${pkg.name}@${pkg.version}`; // @todo: find an official way to do this for top level package
100102

101-
const tree = Npm.logicalTree(pkg, internals.getInstalledContents(cwd));
103+
try {
104+
var tree = Npm.logicalTree(pkg, internals.getLockFile(cwd));
105+
}
106+
catch (err) {
107+
throw new Error('Failed to read the installed tree - you might want to `rm -rf node_modules && npm i --ignore-scripts`.');
108+
}
109+
102110
const queue = internals.queue(tree);
103111

104112
const allowScripts = pkg.allowScripts || {};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/dominykas/allow-scripts.git"
88
},
99
"scripts": {
10-
"test": "lab -L -t 96 -m 5000"
10+
"test": "lab -L -t 93 -m 5000"
1111
},
1212
"bin": {
1313
"allow-scripts": "./bin/allow-scripts.js"

test/fixtures/deep.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"private": true,
3+
"name": "@example/deep",
4+
"version": "0.0.0",
5+
"dependencies": {
6+
"@example/basic": "*"
7+
},
8+
"allowScripts": {
9+
"@example/basic": "*",
10+
"@example/with-preinstall-script": "*",
11+
"@example/with-install-script": "*",
12+
"@example/with-postinstall-script": true
13+
}
14+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
preinstall from with-preinstall-script
12
preinstall from basic
23
install from with-install-script
34
install from basic
5+
postinstall from with-postinstall-script
46
postinstall from basic
5-
prepublish from basic
6-
prepare from basic

test/fixtures/index.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ exports.setup = (main, deps) => {
2222
Mkdirp.sync(cwd);
2323
Fs.copyFileSync(Path.join(__dirname, `${main}.json`), Path.join(cwd, 'package.json'));
2424
Fs.writeFileSync(Path.join(cwd, 'res.txt'), '');
25-
delete require.cache[Path.join(cwd, 'package.json')];
2625

2726
deps.forEach((dep) => {
2827

2928
const pkg = require(`./${dep}.json`);
3029

3130
Mkdirp.sync(Path.join(cwd, 'node_modules', pkg.name));
32-
Fs.writeFileSync(Path.join(cwd, 'node_modules', pkg.name, 'package.json'), JSON.stringify(Object.assign({}, pkg, {
31+
const pkgJsonPath = Path.join(cwd, 'node_modules', pkg.name, 'package.json');
32+
Fs.writeFileSync(pkgJsonPath, JSON.stringify(Object.assign({}, pkg, {
3333
_id: `${pkg.name}@${pkg.version}`
3434
})));
3535
});
@@ -42,11 +42,22 @@ exports.setup = (main, deps) => {
4242
internals.restore.push(() => {
4343

4444
process.env.OUTPUT = originalOutput;
45+
Object.keys(require.cache).forEach((k) => {
46+
47+
if (k.startsWith(cwd)) {
48+
delete require.cache[k];
49+
}
50+
});
4551
});
4652

4753
const log = [];
4854
const appendLog = (...items) => {
4955

56+
// @todo: should suppress this in production code
57+
if (items[0] === 'npm notice created a lockfile as npm-shrinkwrap.json. You should commit this file.\n') {
58+
return;
59+
}
60+
5061
log.push(items.map((i) => i || '').join(' ').replace(new RegExp(cwd, 'g'), '.'));
5162
};
5263

test/index.js

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
'use strict';
22

3-
const Cp = require('child_process');
43
const Fs = require('fs');
54
const Fixtures = require('./fixtures');
65
const Path = require('path');
7-
const Sinon = require('sinon');
86

97
const Allow = require('..');
108

@@ -61,6 +59,24 @@ describe('allow-scripts', () => {
6159
expect(fixture.getLog()).to.equal(Fs.readFileSync(Path.join(__dirname, 'fixtures', 'basic.dry-run.txt')).toString().trim());
6260
});
6361

62+
it('executes allowed scripts (subdeps)', async () => {
63+
64+
const fixture = Fixtures.setup('deep', [
65+
'basic',
66+
'with-preinstall-script',
67+
'with-install-script',
68+
'with-postinstall-script',
69+
'without-scripts',
70+
'without-install-scripts'
71+
]);
72+
73+
await Allow.run({});
74+
75+
expect(fixture.getActualResult()).to.equal(Fs.readFileSync(Path.join(__dirname, 'fixtures', 'deep.txt')).toString().trim());
76+
expect(fixture.getLog()).not.to.contain('without-scripts');
77+
expect(fixture.getLog()).not.to.contain('without-install-scripts');
78+
});
79+
6480
it('crashes on script not in allowed list', async () => {
6581

6682
const fixture = Fixtures.setup('not-in-allowed', [
@@ -133,28 +149,17 @@ describe('allow-scripts', () => {
133149
expect(fixture.getLog()).to.equal('');
134150
});
135151

136-
it('deals with incomplete installed tree', async () => {
152+
it('crashes on incomplete installed tree', async () => {
137153

138-
const fixture = Fixtures.setup('basic', [
154+
Fixtures.setup('basic', [
139155
// 'with-preinstall-script',
140156
'with-install-script',
141157
// 'with-postinstall-script',
142158
'without-scripts',
143159
'without-install-scripts'
144160
]);
145161

146-
await Allow.run({});
147-
148-
expect(fixture.getActualResult()).to.equal(Fs.readFileSync(Path.join(__dirname, 'fixtures', 'basic.incomplete.txt')).toString().trim());
149-
});
150-
151-
it('deals with unparseable tree', async () => {
152-
153-
Sinon.stub(Cp, 'execSync').returns('not-json');
154-
155-
Fixtures.setup('basic', []);
156-
157-
await expect(Allow.run({})).to.reject('Failed to read the contents of node_modules. `npm ls --json` returned: not-json');
162+
await expect(Allow.run({})).to.reject('Failed to read the installed tree - you might want to `rm -rf node_modules && npm i --ignore-scripts`.');
158163
});
159164
});
160165
});

0 commit comments

Comments
 (0)