Skip to content

Commit 5e83f01

Browse files
committed
chore(check-node-modules): make check/reinstall node_modules work across platforms
The previous implementations (based on shell scripts) threw errors on Windows, because it was not able to `rm -rf` 'node_modules' (due to the 255 character limit in file-paths). This implementation works consistently across platforms and is heavily based on 'https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js'. Fixes angular#11143 Closes angular#11353
1 parent 681affe commit 5e83f01

File tree

4 files changed

+177
-18
lines changed

4 files changed

+177
-18
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ install:
4848
- npm config set loglevel http
4949
- npm install -g [email protected]
5050
# Instal npm dependecies and ensure that npm cache is not stale
51-
- scripts/npm/install-dependencies.sh
51+
- node scripts/npm/check-node-modules.js --reinstall
5252

5353
before_script:
5454
- mkdir -p $LOGS_DIR

Gruntfile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ module.exports = function(grunt) {
305305

306306
shell: {
307307
"npm-install": {
308-
command: path.normalize('scripts/npm/install-dependencies.sh')
308+
command: 'node scripts/npm/check-node-modules.js'
309309
},
310310

311311
"promises-aplus-tests": {

scripts/npm/check-node-modules.js

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Implementation based on
2+
// https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js
3+
'use strict';
4+
5+
// Imports
6+
var fs = require('fs');
7+
var path = require('path');
8+
9+
// Constants
10+
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
11+
var NPM_SHRINKWRAP_CACHED_FILE = 'node_modules/npm-shrinkwrap.cached.json';
12+
var FS_OPTS = {encoding: 'utf-8'};
13+
var PROJECT_ROOT = path.join(__dirname, '../../');
14+
15+
// Variables
16+
var progressIndicatorTimer = null;
17+
18+
// Run
19+
_main();
20+
21+
// Functions - Definitions
22+
function _main() {
23+
var reinstallIfStale = process.argv.indexOf('--reinstall') !== -1;
24+
checkNodeModules(reinstallIfStale);
25+
}
26+
27+
function cacheNpmShrinkwrapFile() {
28+
var absoluteMarkerFilePath = path.join(PROJECT_ROOT, NPM_SHRINKWRAP_FILE);
29+
var absoluteCachedMarkerFilePath = path.join(PROJECT_ROOT, NPM_SHRINKWRAP_CACHED_FILE);
30+
31+
startProgressIndicator(' Caching marker file...');
32+
copyFile(absoluteMarkerFilePath, absoluteCachedMarkerFilePath, onCopied);
33+
34+
// Helpers
35+
function onCopied(err) {
36+
stopProgressIndicator();
37+
if (err) logError(err);
38+
}
39+
}
40+
41+
function checkNodeModules(reinstallIfStale) {
42+
var nodeModulesOk = compareMarkerFiles();
43+
44+
if (nodeModulesOk) {
45+
console.log(':-) npm dependencies are looking good!');
46+
} else {
47+
console.warn(':-( npm dependencies are stale or in an unknown state!');
48+
49+
if (reinstallIfStale) {
50+
purgeModules();
51+
installModules();
52+
}
53+
}
54+
55+
return nodeModulesOk;
56+
}
57+
58+
function compareMarkerFiles() {
59+
var absoluteMarkerFilePath = path.join(PROJECT_ROOT, NPM_SHRINKWRAP_FILE);
60+
var absoluteCachedMarkerFilePath = path.join(PROJECT_ROOT, NPM_SHRINKWRAP_CACHED_FILE);
61+
62+
if (!fs.existsSync(absoluteCachedMarkerFilePath)) return false;
63+
64+
var markerContent = fs.readFileSync(absoluteMarkerFilePath, FS_OPTS);
65+
var cachedMarkerContent = fs.readFileSync(absoluteCachedMarkerFilePath, FS_OPTS);
66+
67+
return markerContent === cachedMarkerContent;
68+
}
69+
70+
// Implementation based on
71+
// https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js#answer-21995878
72+
function copyFile(srcPath, dstPath, callback) {
73+
var callbackCalled = false;
74+
75+
if (!fs.existsSync(srcPath)) {
76+
done(new Error('Missing source file: ' + srcPath));
77+
return;
78+
}
79+
80+
var rs = fs.createReadStream(srcPath);
81+
rs.on('error', done);
82+
83+
var ws = fs.createWriteStream(dstPath);
84+
ws.on('error', done);
85+
ws.on('finish', done);
86+
87+
rs.pipe(ws);
88+
89+
// Helpers
90+
function done(err) {
91+
if (callback && !callbackCalled) {
92+
callbackCalled = true;
93+
callback(err);
94+
}
95+
}
96+
}
97+
98+
// Custom implementation of `rm -rf` that works consistently across OSes
99+
function deleteDirSync(path) {
100+
if (fs.existsSync(path)) {
101+
fs.readdirSync(path).forEach(deleteDirOrFileSync);
102+
fs.rmdirSync(path);
103+
}
104+
105+
// Helpers
106+
function deleteDirOrFileSync(subpath) {
107+
var curPath = path + '/' + subpath;
108+
109+
if (fs.lstatSync(curPath).isDirectory()) {
110+
deleteDirSync(curPath);
111+
} else {
112+
fs.unlinkSync(curPath);
113+
}
114+
}
115+
}
116+
117+
function installModules() {
118+
startProgressIndicator(' Running `npm install` (this may take a while)...');
119+
120+
var exec = require('child_process').exec;
121+
var opts = {cwd: PROJECT_ROOT};
122+
var proc = exec('npm install', opts, onInstalled);
123+
124+
// TODO(gkalpak): Decide if we actually want these
125+
// proc.stdout.pipe(process.stdout);
126+
// proc.stderr.pipe(process.stderr);
127+
128+
// Helpers
129+
function onInstalled(err) {
130+
stopProgressIndicator();
131+
132+
if (err) {
133+
logError(err);
134+
return;
135+
}
136+
137+
cacheNpmShrinkwrapFile();
138+
}
139+
}
140+
141+
function purgeModules() {
142+
startProgressIndicator(' Purging \'node_modules\'...');
143+
144+
var nodeModulesPath = path.join(PROJECT_ROOT, 'node_modules');
145+
deleteDirSync(nodeModulesPath);
146+
147+
stopProgressIndicator();
148+
}
149+
150+
function logError(err) {
151+
var separator = new Array(81).join('!');
152+
153+
console.error(separator);
154+
console.error('Operation completed with errors:');
155+
console.error(err);
156+
console.error(separator);
157+
}
158+
159+
function startProgressIndicator(taskDescription) {
160+
stopProgressIndicator();
161+
162+
var stdout = process.stdout;
163+
164+
stdout.write(taskDescription);
165+
progressIndicatorTimer = setInterval(stdout.write.bind(stdout, '.'), 5000);
166+
}
167+
168+
function stopProgressIndicator() {
169+
if (progressIndicatorTimer) {
170+
clearInterval(progressIndicatorTimer);
171+
progressIndicatorTimer = null;
172+
173+
process.stdout.write('\n');
174+
}
175+
}

scripts/npm/install-dependencies.sh

-16
This file was deleted.

0 commit comments

Comments
 (0)