UNPKG

9.73 kBJavaScriptView Raw
1'use strict';
2
3const CoreObject = require('core-object');
4const fs = require('fs-extra');
5const util = require('util');
6const copy = util.promisify(fs.copy);
7const path = require('path');
8const debug = require('debug')('ember-try:dependency-manager-adapter:npm');
9const rimraf = util.promisify(require('rimraf'));
10const chalk = require('chalk');
11const semver = require('semver');
12
13module.exports = CoreObject.extend({
14 init() {
15 this._super.apply(this, arguments);
16 this.run = this.run || require('../utils/run');
17 },
18 useYarnCommand: false,
19 yarnLock: 'yarn.lock',
20 yarnLockBackupFileName: 'yarn.lock.ember-try',
21 configKey: 'npm',
22 packageJSON: 'package.json',
23 packageJSONBackupFileName: 'package.json.ember-try',
24 nodeModules: 'node_modules',
25 nodeModulesBackupLocation: '.node_modules.ember-try',
26 npmShrinkWrap: 'npm-shrinkwrap.json',
27 npmShrinkWrapBackupFileName: 'npm-shrinkwrap.json.ember-try',
28 packageLock: 'package-lock.json',
29 packageLockBackupFileName: 'package-lock.json.ember-try',
30
31 async setup(options) {
32 if (!options) {
33 options = {};
34 }
35
36 this._runYarnCheck(options.ui);
37
38 return await this._backupOriginalDependencies();
39 },
40
41 async changeToDependencySet(depSet) {
42 this.applyDependencySet(depSet);
43
44 await this._install(depSet);
45
46 let deps = Object.assign({}, depSet.dependencies, depSet.devDependencies);
47 let currentDeps = Object.keys(deps).map((dep) => {
48 return {
49 name: dep,
50 versionExpected: deps[dep],
51 versionSeen: this._findCurrentVersionOf(dep),
52 packageManager: this.useYarnCommand ? 'yarn' : 'npm',
53 };
54 });
55
56 debug('Switched to dependencies: \n', currentDeps);
57
58 return currentDeps;
59 },
60
61 async cleanup() {
62 try {
63 await this._restoreOriginalDependencies();
64
65 debug('Remove backup package.json and node_modules');
66
67 let cleanupTasks = [
68 rimraf(path.join(this.cwd, this.packageJSONBackupFileName)),
69 rimraf(path.join(this.cwd, this.nodeModulesBackupLocation)),
70 ];
71
72 if (fs.existsSync(path.join(this.cwd, this.yarnLockBackupFileName))) {
73 cleanupTasks.push(rimraf(path.join(this.cwd, this.yarnLockBackupFileName)));
74 }
75
76 if (fs.existsSync(path.join(this.cwd, this.npmShrinkWrapBackupFileName))) {
77 cleanupTasks.push(rimraf(path.join(this.cwd, this.npmShrinkWrapBackupFileName)));
78 }
79
80 if (fs.existsSync(path.join(this.cwd, this.packageLockBackupFileName))) {
81 cleanupTasks.push(rimraf(path.join(this.cwd, this.packageLockBackupFileName)));
82 }
83
84 return await Promise.all(cleanupTasks);
85 } catch (e) {
86 console.log('Error cleaning up npm scenario:', e); // eslint-disable-line no-console
87 }
88 },
89
90 _runYarnCheck(ui) {
91 if (!this.useYarnCommand) {
92 try {
93 if (fs.statSync(path.join(this.cwd, this.yarnLock)).isFile()) {
94 ui.writeLine(
95 chalk.yellow(
96 'Detected a yarn.lock file. Add `useYarn: true` to your `config/ember-try.js` configuration file if you want to use Yarn to install npm dependencies.'
97 )
98 );
99 }
100 } catch (e) {
101 // If no yarn.lock is found, no need to warn.
102 }
103 }
104 },
105
106 _findCurrentVersionOf(packageName) {
107 let filename = path.join(this.cwd, this.nodeModules, packageName, this.packageJSON);
108 if (fs.existsSync(filename)) {
109 return JSON.parse(fs.readFileSync(filename)).version;
110 } else {
111 return null;
112 }
113 },
114
115 async _install(depSet) {
116 let mgrOptions = this.managerOptions || [];
117 let cmd = this.useYarnCommand ? 'yarn' : 'npm';
118
119 // buildManagerOptions overrides all default
120 if (typeof this.buildManagerOptions === 'function') {
121 mgrOptions = this.buildManagerOptions(depSet);
122
123 if (!Array.isArray(mgrOptions)) {
124 throw new Error('buildManagerOptions must return an array of options');
125 }
126 } else {
127 if (this.useYarnCommand) {
128 if (mgrOptions.indexOf('--no-lockfile') === -1) {
129 mgrOptions = mgrOptions.concat(['--no-lockfile']);
130 }
131 // npm warns on incompatible engines
132 // yarn errors, not a good experience
133 if (mgrOptions.indexOf('--ignore-engines') === -1) {
134 mgrOptions = mgrOptions.concat(['--ignore-engines']);
135 }
136 } else if (mgrOptions.indexOf('--no-shrinkwrap') === -1) {
137 mgrOptions = mgrOptions.concat(['--no-shrinkwrap']);
138 }
139 }
140
141 debug('Run npm/yarn install with options %s', mgrOptions);
142
143 await this.run(cmd, [].concat(['install'], mgrOptions), { cwd: this.cwd });
144
145 if (!this.useYarnCommand) {
146 let res = await this.run('npm', ['--version'], { cwd: this.cwd, stdio: 'pipe' });
147 let version = res.stdout;
148 if (version.match(/^4./)) {
149 debug('Running npm prune because version is %s', version);
150 return await this.run(this.configKey, ['prune'], { cwd: this.cwd });
151 }
152
153 debug('Not running npm prune because version is %s', version);
154 }
155 },
156
157 applyDependencySet(depSet) {
158 debug('Changing to dependency set: %s', JSON.stringify(depSet));
159
160 if (!depSet) {
161 return;
162 }
163
164 let backupPackageJSON = path.join(this.cwd, this.packageJSONBackupFileName);
165 let packageJSONFile = path.join(this.cwd, this.packageJSON);
166 let packageJSON = JSON.parse(fs.readFileSync(backupPackageJSON));
167 let newPackageJSON = this._packageJSONForDependencySet(packageJSON, depSet);
168
169 debug('Write package.json with: \n', JSON.stringify(newPackageJSON));
170
171 fs.writeFileSync(packageJSONFile, JSON.stringify(newPackageJSON, null, 2));
172 },
173
174 _packageJSONForDependencySet(packageJSON, depSet) {
175 this._overridePackageJSONDependencies(packageJSON, depSet, 'dependencies');
176 this._overridePackageJSONDependencies(packageJSON, depSet, 'devDependencies');
177 this._overridePackageJSONDependencies(packageJSON, depSet, 'peerDependencies');
178 this._overridePackageJSONDependencies(packageJSON, depSet, 'ember');
179
180 if (this.useYarnCommand) {
181 this._overridePackageJSONDependencies(packageJSON, depSet, 'resolutions');
182 } else {
183 this._overridePackageJSONDependencies(packageJSON, depSet, 'overrides');
184 }
185
186 return packageJSON;
187 },
188
189 _overridePackageJSONDependencies(packageJSON, depSet, kindOfDependency) {
190 if (!depSet[kindOfDependency]) {
191 return;
192 }
193
194 let packageNames = Object.keys(depSet[kindOfDependency]);
195
196 packageNames.forEach((packageName) => {
197 if (!packageJSON[kindOfDependency]) {
198 packageJSON[kindOfDependency] = {};
199 }
200
201 let version = depSet[kindOfDependency][packageName];
202 if (version === null) {
203 delete packageJSON[kindOfDependency][packageName];
204 } else {
205 packageJSON[kindOfDependency][packageName] = version;
206
207 // in npm we need to always add an override if the version is a pre-release
208 if (
209 !this.useYarnCommand &&
210 (semver.prerelease(version) || /^https*:\/\/.*\.tg*z/.test(version))
211 ) {
212 if (!packageJSON.overrides) {
213 packageJSON.overrides = {};
214 }
215
216 packageJSON.overrides[packageName] = `$${packageName}`;
217 }
218 }
219 });
220 },
221
222 _restoreOriginalDependencies() {
223 debug('Restoring original package.json and node_modules');
224
225 let restoreTasks = [
226 copy(
227 path.join(this.cwd, this.packageJSONBackupFileName),
228 path.join(this.cwd, this.packageJSON)
229 ),
230 ];
231
232 let nodeModulesBackupLocation = path.join(this.cwd, this.nodeModulesBackupLocation);
233 if (fs.existsSync(nodeModulesBackupLocation)) {
234 restoreTasks.push(
235 copy(nodeModulesBackupLocation, path.join(this.cwd, this.nodeModules), { clobber: true })
236 );
237 }
238
239 let yarnLockBackupFileName = path.join(this.cwd, this.yarnLockBackupFileName);
240 if (fs.existsSync(yarnLockBackupFileName)) {
241 restoreTasks.push(copy(yarnLockBackupFileName, path.join(this.cwd, this.yarnLock)));
242 }
243
244 let npmShrinkWrapBackupFileName = path.join(this.cwd, this.npmShrinkWrapBackupFileName);
245 if (fs.existsSync(npmShrinkWrapBackupFileName)) {
246 restoreTasks.push(copy(npmShrinkWrapBackupFileName, path.join(this.cwd, this.npmShrinkWrap)));
247 }
248
249 let packageLockBackupFileName = path.join(this.cwd, this.packageLockBackupFileName);
250 if (fs.existsSync(packageLockBackupFileName)) {
251 restoreTasks.push(copy(packageLockBackupFileName, path.join(this.cwd, this.packageLock)));
252 }
253
254 return Promise.all(restoreTasks);
255 },
256
257 _backupOriginalDependencies() {
258 debug('Backing up package.json and node_modules');
259
260 let backupTasks = [
261 copy(
262 path.join(this.cwd, this.packageJSON),
263 path.join(this.cwd, this.packageJSONBackupFileName)
264 ),
265 ];
266
267 let nodeModulesPath = path.join(this.cwd, this.nodeModules);
268 if (fs.existsSync(nodeModulesPath)) {
269 backupTasks.push(
270 copy(nodeModulesPath, path.join(this.cwd, this.nodeModulesBackupLocation), {
271 clobber: true,
272 })
273 );
274 }
275
276 let yarnLockPath = path.join(this.cwd, this.yarnLock);
277 if (fs.existsSync(yarnLockPath)) {
278 backupTasks.push(copy(yarnLockPath, path.join(this.cwd, this.yarnLockBackupFileName)));
279 }
280
281 let npmShrinkWrapPath = path.join(this.cwd, this.npmShrinkWrap);
282 if (fs.existsSync(npmShrinkWrapPath)) {
283 backupTasks.push(
284 copy(npmShrinkWrapPath, path.join(this.cwd, this.npmShrinkWrapBackupFileName))
285 );
286 }
287
288 let packageLockPath = path.join(this.cwd, this.packageLock);
289 if (fs.existsSync(packageLockPath)) {
290 backupTasks.push(copy(packageLockPath, path.join(this.cwd, this.packageLockBackupFileName)));
291 }
292
293 return Promise.all(backupTasks);
294 },
295});