-
-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1026 from bertdeblock/separate-yarn-adapter
Use a completely separate adapter for yarn
- Loading branch information
Showing
14 changed files
with
770 additions
and
799 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
'use strict'; | ||
|
||
const chalk = require('chalk'); | ||
const debug = require('debug'); | ||
const { get, set } = require('es-toolkit/compat'); | ||
const fs = require('fs-extra'); | ||
const path = require('node:path'); | ||
const semver = require('semver'); | ||
const Backup = require('../utils/backup'); | ||
const { LOCKFILE, PACKAGE_JSON } = require('../utils/package-managers'); | ||
|
||
class BaseAdapter { | ||
configKey = 'npm'; | ||
defaultInstallOptions = []; | ||
lockfile = ''; | ||
name = ''; | ||
overridesKey = ''; | ||
|
||
backup = null; | ||
debugFunction = null; | ||
|
||
constructor(options) { | ||
this.backup = new Backup({ cwd: options.cwd }); | ||
this.buildManagerOptions = options.buildManagerOptions; | ||
this.cwd = options.cwd; | ||
this.managerOptions = options.managerOptions; | ||
this.run = options.run ?? require('../utils/run'); | ||
} | ||
|
||
debug(...args) { | ||
if (this.debugFunction === null) { | ||
this.debugFunction = debug(`ember-try:dependency-manager-adapter:${this.name}`); | ||
} | ||
|
||
this.debugFunction(...args); | ||
} | ||
|
||
async setup(options = {}) { | ||
this._checkForDifferentLockfiles(options.ui); | ||
|
||
await this.backup.addFiles([PACKAGE_JSON, this.lockfile]); | ||
} | ||
|
||
async changeToDependencySet(dependencySet) { | ||
await this.applyDependencySet(dependencySet); | ||
await this._install(dependencySet); | ||
|
||
const dependencies = { | ||
...dependencySet.dependencies, | ||
...dependencySet.devDependencies, | ||
}; | ||
|
||
const currentDependencies = Object.keys(dependencies).map((name) => ({ | ||
name, | ||
packageManager: this.name, | ||
versionExpected: dependencies[name], | ||
versionSeen: this._findCurrentVersionOf(name), | ||
})); | ||
|
||
this.debug('Switched to dependencies: \n', currentDependencies); | ||
|
||
return currentDependencies; | ||
} | ||
|
||
async applyDependencySet(dependencySet) { | ||
if (dependencySet === undefined) { | ||
return; | ||
} | ||
|
||
this.debug('Changing to dependency set: %s', JSON.stringify(dependencySet)); | ||
|
||
const oldPackageJSON = JSON.parse(fs.readFileSync(this.backup.pathForFile(PACKAGE_JSON))); | ||
const newPackageJSON = this._packageJSONForDependencySet(oldPackageJSON, dependencySet); | ||
|
||
this.debug('Write package.json with: \n', JSON.stringify(newPackageJSON)); | ||
|
||
fs.writeFileSync(path.join(this.cwd, PACKAGE_JSON), JSON.stringify(newPackageJSON, null, 2)); | ||
} | ||
|
||
async cleanup() { | ||
try { | ||
await this.backup.restoreFiles([PACKAGE_JSON, this.lockfile]); | ||
await this.backup.cleanUp(); | ||
await this._install(); | ||
} catch (error) { | ||
console.error('Error cleaning up scenario:', error); | ||
} | ||
} | ||
|
||
_checkForDifferentLockfiles(ui) { | ||
for (const packageManager in LOCKFILE) { | ||
const lockfile = LOCKFILE[packageManager]; | ||
|
||
if (lockfile === this.lockfile) { | ||
continue; | ||
} | ||
|
||
try { | ||
if (fs.statSync(path.join(this.cwd, lockfile)).isFile()) { | ||
ui.writeLine( | ||
chalk.yellow( | ||
`Detected a \`${lockfile}\` file. Add \`packageManager: '${packageManager}'\` to your \`config/ember-try.js\` configuration file if you want to use ${packageManager} to install dependencies.`, | ||
), | ||
); | ||
} | ||
} catch { | ||
// Move along. | ||
} | ||
} | ||
} | ||
|
||
_findCurrentVersionOf(name) { | ||
const filename = path.join(this.cwd, 'node_modules', name, PACKAGE_JSON); | ||
|
||
if (fs.existsSync(filename)) { | ||
return JSON.parse(fs.readFileSync(filename)).version; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
async _install(dependencySet) { | ||
let managerOptions = this.managerOptions || []; | ||
|
||
if (typeof this.buildManagerOptions === 'function') { | ||
managerOptions = this.buildManagerOptions(dependencySet); | ||
|
||
if (Array.isArray(managerOptions) === false) { | ||
throw new Error('buildManagerOptions must return an array of options'); | ||
} | ||
} else if (this.defaultInstallOptions.length) { | ||
for (const option of this.defaultInstallOptions) { | ||
if (managerOptions.includes(option) === false) { | ||
managerOptions.push(option); | ||
} | ||
} | ||
} | ||
|
||
this.debug(`Running ${this.name} install with options %s`, managerOptions); | ||
|
||
await this.run(this.name, ['install', ...managerOptions], { cwd: this.cwd }); | ||
} | ||
|
||
_packageJSONForDependencySet(packageJSON, dependencySet) { | ||
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'dependencies'); | ||
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'devDependencies'); | ||
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'peerDependencies'); | ||
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'ember'); | ||
this._overridePackageJSONDependencies(packageJSON, dependencySet, this.overridesKey); | ||
|
||
return packageJSON; | ||
} | ||
|
||
_overridePackageJSONDependencies(packageJSON, dependencySet, kindOfDependency) { | ||
if (get(dependencySet, kindOfDependency) === undefined) { | ||
return; | ||
} | ||
|
||
let packageNames = Object.keys(get(dependencySet, kindOfDependency)); | ||
|
||
for (let packageName of packageNames) { | ||
let version = get(dependencySet, `${kindOfDependency}.${packageName}`); | ||
|
||
if (version === null) { | ||
delete get(packageJSON, kindOfDependency)[packageName]; | ||
} else { | ||
set(packageJSON, `${kindOfDependency}.${packageName}`, version); | ||
|
||
if (semver.prerelease(version) || /^https*:\/\/.*\.tg*z/.test(version)) { | ||
set(packageJSON, `${this.overridesKey}.${packageName}`, `$${packageName}`); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = { BaseAdapter }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,195 +1,11 @@ | ||
'use strict'; | ||
|
||
const fs = require('fs-extra'); | ||
const path = require('path'); | ||
const debug = require('debug')('ember-try:dependency-manager-adapter:npm'); | ||
const chalk = require('chalk'); | ||
const semver = require('semver'); | ||
const Backup = require('../utils/backup'); | ||
|
||
module.exports = class { | ||
configKey = 'npm'; | ||
packageJSON = 'package.json'; | ||
packageLock = 'package-lock.json'; | ||
useYarnCommand = false; | ||
yarnLock = 'yarn.lock'; | ||
|
||
constructor(options) { | ||
this.buildManagerOptions = options.buildManagerOptions; | ||
this.cwd = options.cwd; | ||
this.managerOptions = options.managerOptions; | ||
this.run = options.run || require('../utils/run'); | ||
this.useYarnCommand = options.useYarnCommand ?? false; | ||
|
||
this.backup = new Backup({ cwd: this.cwd }); | ||
} | ||
|
||
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(); | ||
} 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 `packageManager: 'yarn'` 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, 'node_modules', 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-package-lock') === -1) { | ||
mgrOptions = mgrOptions.concat(['--no-package-lock']); | ||
} | ||
} | ||
|
||
debug('Run npm/yarn install with options %s', mgrOptions); | ||
|
||
await this.run(cmd, [].concat(['install'], mgrOptions), { cwd: this.cwd }); | ||
} | ||
|
||
applyDependencySet(depSet) { | ||
debug('Changing to dependency set: %s', JSON.stringify(depSet)); | ||
|
||
if (!depSet) { | ||
return; | ||
} | ||
|
||
let backupPackageJSON = this.backup.pathForFile(this.packageJSON); | ||
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}`; | ||
} | ||
} | ||
}); | ||
} | ||
|
||
async _restoreOriginalDependencies() { | ||
await this.backup.restoreFiles([this.packageJSON, this.packageLock, this.yarnLock]); | ||
await this.backup.cleanUp(); | ||
await this._install(); | ||
} | ||
|
||
async _backupOriginalDependencies() { | ||
await this.backup.addFiles([this.packageJSON, this.packageLock, this.yarnLock]); | ||
} | ||
const { LOCKFILE } = require('../utils/package-managers'); | ||
const { BaseAdapter } = require('./base'); | ||
|
||
module.exports = class NpmAdapter extends BaseAdapter { | ||
defaultInstallOptions = ['--no-package-lock']; | ||
lockfile = LOCKFILE.npm; | ||
name = 'npm'; | ||
overridesKey = 'overrides'; | ||
}; |
Oops, something went wrong.