Skip to content

Commit

Permalink
Merge pull request #1026 from bertdeblock/separate-yarn-adapter
Browse files Browse the repository at this point in the history
Use a completely separate adapter for yarn
  • Loading branch information
bertdeblock authored Dec 13, 2024
2 parents 324be12 + c46951f commit e07ff04
Show file tree
Hide file tree
Showing 14 changed files with 770 additions and 799 deletions.
177 changes: 177 additions & 0 deletions lib/dependency-manager-adapters/base.js
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 };
200 changes: 8 additions & 192 deletions lib/dependency-manager-adapters/npm.js
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';
};
Loading

0 comments on commit e07ff04

Please sign in to comment.