Skip to content

Commit e07ff04

Browse files
authored
Merge pull request #1026 from bertdeblock/separate-yarn-adapter
Use a completely separate adapter for yarn
2 parents 324be12 + c46951f commit e07ff04

14 files changed

Lines changed: 770 additions & 799 deletions

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
'use strict';
2+
3+
const chalk = require('chalk');
4+
const debug = require('debug');
5+
const { get, set } = require('es-toolkit/compat');
6+
const fs = require('fs-extra');
7+
const path = require('node:path');
8+
const semver = require('semver');
9+
const Backup = require('../utils/backup');
10+
const { LOCKFILE, PACKAGE_JSON } = require('../utils/package-managers');
11+
12+
class BaseAdapter {
13+
configKey = 'npm';
14+
defaultInstallOptions = [];
15+
lockfile = '';
16+
name = '';
17+
overridesKey = '';
18+
19+
backup = null;
20+
debugFunction = null;
21+
22+
constructor(options) {
23+
this.backup = new Backup({ cwd: options.cwd });
24+
this.buildManagerOptions = options.buildManagerOptions;
25+
this.cwd = options.cwd;
26+
this.managerOptions = options.managerOptions;
27+
this.run = options.run ?? require('../utils/run');
28+
}
29+
30+
debug(...args) {
31+
if (this.debugFunction === null) {
32+
this.debugFunction = debug(`ember-try:dependency-manager-adapter:${this.name}`);
33+
}
34+
35+
this.debugFunction(...args);
36+
}
37+
38+
async setup(options = {}) {
39+
this._checkForDifferentLockfiles(options.ui);
40+
41+
await this.backup.addFiles([PACKAGE_JSON, this.lockfile]);
42+
}
43+
44+
async changeToDependencySet(dependencySet) {
45+
await this.applyDependencySet(dependencySet);
46+
await this._install(dependencySet);
47+
48+
const dependencies = {
49+
...dependencySet.dependencies,
50+
...dependencySet.devDependencies,
51+
};
52+
53+
const currentDependencies = Object.keys(dependencies).map((name) => ({
54+
name,
55+
packageManager: this.name,
56+
versionExpected: dependencies[name],
57+
versionSeen: this._findCurrentVersionOf(name),
58+
}));
59+
60+
this.debug('Switched to dependencies: \n', currentDependencies);
61+
62+
return currentDependencies;
63+
}
64+
65+
async applyDependencySet(dependencySet) {
66+
if (dependencySet === undefined) {
67+
return;
68+
}
69+
70+
this.debug('Changing to dependency set: %s', JSON.stringify(dependencySet));
71+
72+
const oldPackageJSON = JSON.parse(fs.readFileSync(this.backup.pathForFile(PACKAGE_JSON)));
73+
const newPackageJSON = this._packageJSONForDependencySet(oldPackageJSON, dependencySet);
74+
75+
this.debug('Write package.json with: \n', JSON.stringify(newPackageJSON));
76+
77+
fs.writeFileSync(path.join(this.cwd, PACKAGE_JSON), JSON.stringify(newPackageJSON, null, 2));
78+
}
79+
80+
async cleanup() {
81+
try {
82+
await this.backup.restoreFiles([PACKAGE_JSON, this.lockfile]);
83+
await this.backup.cleanUp();
84+
await this._install();
85+
} catch (error) {
86+
console.error('Error cleaning up scenario:', error);
87+
}
88+
}
89+
90+
_checkForDifferentLockfiles(ui) {
91+
for (const packageManager in LOCKFILE) {
92+
const lockfile = LOCKFILE[packageManager];
93+
94+
if (lockfile === this.lockfile) {
95+
continue;
96+
}
97+
98+
try {
99+
if (fs.statSync(path.join(this.cwd, lockfile)).isFile()) {
100+
ui.writeLine(
101+
chalk.yellow(
102+
`Detected a \`${lockfile}\` file. Add \`packageManager: '${packageManager}'\` to your \`config/ember-try.js\` configuration file if you want to use ${packageManager} to install dependencies.`,
103+
),
104+
);
105+
}
106+
} catch {
107+
// Move along.
108+
}
109+
}
110+
}
111+
112+
_findCurrentVersionOf(name) {
113+
const filename = path.join(this.cwd, 'node_modules', name, PACKAGE_JSON);
114+
115+
if (fs.existsSync(filename)) {
116+
return JSON.parse(fs.readFileSync(filename)).version;
117+
} else {
118+
return null;
119+
}
120+
}
121+
122+
async _install(dependencySet) {
123+
let managerOptions = this.managerOptions || [];
124+
125+
if (typeof this.buildManagerOptions === 'function') {
126+
managerOptions = this.buildManagerOptions(dependencySet);
127+
128+
if (Array.isArray(managerOptions) === false) {
129+
throw new Error('buildManagerOptions must return an array of options');
130+
}
131+
} else if (this.defaultInstallOptions.length) {
132+
for (const option of this.defaultInstallOptions) {
133+
if (managerOptions.includes(option) === false) {
134+
managerOptions.push(option);
135+
}
136+
}
137+
}
138+
139+
this.debug(`Running ${this.name} install with options %s`, managerOptions);
140+
141+
await this.run(this.name, ['install', ...managerOptions], { cwd: this.cwd });
142+
}
143+
144+
_packageJSONForDependencySet(packageJSON, dependencySet) {
145+
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'dependencies');
146+
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'devDependencies');
147+
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'peerDependencies');
148+
this._overridePackageJSONDependencies(packageJSON, dependencySet, 'ember');
149+
this._overridePackageJSONDependencies(packageJSON, dependencySet, this.overridesKey);
150+
151+
return packageJSON;
152+
}
153+
154+
_overridePackageJSONDependencies(packageJSON, dependencySet, kindOfDependency) {
155+
if (get(dependencySet, kindOfDependency) === undefined) {
156+
return;
157+
}
158+
159+
let packageNames = Object.keys(get(dependencySet, kindOfDependency));
160+
161+
for (let packageName of packageNames) {
162+
let version = get(dependencySet, `${kindOfDependency}.${packageName}`);
163+
164+
if (version === null) {
165+
delete get(packageJSON, kindOfDependency)[packageName];
166+
} else {
167+
set(packageJSON, `${kindOfDependency}.${packageName}`, version);
168+
169+
if (semver.prerelease(version) || /^https*:\/\/.*\.tg*z/.test(version)) {
170+
set(packageJSON, `${this.overridesKey}.${packageName}`, `$${packageName}`);
171+
}
172+
}
173+
}
174+
}
175+
}
176+
177+
module.exports = { BaseAdapter };
Lines changed: 8 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,11 @@
11
'use strict';
22

3-
const fs = require('fs-extra');
4-
const path = require('path');
5-
const debug = require('debug')('ember-try:dependency-manager-adapter:npm');
6-
const chalk = require('chalk');
7-
const semver = require('semver');
8-
const Backup = require('../utils/backup');
9-
10-
module.exports = class {
11-
configKey = 'npm';
12-
packageJSON = 'package.json';
13-
packageLock = 'package-lock.json';
14-
useYarnCommand = false;
15-
yarnLock = 'yarn.lock';
16-
17-
constructor(options) {
18-
this.buildManagerOptions = options.buildManagerOptions;
19-
this.cwd = options.cwd;
20-
this.managerOptions = options.managerOptions;
21-
this.run = options.run || require('../utils/run');
22-
this.useYarnCommand = options.useYarnCommand ?? false;
23-
24-
this.backup = new Backup({ cwd: this.cwd });
25-
}
26-
27-
async setup(options) {
28-
if (!options) {
29-
options = {};
30-
}
31-
32-
this._runYarnCheck(options.ui);
33-
34-
return await this._backupOriginalDependencies();
35-
}
36-
37-
async changeToDependencySet(depSet) {
38-
this.applyDependencySet(depSet);
39-
40-
await this._install(depSet);
41-
42-
let deps = Object.assign({}, depSet.dependencies, depSet.devDependencies);
43-
let currentDeps = Object.keys(deps).map((dep) => {
44-
return {
45-
name: dep,
46-
versionExpected: deps[dep],
47-
versionSeen: this._findCurrentVersionOf(dep),
48-
packageManager: this.useYarnCommand ? 'yarn' : 'npm',
49-
};
50-
});
51-
52-
debug('Switched to dependencies: \n', currentDeps);
53-
54-
return currentDeps;
55-
}
56-
57-
async cleanup() {
58-
try {
59-
await this._restoreOriginalDependencies();
60-
} catch (e) {
61-
console.log('Error cleaning up npm scenario:', e); // eslint-disable-line no-console
62-
}
63-
}
64-
65-
_runYarnCheck(ui) {
66-
if (!this.useYarnCommand) {
67-
try {
68-
if (fs.statSync(path.join(this.cwd, this.yarnLock)).isFile()) {
69-
ui.writeLine(
70-
chalk.yellow(
71-
"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.",
72-
),
73-
);
74-
}
75-
} catch (e) {
76-
// If no yarn.lock is found, no need to warn.
77-
}
78-
}
79-
}
80-
81-
_findCurrentVersionOf(packageName) {
82-
let filename = path.join(this.cwd, 'node_modules', packageName, this.packageJSON);
83-
if (fs.existsSync(filename)) {
84-
return JSON.parse(fs.readFileSync(filename)).version;
85-
} else {
86-
return null;
87-
}
88-
}
89-
90-
async _install(depSet) {
91-
let mgrOptions = this.managerOptions || [];
92-
let cmd = this.useYarnCommand ? 'yarn' : 'npm';
93-
94-
// buildManagerOptions overrides all default
95-
if (typeof this.buildManagerOptions === 'function') {
96-
mgrOptions = this.buildManagerOptions(depSet);
97-
98-
if (!Array.isArray(mgrOptions)) {
99-
throw new Error('buildManagerOptions must return an array of options');
100-
}
101-
} else {
102-
if (this.useYarnCommand) {
103-
if (mgrOptions.indexOf('--no-lockfile') === -1) {
104-
mgrOptions = mgrOptions.concat(['--no-lockfile']);
105-
}
106-
// npm warns on incompatible engines
107-
// yarn errors, not a good experience
108-
if (mgrOptions.indexOf('--ignore-engines') === -1) {
109-
mgrOptions = mgrOptions.concat(['--ignore-engines']);
110-
}
111-
} else if (mgrOptions.indexOf('--no-package-lock') === -1) {
112-
mgrOptions = mgrOptions.concat(['--no-package-lock']);
113-
}
114-
}
115-
116-
debug('Run npm/yarn install with options %s', mgrOptions);
117-
118-
await this.run(cmd, [].concat(['install'], mgrOptions), { cwd: this.cwd });
119-
}
120-
121-
applyDependencySet(depSet) {
122-
debug('Changing to dependency set: %s', JSON.stringify(depSet));
123-
124-
if (!depSet) {
125-
return;
126-
}
127-
128-
let backupPackageJSON = this.backup.pathForFile(this.packageJSON);
129-
let packageJSONFile = path.join(this.cwd, this.packageJSON);
130-
let packageJSON = JSON.parse(fs.readFileSync(backupPackageJSON));
131-
let newPackageJSON = this._packageJSONForDependencySet(packageJSON, depSet);
132-
133-
debug('Write package.json with: \n', JSON.stringify(newPackageJSON));
134-
135-
fs.writeFileSync(packageJSONFile, JSON.stringify(newPackageJSON, null, 2));
136-
}
137-
138-
_packageJSONForDependencySet(packageJSON, depSet) {
139-
this._overridePackageJSONDependencies(packageJSON, depSet, 'dependencies');
140-
this._overridePackageJSONDependencies(packageJSON, depSet, 'devDependencies');
141-
this._overridePackageJSONDependencies(packageJSON, depSet, 'peerDependencies');
142-
this._overridePackageJSONDependencies(packageJSON, depSet, 'ember');
143-
144-
if (this.useYarnCommand) {
145-
this._overridePackageJSONDependencies(packageJSON, depSet, 'resolutions');
146-
} else {
147-
this._overridePackageJSONDependencies(packageJSON, depSet, 'overrides');
148-
}
149-
150-
return packageJSON;
151-
}
152-
153-
_overridePackageJSONDependencies(packageJSON, depSet, kindOfDependency) {
154-
if (!depSet[kindOfDependency]) {
155-
return;
156-
}
157-
158-
let packageNames = Object.keys(depSet[kindOfDependency]);
159-
160-
packageNames.forEach((packageName) => {
161-
if (!packageJSON[kindOfDependency]) {
162-
packageJSON[kindOfDependency] = {};
163-
}
164-
165-
let version = depSet[kindOfDependency][packageName];
166-
if (version === null) {
167-
delete packageJSON[kindOfDependency][packageName];
168-
} else {
169-
packageJSON[kindOfDependency][packageName] = version;
170-
171-
// in npm we need to always add an override if the version is a pre-release
172-
if (
173-
!this.useYarnCommand &&
174-
(semver.prerelease(version) || /^https*:\/\/.*\.tg*z/.test(version))
175-
) {
176-
if (!packageJSON.overrides) {
177-
packageJSON.overrides = {};
178-
}
179-
180-
packageJSON.overrides[packageName] = `$${packageName}`;
181-
}
182-
}
183-
});
184-
}
185-
186-
async _restoreOriginalDependencies() {
187-
await this.backup.restoreFiles([this.packageJSON, this.packageLock, this.yarnLock]);
188-
await this.backup.cleanUp();
189-
await this._install();
190-
}
191-
192-
async _backupOriginalDependencies() {
193-
await this.backup.addFiles([this.packageJSON, this.packageLock, this.yarnLock]);
194-
}
3+
const { LOCKFILE } = require('../utils/package-managers');
4+
const { BaseAdapter } = require('./base');
5+
6+
module.exports = class NpmAdapter extends BaseAdapter {
7+
defaultInstallOptions = ['--no-package-lock'];
8+
lockfile = LOCKFILE.npm;
9+
name = 'npm';
10+
overridesKey = 'overrides';
19511
};

0 commit comments

Comments
 (0)