diff --git a/src/api/extendOptions.ts b/src/api/extendOptions.ts index d8a9bcb9cd..8015d7daee 100644 --- a/src/api/extendOptions.ts +++ b/src/api/extendOptions.ts @@ -3,6 +3,7 @@ import globalBinPath = require('global-bin-path') import path = require('path') import logger from 'pnpm-logger' import expandTilde from '../fs/expandTilde' +import pnpmPkgJson from '../pnpmPkgJson' const DEFAULT_GLOBAL_PATH = path.join(globalBinPath(), 'pnpm-global') const DEFAULT_LOCAL_REGISTRY = expandTilde('~/.pnpm-registry') @@ -31,6 +32,7 @@ const defaults = () => ({ childConcurrency: 5, offline: false, registry: 'https://registry.npmjs.org/', + userAgent: `${pnpmPkgJson.name}/${pnpmPkgJson.version} npm/? node/${process.version} ${process.platform} ${process.arch}`, }) export default (opts?: PnpmOptions): StrictPnpmOptions => { diff --git a/src/api/install.ts b/src/api/install.ts index b0d1254903..fb51009122 100644 --- a/src/api/install.ts +++ b/src/api/install.ts @@ -133,7 +133,7 @@ async function installInContext ( const scripts = ctx.pkg && ctx.pkg.scripts || {} if (scripts['preinstall']) { - npmRun('preinstall', ctx.root) + npmRun('preinstall', ctx.root, opts.userAgent) } } @@ -225,7 +225,9 @@ async function installInContext ( R.uniqBy(linkedPkg => linkedPkg.hardlinkedLocation, R.values(linkedPkgsMap).filter(pkg => pkg.id === pkgId)) .map(pkg => limitChild(async () => { try { - await postInstall(pkg.hardlinkedLocation, installLogger(pkgId)) + await postInstall(pkg.hardlinkedLocation, installLogger(pkgId), { + userAgent: opts.userAgent + }) } catch (err) { if (installCtx.installs[pkgId].optional) { logger.warn({ @@ -244,10 +246,10 @@ async function installInContext ( const scripts = ctx.pkg && ctx.pkg.scripts || {} if (scripts['postinstall']) { - npmRun('postinstall', ctx.root) + npmRun('postinstall', ctx.root, opts.userAgent) } if (scripts['prepublish']) { - npmRun('prepublish', ctx.root) + npmRun('prepublish', ctx.root, opts.userAgent) } } } @@ -291,10 +293,11 @@ function adaptConfig (opts: StrictPnpmOptions) { } } -function npmRun (scriptName: string, pkgRoot: string) { +function npmRun (scriptName: string, pkgRoot: string, userAgent: string) { const result = runScriptSync('npm', ['run', scriptName], { cwd: pkgRoot, - stdio: 'inherit' + stdio: 'inherit', + userAgent, }) if (result.status !== 0) { process.exit(result.status) diff --git a/src/cmd/run.ts b/src/cmd/run.ts index fce1f5de3f..7827d7fd8d 100644 --- a/src/cmd/run.ts +++ b/src/cmd/run.ts @@ -5,5 +5,6 @@ export default function (input: string[], opts: PnpmOptions) { return runScriptSync('npm', ['run'].concat(input), { cwd: process.cwd(), stdio: 'inherit', + userAgent: undefined, }) } diff --git a/src/cmd/runNpm.ts b/src/cmd/runNpm.ts index e32a7d5549..e1b5bb794d 100644 --- a/src/cmd/runNpm.ts +++ b/src/cmd/runNpm.ts @@ -4,6 +4,7 @@ export default function runNpm (args: string[]) { const result = runScriptSync('npm', args, { cwd: process.cwd(), stdio: 'inherit', + userAgent: undefined, }) process.exit(result.status) } diff --git a/src/install/postInstall.ts b/src/install/postInstall.ts index 41dd60fdee..52074f681e 100644 --- a/src/install/postInstall.ts +++ b/src/install/postInstall.ts @@ -7,12 +7,18 @@ import readPkg from '../fs/readPkg' const pnpmNodeModules = findUp.sync('node_modules', {cwd: __dirname}) const nodeGyp = path.resolve(pnpmNodeModules, 'node-gyp/bin/node-gyp.js') -export default async function postInstall (root: string, log: Function) { +export default async function postInstall ( + root: string, + log: Function, + opts: { + userAgent: string + } +) { const pkg = await readPkg(root) const scripts = pkg && pkg.scripts || {} if (!scripts['install']) { - await checkBindingGyp(root, log) + await checkBindingGyp(root, log, opts) } if (scripts['install']) { @@ -25,7 +31,7 @@ export default async function postInstall (root: string, log: Function) { async function npmRunScript (scriptName: string) { if (!scripts[scriptName]) return - return runScript('npm', ['run', scriptName], { cwd: root, log }) + return runScript('npm', ['run', scriptName], { cwd: root, log, userAgent: opts.userAgent }) } } @@ -33,7 +39,13 @@ export default async function postInstall (root: string, log: Function) { * Run node-gyp when binding.gyp is available. Only do this when there's no * `install` script (see `npm help scripts`). */ -async function checkBindingGyp (root: string, log: Function) { +async function checkBindingGyp ( + root: string, + log: Function, + opts: { + userAgent: string + } +) { try { await fs.stat(path.join(root, 'binding.gyp')) } catch (err) { @@ -41,5 +53,5 @@ async function checkBindingGyp (root: string, log: Function) { return } } - return runScript(nodeGyp, ['rebuild'], { cwd: root, log }) + return runScript(nodeGyp, ['rebuild'], { cwd: root, log, userAgent: opts.userAgent }) } diff --git a/src/runScript.ts b/src/runScript.ts index 1a2908b0e0..dbb9602d1f 100644 --- a/src/runScript.ts +++ b/src/runScript.ts @@ -6,12 +6,15 @@ import PATH = require('path-name') const scriptLogger = logger('run_script') -export type RunScriptOptions = { - cwd: string, - log: Function -} - -export default function runScript (command: string, args: string[], opts: RunScriptOptions) { +export default function runScript ( + command: string, + args: string[], + opts: { + cwd: string, + log: Function, + userAgent: string, + } +) { opts = Object.assign({log: (() => {})}, opts) args = args || [] const log = opts.log @@ -21,7 +24,7 @@ export default function runScript (command: string, args: string[], opts: RunScr return new Promise((resolve, reject) => { const proc = spawn(command, args, { cwd: opts.cwd, - env: createEnv(opts.cwd) + env: createEnv(opts) }) log('stdout', '$ ' + script) @@ -37,24 +40,38 @@ export default function runScript (command: string, args: string[], opts: RunScr }) } -export type RunSyncScriptOptions = { - cwd: string, - stdio: string -} - -export function sync (command: string, args: string[], opts: RunSyncScriptOptions) { +export function sync ( + command: string, + args: string[], + opts: { + cwd: string, + stdio: string, + userAgent?: string, + } +) { opts = Object.assign({}, opts) return spawn.sync(command, args, Object.assign({}, opts, { - env: createEnv(opts.cwd) + env: createEnv(opts) })) } -function createEnv (cwd: string) { +function createEnv ( + opts: { + cwd: string, + userAgent?: string, + } +) { const env = Object.create(process.env) + env[PATH] = [ - path.join(cwd, 'node_modules', '.bin'), + path.join(opts.cwd, 'node_modules', '.bin'), path.dirname(process.execPath), process.env[PATH] ].join(path.delimiter) + + if (opts.userAgent) { + env['npm_config_user_agent'] = opts.userAgent + } + return env } diff --git a/src/types.ts b/src/types.ts index 0e22ad866c..4a08b1b2f5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -83,7 +83,7 @@ export type StrictPnpmOptions = { ca?: string, strictSsl: boolean, - userAgent?: string, + userAgent: string, tag: string, metaCache: Map, diff --git a/test/install/lifecycleScripts.ts b/test/install/lifecycleScripts.ts index 1fb5a56716..0768f0130e 100644 --- a/test/install/lifecycleScripts.ts +++ b/test/install/lifecycleScripts.ts @@ -6,6 +6,11 @@ import { testDefaults, execPnpmSync, } from '../utils' +import path = require('path') +import loadJsonFile = require('load-json-file') + +const pkgRoot = path.join(__dirname, '..', '..') +const pnpmPkg = loadJsonFile.sync(path.join(pkgRoot, 'package.json')) const test = promisifyTape(tape) @@ -42,6 +47,22 @@ test('installation fails if lifecycle script fails', t => { t.end() }) +test('lifecycle script runs with the correct user agent', t => { + const project = prepare(t, { + scripts: { + preinstall: 'node --eval "console.log(process.env.npm_config_user_agent)"' + }, + }) + + const result = execPnpmSync('install') + + t.equal(result.status, 0, 'installation was successfull') + const expectedUserAgent = `${pnpmPkg.name}/${pnpmPkg.version} npm/? node/${process.version} ${process.platform} ${process.arch}` + t.ok(result.stdout.toString().indexOf(expectedUserAgent) !== -1, 'correct npm_config_user_agent value') + + t.end() +}) + test('preinstall is executed before general installation', t => { const project = prepare(t, { scripts: {