Skip to content

Commit 9ccb15e

Browse files
authored
Merge pull request serverless-heaven#373 from serverless-heaven/error-on-stdout-empty-with-npm
Do not swallow errors in case npm ls fails without an empty stdout and use spawn for child processes
2 parents c99329f + 080ae6f commit 9ccb15e

13 files changed

+512
-254
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ custom:
6161
webpackConfig: 'webpack.config.js' # Name of webpack configuration file
6262
includeModules: false # Node modules configuration for packaging
6363
packager: 'npm' # Packager that will be used to package your external modules
64-
packExternalModulesMaxBuffer: 200 * 1024 # Size of stdio buffers for spawned child processes
6564
```
6665

6766
### Webpack configuration file

lib/Configuration.js

-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const DefaultConfig = {
1313
includeModules: false,
1414
packager: 'npm',
1515
packagerOptions: {},
16-
packExternalModulesMaxBuffer: 200 * 1024,
1716
config: null
1817
};
1918

@@ -31,10 +30,6 @@ class Configuration {
3130
this._config.includeModules = custom.webpackIncludeModules;
3231
this._hasLegacyConfig = true;
3332
}
34-
if (custom.packExternalModulesMaxBuffer) {
35-
this._config.packExternalModulesMaxBuffer = custom.packExternalModulesMaxBuffer;
36-
this._hasLegacyConfig = true;
37-
}
3833
if (_.isString(custom.webpack)) {
3934
this._config.webpackConfig = custom.webpack;
4035
this._hasLegacyConfig = true;
@@ -55,10 +50,6 @@ class Configuration {
5550
return this._config.includeModules;
5651
}
5752

58-
get packExternalModulesMaxBuffer() {
59-
return this._config.packExternalModulesMaxBuffer;
60-
}
61-
6253
get packager() {
6354
return this._config.packager;
6455
}

lib/Configuration.test.js

-10
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ describe('Configuration', () => {
1818
includeModules: false,
1919
packager: 'npm',
2020
packagerOptions: {},
21-
packExternalModulesMaxBuffer: 200 * 1024,
2221
config: null
2322
};
2423
});
@@ -43,12 +42,6 @@ describe('Configuration', () => {
4342
expect(config).to.have.a.property('includeModules').that.deep.equals(testCustom.webpackIncludeModules);
4443
});
4544

46-
it('should use custom.packExternalModulesMaxBuffer', () => {
47-
const testCustom = { packExternalModulesMaxBuffer: 4711 };
48-
const config = new Configuration(testCustom);
49-
expect(config).to.have.a.property('packExternalModulesMaxBuffer').that.equals(4711);
50-
});
51-
5245
it('should use custom.webpack as string', () => {
5346
const testCustom = { webpack: 'myWebpackFile.js' };
5447
const config = new Configuration(testCustom);
@@ -73,7 +66,6 @@ describe('Configuration', () => {
7366
includeModules: { forceInclude: ['mod1'] },
7467
packager: 'npm',
7568
packagerOptions: {},
76-
packExternalModulesMaxBuffer: 200 * 1024,
7769
config: null
7870
});
7971
});
@@ -93,7 +85,6 @@ describe('Configuration', () => {
9385
includeModules: { forceInclude: ['mod1'] },
9486
packager: 'npm',
9587
packagerOptions: {},
96-
packExternalModulesMaxBuffer: 200 * 1024,
9788
config: null
9889
});
9990
});
@@ -112,7 +103,6 @@ describe('Configuration', () => {
112103
includeModules: { forceInclude: ['mod1'] },
113104
packager: 'npm',
114105
packagerOptions: {},
115-
packExternalModulesMaxBuffer: 200 * 1024,
116106
config: null
117107
});
118108
});

lib/packExternalModules.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,8 @@ module.exports = {
195195
.then(packager => {
196196
// Get first level dependency graph
197197
this.options.verbose && this.serverless.cli.log(`Fetch dependency graph from ${packageJsonPath}`);
198-
const maxExecBufferSize = this.configuration.packExternalModulesMaxBuffer;
199198

200-
return packager.getProdDependencies(path.dirname(packageJsonPath), 1, maxExecBufferSize)
199+
return packager.getProdDependencies(path.dirname(packageJsonPath), 1)
201200
.then(dependencyGraph => {
202201
const problems = _.get(dependencyGraph, 'problems', []);
203202
if (this.options.verbose && !_.isEmpty(problems)) {
@@ -271,7 +270,7 @@ module.exports = {
271270
.then(() => {
272271
const start = _.now();
273272
this.serverless.cli.log('Packing external modules: ' + compositeModules.join(', '));
274-
return packager.install(compositeModulePath, maxExecBufferSize, this.configuration.packagerOptions)
273+
return packager.install(compositeModulePath, this.configuration.packagerOptions)
275274
.then(() => this.options.verbose && this.serverless.cli.log(`Package took [${_.now() - start} ms]`))
276275
.return(stats.stats);
277276
})
@@ -320,13 +319,13 @@ module.exports = {
320319
.then(() => {
321320
// Prune extraneous packages - removes not needed ones
322321
const startPrune = _.now();
323-
return packager.prune(modulePath, maxExecBufferSize, this.configuration.packagerOptions)
322+
return packager.prune(modulePath, this.configuration.packagerOptions)
324323
.tap(() => this.options.verbose && this.serverless.cli.log(`Prune: ${modulePath} [${_.now() - startPrune} ms]`));
325324
})
326325
.then(() => {
327326
// Prune extraneous packages - removes not needed ones
328327
const startRunScripts = _.now();
329-
return packager.runScripts(modulePath, maxExecBufferSize, _.keys(packageScripts))
328+
return packager.runScripts(modulePath, _.keys(packageScripts))
330329
.tap(() => this.options.verbose && this.serverless.cli.log(`Run scripts: ${modulePath} [${_.now() - startRunScripts} ms]`));
331330
});
332331
})

lib/packagers/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
* interface Packager {
88
*
99
* static get lockfileName(): string;
10-
* static getProdDependencies(cwd: string, depth: number = 1, maxExecBufferSize = undefined): BbPromise<Object>;
10+
* static getProdDependencies(cwd: string, depth: number = 1): BbPromise<Object>;
1111
* static rebaseLockfile(pathToPackageRoot: string, lockfile: Object): void;
12-
* static install(cwd: string, maxExecBufferSize = undefined): BbPromise<void>;
12+
* static install(cwd: string): BbPromise<void>;
1313
* static prune(cwd: string): BbPromise<void>;
14-
* static runScripts(cwd: string, maxExecBufferSize, scriptNames): BbPromise<void>;
14+
* static runScripts(cwd: string, scriptNames): BbPromise<void>;
1515
*
1616
* }
1717
*/

lib/packagers/npm.js

+47-46
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
const _ = require('lodash');
77
const BbPromise = require('bluebird');
8-
const childProcess = require('child_process');
8+
const Utils = require('../utils');
99

1010
class NPM {
1111
static get lockfileName() { // eslint-disable-line lodash/prefer-constant
@@ -16,39 +16,44 @@ class NPM {
1616
return true;
1717
}
1818

19-
static getProdDependencies(cwd, depth, maxExecBufferSize) {
19+
static getProdDependencies(cwd, depth) {
2020
// Get first level dependency graph
21-
const command = `npm ls -prod -json -depth=${depth || 1}`; // Only prod dependencies
21+
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
22+
const args = [
23+
'ls',
24+
'-prod', // Only prod dependencies
25+
'-json',
26+
`-depth=${depth || 1}`
27+
];
2228

2329
const ignoredNpmErrors = [
2430
{ npmError: 'extraneous', log: false },
2531
{ npmError: 'missing', log: false },
2632
{ npmError: 'peer dep missing', log: true },
2733
];
2834

29-
return BbPromise.fromCallback(cb => {
30-
childProcess.exec(command, {
31-
cwd: cwd,
32-
maxBuffer: maxExecBufferSize,
33-
encoding: 'utf8'
34-
}, (err, stdout, stderr) => {
35-
if (err) {
36-
// Only exit with an error if we have critical npm errors for 2nd level inside
37-
const errors = _.split(stderr, '\n');
38-
const failed = _.reduce(errors, (failed, error) => {
39-
if (failed) {
40-
return true;
41-
}
42-
return !_.isEmpty(error) && !_.some(ignoredNpmErrors, ignoredError => _.startsWith(error, `npm ERR! ${ignoredError.npmError}`));
43-
}, false);
44-
35+
return Utils.spawnProcess(command, args, {
36+
cwd: cwd
37+
})
38+
.catch(err => {
39+
if (err instanceof Utils.SpawnError) {
40+
// Only exit with an error if we have critical npm errors for 2nd level inside
41+
const errors = _.split(err.stderr, '\n');
42+
const failed = _.reduce(errors, (failed, error) => {
4543
if (failed) {
46-
return cb(err);
44+
return true;
4745
}
46+
return !_.isEmpty(error) && !_.some(ignoredNpmErrors, ignoredError => _.startsWith(error, `npm ERR! ${ignoredError.npmError}`));
47+
}, false);
48+
49+
if (!failed && !_.isEmpty(err.stdout)) {
50+
return BbPromise.resolve({ stdout: err.stdout });
4851
}
49-
return cb(null, stdout);
50-
});
52+
}
53+
54+
return BbPromise.reject(err);
5155
})
56+
.then(processOutput => processOutput.stdout)
5257
.then(depJson => BbPromise.try(() => JSON.parse(depJson)));
5358
}
5459

@@ -73,36 +78,32 @@ class NPM {
7378
}
7479
}
7580

76-
static install(cwd, maxExecBufferSize) {
77-
return BbPromise.fromCallback(cb => {
78-
childProcess.exec('npm install', {
79-
cwd: cwd,
80-
maxBuffer: maxExecBufferSize,
81-
encoding: 'utf8'
82-
}, cb);
83-
})
81+
static install(cwd) {
82+
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
83+
const args = ['install'];
84+
85+
return Utils.spawnProcess(command, args, { cwd })
8486
.return();
8587
}
8688

87-
static prune(cwd, maxExecBufferSize) {
88-
return BbPromise.fromCallback(cb => {
89-
childProcess.exec('npm prune', {
90-
cwd: cwd,
91-
maxBuffer: maxExecBufferSize,
92-
encoding: 'utf8'
93-
}, cb);
94-
})
89+
static prune(cwd) {
90+
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
91+
const args = ['prune'];
92+
93+
return Utils.spawnProcess(command, args, { cwd })
9594
.return();
9695
}
9796

98-
static runScripts(cwd, maxExecBufferSize, scriptNames) {
99-
return BbPromise.mapSeries(scriptNames, scriptName => BbPromise.fromCallback(cb => {
100-
childProcess.exec(`npm run ${scriptName}`, {
101-
cwd: cwd,
102-
maxBuffer: maxExecBufferSize,
103-
encoding: 'utf8'
104-
}, cb);
105-
}))
97+
static runScripts(cwd, scriptNames) {
98+
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
99+
return BbPromise.mapSeries(scriptNames, scriptName => {
100+
const args = [
101+
'run',
102+
scriptName
103+
];
104+
105+
return Utils.spawnProcess(command, args, { cwd });
106+
})
106107
.return();
107108
}
108109
}

0 commit comments

Comments
 (0)