Skip to content

Commit c2e80da

Browse files
committed
Implement pnpm adapter
1 parent cb5f901 commit c2e80da

File tree

3 files changed

+518
-0
lines changed

3 files changed

+518
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
'use strict';
2+
3+
const CoreObject = require('core-object');
4+
const fs = require('fs-extra');
5+
const path = require('path');
6+
const debug = require('debug')('ember-try:dependency-manager-adapter:pnpm');
7+
8+
const PACKAGE_JSON = 'package.json';
9+
const PACKAGE_JSON_BACKUP = 'package.json.ember-try';
10+
const PNPM_LOCKFILE = 'pnpm-lock.yaml';
11+
12+
// Note: the upstream convention is to append `.ember-try` _after_ the file
13+
// extension, however this breaks syntax highlighting, so I've chosen to
14+
// insert it right before the file extension.
15+
const PNPM_LOCKFILE_BACKUP = 'pnpm-lock.ember-try.yaml';
16+
17+
module.exports = CoreObject.extend({
18+
// This still needs to be `npm` because we're still reading the dependencies
19+
// from the `npm` key of the ember-try config.
20+
configKey: 'npm',
21+
22+
init() {
23+
this._super.apply(this, arguments);
24+
this.run = this.run || require('../utils/run');
25+
},
26+
27+
async setup() {
28+
let pkg = path.join(this.cwd, PACKAGE_JSON);
29+
let pkgBackup = path.join(this.cwd, PACKAGE_JSON_BACKUP);
30+
debug(`Copying ${PACKAGE_JSON}`);
31+
await fs.copy(pkg, pkgBackup);
32+
33+
let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
34+
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
35+
if (fs.existsSync(lockFile)) {
36+
debug(`Copying ${PNPM_LOCKFILE}`);
37+
await fs.copy(lockFile, lockFileBackup);
38+
}
39+
},
40+
41+
async changeToDependencySet(depSet) {
42+
await 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: 'pnpm',
53+
};
54+
});
55+
56+
debug('Switched to dependencies: \n', currentDeps);
57+
58+
return currentDeps;
59+
},
60+
61+
async cleanup() {
62+
try {
63+
debug(`Restoring original ${PACKAGE_JSON}`);
64+
let pkg = path.join(this.cwd, PACKAGE_JSON);
65+
let pkgBackup = path.join(this.cwd, PACKAGE_JSON_BACKUP);
66+
await fs.copy(pkgBackup, pkg);
67+
await fs.remove(pkgBackup);
68+
69+
debug(`Restoring original ${PNPM_LOCKFILE}`);
70+
let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
71+
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
72+
await fs.copy(lockFileBackup, lockFile);
73+
await fs.remove(lockFileBackup);
74+
75+
await this._install();
76+
} catch (e) {
77+
console.log('Error cleaning up scenario:', e); // eslint-disable-line no-console
78+
}
79+
},
80+
81+
_findCurrentVersionOf(packageName) {
82+
let filename = path.join(this.cwd, 'node_modules', packageName, PACKAGE_JSON);
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+
93+
// buildManagerOptions overrides all default
94+
if (typeof this.buildManagerOptions === 'function') {
95+
mgrOptions = this.buildManagerOptions(depSet);
96+
97+
if (!Array.isArray(mgrOptions)) {
98+
throw new Error('buildManagerOptions must return an array of options');
99+
}
100+
} else if (!mgrOptions.includes('--frozen-lockfile=false')) {
101+
mgrOptions.push('--frozen-lockfile=false');
102+
}
103+
104+
// Note: We are explicitly *not* using `--no-lockfile` here, so that we
105+
// only have to resolve the dependencies that have actually changed.
106+
107+
debug('Run pnpm install with options %s', mgrOptions);
108+
109+
await this.run('pnpm', [].concat(['install'], mgrOptions), { cwd: this.cwd });
110+
},
111+
112+
async applyDependencySet(depSet) {
113+
debug('Changing to dependency set: %s', JSON.stringify(depSet));
114+
115+
if (!depSet) {
116+
return;
117+
}
118+
119+
let backupPackageJSON = path.join(this.cwd, PACKAGE_JSON_BACKUP);
120+
let packageJSONFile = path.join(this.cwd, PACKAGE_JSON);
121+
let packageJSON = JSON.parse(fs.readFileSync(backupPackageJSON));
122+
let newPackageJSON = this._packageJSONForDependencySet(packageJSON, depSet);
123+
124+
debug('Write package.json with: \n', JSON.stringify(newPackageJSON));
125+
fs.writeFileSync(packageJSONFile, JSON.stringify(newPackageJSON, null, 2));
126+
127+
// We restore the original lockfile here, so that we always create a minimal
128+
// diff compared to the original locked dependency set.
129+
130+
let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
131+
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
132+
if (fs.existsSync(lockFileBackup)) {
133+
debug(`Restoring original ${PNPM_LOCKFILE}`);
134+
await fs.copy(lockFileBackup, lockFile);
135+
}
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+
// see https://pnpm.io/package_json#pnpmoverrides
145+
this._overridePackageJSONDependencies(packageJSON, depSet, 'overrides');
146+
147+
return packageJSON;
148+
},
149+
150+
_overridePackageJSONDependencies(packageJSON, depSet, kindOfDependency) {
151+
if (!depSet[kindOfDependency]) {
152+
return;
153+
}
154+
155+
let packageNames = Object.keys(depSet[kindOfDependency]);
156+
157+
for (let packageName of packageNames) {
158+
if (!packageJSON[kindOfDependency]) {
159+
packageJSON[kindOfDependency] = {};
160+
}
161+
162+
let version = depSet[kindOfDependency][packageName];
163+
if (version === null) {
164+
delete packageJSON[kindOfDependency][packageName];
165+
} else {
166+
packageJSON[kindOfDependency][packageName] = version;
167+
}
168+
}
169+
},
170+
});

lib/utils/dependency-manager-adapter-factory.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const NpmAdapter = require('../dependency-manager-adapters/npm');
4+
const PnpmAdapter = require('../dependency-manager-adapters/pnpm');
45
const WorkspaceAdapter = require('../dependency-manager-adapters/workspace');
56

67
module.exports = {
@@ -34,6 +35,18 @@ module.exports = {
3435
buildManagerOptions: config.buildManagerOptions,
3536
})
3637
);
38+
} else if (config.usePnpm) {
39+
console.warn(
40+
'pnpm support is experimental for now. if you notice any problems please open an issue.'
41+
);
42+
43+
adapters.push(
44+
new PnpmAdapter({
45+
cwd: root,
46+
managerOptions: config.npmOptions,
47+
buildManagerOptions: config.buildManagerOptions,
48+
})
49+
);
3750
} else if (hasNpm) {
3851
adapters.push(
3952
new NpmAdapter({

0 commit comments

Comments
 (0)