ember-try
Version:
An ember-cli addon to test against multiple dependencies, such as ember and ember-data.
296 lines (241 loc) • 9.73 kB
JavaScript
'use strict';
const CoreObject = require('core-object');
const fs = require('fs-extra');
const util = require('util');
const copy = util.promisify(fs.copy);
const path = require('path');
const debug = require('debug')('ember-try:dependency-manager-adapter:npm');
const rimraf = util.promisify(require('rimraf'));
const chalk = require('chalk');
const semver = require('semver');
module.exports = CoreObject.extend({
init() {
this._super.apply(this, arguments);
this.run = this.run || require('../utils/run');
},
useYarnCommand: false,
yarnLock: 'yarn.lock',
yarnLockBackupFileName: 'yarn.lock.ember-try',
configKey: 'npm',
packageJSON: 'package.json',
packageJSONBackupFileName: 'package.json.ember-try',
nodeModules: 'node_modules',
nodeModulesBackupLocation: '.node_modules.ember-try',
npmShrinkWrap: 'npm-shrinkwrap.json',
npmShrinkWrapBackupFileName: 'npm-shrinkwrap.json.ember-try',
packageLock: 'package-lock.json',
packageLockBackupFileName: 'package-lock.json.ember-try',
async setup(options) {
if (!options) {
options = {};
}
this._runYarnCheck(options.ui);
return await this._backupOriginalDependencies();
},
async changeToDependencySet(depSet) {
this.applyDependencySet(depSet);
await this._install(depSet);
let deps = Object.assign({}, depSet.dependencies, depSet.devDependencies);
let currentDeps = Object.keys(deps).map((dep) => {
return {
name: dep,
versionExpected: deps[dep],
versionSeen: this._findCurrentVersionOf(dep),
packageManager: this.useYarnCommand ? 'yarn' : 'npm',
};
});
debug('Switched to dependencies: \n', currentDeps);
return currentDeps;
},
async cleanup() {
try {
await this._restoreOriginalDependencies();
debug('Remove backup package.json and node_modules');
let cleanupTasks = [
rimraf(path.join(this.cwd, this.packageJSONBackupFileName)),
rimraf(path.join(this.cwd, this.nodeModulesBackupLocation)),
];
if (fs.existsSync(path.join(this.cwd, this.yarnLockBackupFileName))) {
cleanupTasks.push(rimraf(path.join(this.cwd, this.yarnLockBackupFileName)));
}
if (fs.existsSync(path.join(this.cwd, this.npmShrinkWrapBackupFileName))) {
cleanupTasks.push(rimraf(path.join(this.cwd, this.npmShrinkWrapBackupFileName)));
}
if (fs.existsSync(path.join(this.cwd, this.packageLockBackupFileName))) {
cleanupTasks.push(rimraf(path.join(this.cwd, this.packageLockBackupFileName)));
}
return await Promise.all(cleanupTasks);
} catch (e) {
console.log('Error cleaning up npm scenario:', e); // eslint-disable-line no-console
}
},
_runYarnCheck(ui) {
if (!this.useYarnCommand) {
try {
if (fs.statSync(path.join(this.cwd, this.yarnLock)).isFile()) {
ui.writeLine(
chalk.yellow(
'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.'
)
);
}
} catch (e) {
// If no yarn.lock is found, no need to warn.
}
}
},
_findCurrentVersionOf(packageName) {
let filename = path.join(this.cwd, this.nodeModules, packageName, this.packageJSON);
if (fs.existsSync(filename)) {
return JSON.parse(fs.readFileSync(filename)).version;
} else {
return null;
}
},
async _install(depSet) {
let mgrOptions = this.managerOptions || [];
let cmd = this.useYarnCommand ? 'yarn' : 'npm';
// buildManagerOptions overrides all default
if (typeof this.buildManagerOptions === 'function') {
mgrOptions = this.buildManagerOptions(depSet);
if (!Array.isArray(mgrOptions)) {
throw new Error('buildManagerOptions must return an array of options');
}
} else {
if (this.useYarnCommand) {
if (mgrOptions.indexOf('--no-lockfile') === -1) {
mgrOptions = mgrOptions.concat(['--no-lockfile']);
}
// npm warns on incompatible engines
// yarn errors, not a good experience
if (mgrOptions.indexOf('--ignore-engines') === -1) {
mgrOptions = mgrOptions.concat(['--ignore-engines']);
}
} else if (mgrOptions.indexOf('--no-shrinkwrap') === -1) {
mgrOptions = mgrOptions.concat(['--no-shrinkwrap']);
}
}
debug('Run npm/yarn install with options %s', mgrOptions);
await this.run(cmd, [].concat(['install'], mgrOptions), { cwd: this.cwd });
if (!this.useYarnCommand) {
let res = await this.run('npm', ['--version'], { cwd: this.cwd, stdio: 'pipe' });
let version = res.stdout;
if (version.match(/^4./)) {
debug('Running npm prune because version is %s', version);
return await this.run(this.configKey, ['prune'], { cwd: this.cwd });
}
debug('Not running npm prune because version is %s', version);
}
},
applyDependencySet(depSet) {
debug('Changing to dependency set: %s', JSON.stringify(depSet));
if (!depSet) {
return;
}
let backupPackageJSON = path.join(this.cwd, this.packageJSONBackupFileName);
let packageJSONFile = path.join(this.cwd, this.packageJSON);
let packageJSON = JSON.parse(fs.readFileSync(backupPackageJSON));
let newPackageJSON = this._packageJSONForDependencySet(packageJSON, depSet);
debug('Write package.json with: \n', JSON.stringify(newPackageJSON));
fs.writeFileSync(packageJSONFile, JSON.stringify(newPackageJSON, null, 2));
},
_packageJSONForDependencySet(packageJSON, depSet) {
this._overridePackageJSONDependencies(packageJSON, depSet, 'dependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'devDependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'peerDependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'ember');
if (this.useYarnCommand) {
this._overridePackageJSONDependencies(packageJSON, depSet, 'resolutions');
} else {
this._overridePackageJSONDependencies(packageJSON, depSet, 'overrides');
}
return packageJSON;
},
_overridePackageJSONDependencies(packageJSON, depSet, kindOfDependency) {
if (!depSet[kindOfDependency]) {
return;
}
let packageNames = Object.keys(depSet[kindOfDependency]);
packageNames.forEach((packageName) => {
if (!packageJSON[kindOfDependency]) {
packageJSON[kindOfDependency] = {};
}
let version = depSet[kindOfDependency][packageName];
if (version === null) {
delete packageJSON[kindOfDependency][packageName];
} else {
packageJSON[kindOfDependency][packageName] = version;
// in npm we need to always add an override if the version is a pre-release
if (
!this.useYarnCommand &&
(semver.prerelease(version) || /^https*:\/\/.*\.tg*z/.test(version))
) {
if (!packageJSON.overrides) {
packageJSON.overrides = {};
}
packageJSON.overrides[packageName] = `$${packageName}`;
}
}
});
},
_restoreOriginalDependencies() {
debug('Restoring original package.json and node_modules');
let restoreTasks = [
copy(
path.join(this.cwd, this.packageJSONBackupFileName),
path.join(this.cwd, this.packageJSON)
),
];
let nodeModulesBackupLocation = path.join(this.cwd, this.nodeModulesBackupLocation);
if (fs.existsSync(nodeModulesBackupLocation)) {
restoreTasks.push(
copy(nodeModulesBackupLocation, path.join(this.cwd, this.nodeModules), { clobber: true })
);
}
let yarnLockBackupFileName = path.join(this.cwd, this.yarnLockBackupFileName);
if (fs.existsSync(yarnLockBackupFileName)) {
restoreTasks.push(copy(yarnLockBackupFileName, path.join(this.cwd, this.yarnLock)));
}
let npmShrinkWrapBackupFileName = path.join(this.cwd, this.npmShrinkWrapBackupFileName);
if (fs.existsSync(npmShrinkWrapBackupFileName)) {
restoreTasks.push(copy(npmShrinkWrapBackupFileName, path.join(this.cwd, this.npmShrinkWrap)));
}
let packageLockBackupFileName = path.join(this.cwd, this.packageLockBackupFileName);
if (fs.existsSync(packageLockBackupFileName)) {
restoreTasks.push(copy(packageLockBackupFileName, path.join(this.cwd, this.packageLock)));
}
return Promise.all(restoreTasks);
},
_backupOriginalDependencies() {
debug('Backing up package.json and node_modules');
let backupTasks = [
copy(
path.join(this.cwd, this.packageJSON),
path.join(this.cwd, this.packageJSONBackupFileName)
),
];
let nodeModulesPath = path.join(this.cwd, this.nodeModules);
if (fs.existsSync(nodeModulesPath)) {
backupTasks.push(
copy(nodeModulesPath, path.join(this.cwd, this.nodeModulesBackupLocation), {
clobber: true,
})
);
}
let yarnLockPath = path.join(this.cwd, this.yarnLock);
if (fs.existsSync(yarnLockPath)) {
backupTasks.push(copy(yarnLockPath, path.join(this.cwd, this.yarnLockBackupFileName)));
}
let npmShrinkWrapPath = path.join(this.cwd, this.npmShrinkWrap);
if (fs.existsSync(npmShrinkWrapPath)) {
backupTasks.push(
copy(npmShrinkWrapPath, path.join(this.cwd, this.npmShrinkWrapBackupFileName))
);
}
let packageLockPath = path.join(this.cwd, this.packageLock);
if (fs.existsSync(packageLockPath)) {
backupTasks.push(copy(packageLockPath, path.join(this.cwd, this.packageLockBackupFileName)));
}
return Promise.all(backupTasks);
},
});