|
| 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 | +}); |
0 commit comments