Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement pnpm adapter #787

Merged
merged 1 commit into from Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
170 changes: 170 additions & 0 deletions lib/dependency-manager-adapters/pnpm.js
@@ -0,0 +1,170 @@
'use strict';

const CoreObject = require('core-object');
const fs = require('fs-extra');
const path = require('path');
const debug = require('debug')('ember-try:dependency-manager-adapter:pnpm');

const PACKAGE_JSON = 'package.json';
const PACKAGE_JSON_BACKUP = 'package.json.ember-try';
const PNPM_LOCKFILE = 'pnpm-lock.yaml';

// Note: the upstream convention is to append `.ember-try` _after_ the file
// extension, however this breaks syntax highlighting, so I've chosen to
// insert it right before the file extension.
const PNPM_LOCKFILE_BACKUP = 'pnpm-lock.ember-try.yaml';

module.exports = CoreObject.extend({
// This still needs to be `npm` because we're still reading the dependencies
// from the `npm` key of the ember-try config.
configKey: 'npm',

init() {
this._super.apply(this, arguments);
this.run = this.run || require('../utils/run');
},

async setup() {
let pkg = path.join(this.cwd, PACKAGE_JSON);
let pkgBackup = path.join(this.cwd, PACKAGE_JSON_BACKUP);
debug(`Copying ${PACKAGE_JSON}`);
await fs.copy(pkg, pkgBackup);

let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
if (fs.existsSync(lockFile)) {
debug(`Copying ${PNPM_LOCKFILE}`);
await fs.copy(lockFile, lockFileBackup);
}
},

async changeToDependencySet(depSet) {
await 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: 'pnpm',
};
});

debug('Switched to dependencies: \n', currentDeps);

return currentDeps;
},

async cleanup() {
try {
debug(`Restoring original ${PACKAGE_JSON}`);
let pkg = path.join(this.cwd, PACKAGE_JSON);
let pkgBackup = path.join(this.cwd, PACKAGE_JSON_BACKUP);
await fs.copy(pkgBackup, pkg);
await fs.remove(pkgBackup);

debug(`Restoring original ${PNPM_LOCKFILE}`);
let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
await fs.copy(lockFileBackup, lockFile);
await fs.remove(lockFileBackup);

await this._install();
} catch (e) {
console.log('Error cleaning up scenario:', e); // eslint-disable-line no-console
}
},

_findCurrentVersionOf(packageName) {
let filename = path.join(this.cwd, 'node_modules', packageName, PACKAGE_JSON);
if (fs.existsSync(filename)) {
return JSON.parse(fs.readFileSync(filename)).version;
} else {
return null;
}
},

async _install(depSet) {
let mgrOptions = this.managerOptions || [];

// 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 (!mgrOptions.includes('--frozen-lockfile=false')) {
mgrOptions.push('--frozen-lockfile=false');
}

// Note: We are explicitly *not* using `--no-lockfile` here, so that we
// only have to resolve the dependencies that have actually changed.

debug('Run pnpm install with options %s', mgrOptions);

await this.run('pnpm', [].concat(['install'], mgrOptions), { cwd: this.cwd });
},

async applyDependencySet(depSet) {
debug('Changing to dependency set: %s', JSON.stringify(depSet));

if (!depSet) {
return;
}

let backupPackageJSON = path.join(this.cwd, PACKAGE_JSON_BACKUP);
let packageJSONFile = path.join(this.cwd, PACKAGE_JSON);
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));

// We restore the original lockfile here, so that we always create a minimal
// diff compared to the original locked dependency set.

let lockFile = path.join(this.cwd, PNPM_LOCKFILE);
let lockFileBackup = path.join(this.cwd, PNPM_LOCKFILE_BACKUP);
if (fs.existsSync(lockFileBackup)) {
debug(`Restoring original ${PNPM_LOCKFILE}`);
await fs.copy(lockFileBackup, lockFile);
}
},

_packageJSONForDependencySet(packageJSON, depSet) {
this._overridePackageJSONDependencies(packageJSON, depSet, 'dependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'devDependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'peerDependencies');
this._overridePackageJSONDependencies(packageJSON, depSet, 'ember');

// see https://pnpm.io/package_json#pnpmoverrides
this._overridePackageJSONDependencies(packageJSON, depSet, 'overrides');

return packageJSON;
},

_overridePackageJSONDependencies(packageJSON, depSet, kindOfDependency) {
if (!depSet[kindOfDependency]) {
return;
}

let packageNames = Object.keys(depSet[kindOfDependency]);

for (let packageName of packageNames) {
if (!packageJSON[kindOfDependency]) {
packageJSON[kindOfDependency] = {};
}

let version = depSet[kindOfDependency][packageName];
if (version === null) {
delete packageJSON[kindOfDependency][packageName];
} else {
packageJSON[kindOfDependency][packageName] = version;
}
}
},
});
13 changes: 13 additions & 0 deletions lib/utils/dependency-manager-adapter-factory.js
@@ -1,6 +1,7 @@
'use strict';

const NpmAdapter = require('../dependency-manager-adapters/npm');
const PnpmAdapter = require('../dependency-manager-adapters/pnpm');
const WorkspaceAdapter = require('../dependency-manager-adapters/workspace');

module.exports = {
Expand Down Expand Up @@ -34,6 +35,18 @@ module.exports = {
buildManagerOptions: config.buildManagerOptions,
})
);
} else if (config.usePnpm) {
console.warn(
'pnpm support is experimental for now. if you notice any problems please open an issue.'
);

adapters.push(
new PnpmAdapter({
cwd: root,
managerOptions: config.npmOptions,
buildManagerOptions: config.buildManagerOptions,
})
);
} else if (hasNpm) {
adapters.push(
new NpmAdapter({
Expand Down