diff --git a/README.md b/README.md index 259044d22f..b6d03b11d0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Like this project? Let people know with a [tweet](https://bit.ly/tweet-pnpm). * [Install](#install) * [Usage](#usage) * [Configuring](#configuring) + * [Hooks](#hooks) * [Benchmark](#benchmark) * [Limitations](#limitations) * [Frequently Asked Questions](#frequently-asked-questions) @@ -144,6 +145,32 @@ that rely on location but gives an average of **8% installation speed improvemen If false, doesn't check whether packages in the store were mutated. +### Hooks + +pnpm allows to step directly into the installation process via special functions called *hooks*. +Hooks can be declared in a file called `pnpmfile.js`. `pnpmfile.js` should live in the root of the project. + +An example of a `pnpmfile.js` that changes the dependencies field of a dependency: + +```js +module.exports = { + hooks: { + readPackage + } +} + +// This hook will override the manifest of foo@1 after downloading it from the registry +// foo@1 will always be installed with the second version of bar +function readPackage (pkg) { + if (pkg.name === 'foo' && pkg.version.startsWith('1.')) { + pkg.dependencies = { + bar: '^2.0.0' + } + } + return pkg +} +``` + ## Benchmark pnpm is faster than npm and Yarn. See [this](https://github.com/zkochan/node-package-manager-benchmark) diff --git a/package.json b/package.json index 4469d1a00c..6192958d04 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "pnpm-list": "^1.0.0", "pnpm-logger": "^0.5.4", "ramda": "^0.24.1", - "supi": "^0.2.16", + "supi": "^0.2.17", "update-notifier": "^2.1.0" }, "devDependencies": { diff --git a/shrinkwrap.yaml b/shrinkwrap.yaml index ce1b6924ee..a81eadef67 100644 --- a/shrinkwrap.yaml +++ b/shrinkwrap.yaml @@ -24,7 +24,7 @@ dependencies: pnpm-logger: 0.5.4 ramda: 0.24.1 rimraf: 2.6.1 - supi: 0.2.16 + supi: 0.2.17 update-notifier: 2.2.0 devDependencies: '@types/mkdirp': 0.5.1 @@ -2234,7 +2234,7 @@ packages: /strip-json-comments/2.0.1: resolution: integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo= - /supi/0.2.16: + /supi/0.2.17: dependencies: '@types/byline': 4.2.31 '@types/common-tags': 1.2.5 @@ -2292,7 +2292,7 @@ packages: write-pkg: 3.1.0 write-yaml-file: 1.0.0 resolution: - integrity: sha512-BKYNiwcn9LeMczysq2CoyDK1IWNBD+ar0oFDZ3hHZSu70jjNyQaErz09FzELvXH1cc29Ekm5i/SL8PSmp+dNjA== + integrity: sha512-vnfuOLiCHw4XxhG6fnyxo2oF/ly0U9LT/+y8d0IDSA+eukJGw6AhUsVEZhFdXh07txMRK2QFSMGFT4622YMYew== /supports-color/2.0.0: resolution: integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= @@ -2609,7 +2609,7 @@ specifiers: pnpm-logger: ^0.5.4 ramda: ^0.24.1 rimraf: ^2.5.4 - supi: ^0.2.16 + supi: ^0.2.17 tslint: ^5.4.2 typescript: ^2.4.1 update-notifier: ^2.1.0 diff --git a/src/cmd/install.ts b/src/cmd/install.ts index 29aa931486..96caf612fd 100644 --- a/src/cmd/install.ts +++ b/src/cmd/install.ts @@ -1,4 +1,6 @@ import {install, installPkgs, PnpmOptions} from 'supi' +import path = require('path') +import logger from 'pnpm-logger' /** * Perform installation. @@ -9,8 +11,30 @@ export default function installCmd (input: string[], opts: PnpmOptions) { // `pnpm install ""` is going to be just `pnpm install` input = input.filter(Boolean) + const prefix = opts.prefix || process.cwd() + opts['hooks'] = requireHooks(prefix) + if (!input || !input.length) { return install(opts) } return installPkgs(input, opts) } + +function requireHooks (prefix: string) { + try { + const pnpmFilePath = path.join(prefix, 'pnpmfile.js') + const pnpmFile = require(pnpmFilePath) + const hooks = pnpmFile && pnpmFile.hooks + if (!hooks) return {} + if (hooks.readPackage) { + if (typeof hooks.readPackage !== 'function') { + throw new TypeError('hooks.readPackage should be a function') + } + logger.info('readPackage hook is declared. Manifests of dependencies might get overridden') + } + return hooks + } catch (err) { + if (err['code'] !== 'MODULE_NOT_FOUND') throw err + return {} + } +} diff --git a/test/install/hooks.ts b/test/install/hooks.ts new file mode 100644 index 0000000000..5a83edad4a --- /dev/null +++ b/test/install/hooks.ts @@ -0,0 +1,35 @@ +import tape = require('tape') +import promisifyTape from 'tape-promise' +import { + prepare, + addDistTag, + execPnpm +} from '../utils' +import fs = require('mz/fs') + +const test = promisifyTape(tape) + +test('readPackage hook', async (t: tape.Test) => { + const project = prepare(t) + + await fs.writeFile('pnpmfile.js', ` + 'use strict' + module.exports = { + hooks: { + readPackage (pkg) { + if (pkg.name === 'pkg-with-1-dep') { + pkg.dependencies['dep-of-pkg-with-1-dep'] = '100.0.0' + } + return pkg + } + } + } + `, 'utf8') + + // w/o the hook, 100.1.0 would be installed + await addDistTag('dep-of-pkg-with-1-dep', '100.1.0', 'latest') + + await execPnpm('install', 'pkg-with-1-dep') + + await project.storeHas('dep-of-pkg-with-1-dep', '100.0.0') +}) diff --git a/test/install/index.ts b/test/install/index.ts index 494b5a4e8b..9c55732fdd 100644 --- a/test/install/index.ts +++ b/test/install/index.ts @@ -1,2 +1,3 @@ import './misc' import './lifecycleScripts' +import './hooks'