From 598d57c31533a11fed3c71946c7f594c3fac72a3 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Fri, 14 Sep 2018 20:38:52 +0200
Subject: [PATCH] feat: binarySource
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Defaulting to ‘bundled’ (default behaviour), this option can be changed to ‘global’ if you wish Renovate to use globally installed npm, yarn, pnpm and lerna binaries. Note: composer always uses global regardless.
---
 lib/config/definitions.js                            |  7 +++++++
 lib/manager/npm/post-update/index.js                 | 12 ++++++++----
 lib/manager/npm/post-update/lerna.js                 | 11 ++++++++++-
 lib/manager/npm/post-update/npm.js                   | 11 ++++++++++-
 lib/manager/npm/post-update/pnpm.js                  |  5 ++++-
 lib/manager/npm/post-update/yarn.js                  |  5 ++++-
 test/workers/branch/lock-files/lerna.spec.js         |  4 +++-
 test/workers/branch/lock-files/npm.spec.js           |  4 +++-
 test/workers/branch/lock-files/pnpm.spec.js          |  6 +++++-
 test/workers/branch/lock-files/yarn.spec.js          |  6 +++++-
 .../updates/__snapshots__/flatten.spec.js.snap       |  8 ++++++++
 website/docs/self-hosted-configuration.md            |  4 ++++
 12 files changed, 71 insertions(+), 12 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index ec5ad8b3d3..f5705f514d 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -62,6 +62,13 @@ const options = [
     type: 'boolean',
     default: false,
   },
+  {
+    name: 'binarySource',
+    description: 'Where to source binaries like `npm` and `yarn` from',
+    admin: true,
+    type: 'string',
+    default: 'bundled',
+  },
   // Log options
   {
     name: 'logLevel',
diff --git a/lib/manager/npm/post-update/index.js b/lib/manager/npm/post-update/index.js
index 7cd2ec692d..04dbf257cf 100644
--- a/lib/manager/npm/post-update/index.js
+++ b/lib/manager/npm/post-update/index.js
@@ -303,7 +303,8 @@ async function getAdditionalFiles(config, packageFiles) {
       upath.join(config.localDir, lockFileDir),
       env,
       fileName,
-      config.skipInstalls
+      config.skipInstalls,
+      config.binarySource
     );
     if (res.error) {
       // istanbul ignore if
@@ -354,7 +355,8 @@ async function getAdditionalFiles(config, packageFiles) {
     const lockFileName = upath.join(lockFileDir, 'yarn.lock');
     const res = await yarn.generateLockFile(
       upath.join(config.localDir, lockFileDir),
-      env
+      env,
+      config.binarysource
     );
     if (res.error) {
       // istanbul ignore if
@@ -406,7 +408,8 @@ async function getAdditionalFiles(config, packageFiles) {
     logger.debug(`Generating shrinkwrap.yaml for ${lockFileDir}`);
     const res = await pnpm.generateLockFile(
       upath.join(config.localDir, lockFileDir),
-      env
+      env,
+      config.binarySource
     );
     if (res.error) {
       // istanbul ignore if
@@ -470,7 +473,8 @@ async function getAdditionalFiles(config, packageFiles) {
       lernaPackageFile.lernaClient,
       upath.join(config.localDir, lernaDir),
       env,
-      config.skipInstalls
+      config.skipInstalls,
+      config.binarySource
     );
     // istanbul ignore else
     if (res.error) {
diff --git a/lib/manager/npm/post-update/lerna.js b/lib/manager/npm/post-update/lerna.js
index 03590c1e35..09b6538359 100644
--- a/lib/manager/npm/post-update/lerna.js
+++ b/lib/manager/npm/post-update/lerna.js
@@ -4,7 +4,13 @@ module.exports = {
   generateLockFiles,
 };
 
-async function generateLockFiles(lernaClient, cwd, env, skipInstalls) {
+async function generateLockFiles(
+  lernaClient,
+  cwd,
+  env,
+  skipInstalls,
+  binarySource
+) {
   if (!lernaClient) {
     logger.warn('No lernaClient specified - returning');
     return { error: false };
@@ -38,6 +44,9 @@ async function generateLockFiles(lernaClient, cwd, env, skipInstalls) {
         '--ignore-scripts --ignore-engines --ignore-platform --mutex network:31879';
     }
     cmd = `npm i -g -C ~/.npm/lerna@${lernaVersion} lerna@${lernaVersion} && ${lernaClient} install ${params} && ~/.npm/lerna@${lernaVersion}/bin/lerna bootstrap -- ${params}`;
+    if (binarySource === 'global') {
+      cmd = `${lernaClient} install ${params} && lerna bootstrap -- ${params}`;
+    }
     logger.debug({ cmd });
     // TODO: Switch to native util.promisify once using only node 8
     ({ stdout, stderr } = await exec(cmd, {
diff --git a/lib/manager/npm/post-update/npm.js b/lib/manager/npm/post-update/npm.js
index 9559d900f9..3238f1b7f6 100644
--- a/lib/manager/npm/post-update/npm.js
+++ b/lib/manager/npm/post-update/npm.js
@@ -7,7 +7,13 @@ module.exports = {
   generateLockFile,
 };
 
-async function generateLockFile(cwd, env, filename, skipInstalls) {
+async function generateLockFile(
+  cwd,
+  env,
+  filename,
+  skipInstalls,
+  binarySource
+) {
   logger.debug(`Spawning npm install to create ${cwd}/${filename}`);
   let lockFile = null;
   let stdout;
@@ -52,6 +58,9 @@ async function generateLockFile(cwd, env, filename, skipInstalls) {
         }
       }
     }
+    if (binarySource === 'global') {
+      cmd = 'npm';
+    }
     cmd = `${cmd} --version && ${cmd} install`;
     if (skipInstalls) {
       cmd += ' --package-lock-only --no-audit';
diff --git a/lib/manager/npm/post-update/pnpm.js b/lib/manager/npm/post-update/pnpm.js
index ef925c54af..3e27610062 100644
--- a/lib/manager/npm/post-update/pnpm.js
+++ b/lib/manager/npm/post-update/pnpm.js
@@ -7,7 +7,7 @@ module.exports = {
   generateLockFile,
 };
 
-async function generateLockFile(cwd, env) {
+async function generateLockFile(cwd, env, binarySource) {
   logger.debug(`Spawning pnpm install to create ${cwd}/shrinkwrap.yaml`);
   let lockFile = null;
   let stdout;
@@ -52,6 +52,9 @@ async function generateLockFile(cwd, env) {
         }
       }
     }
+    if (binarySource === 'global') {
+      cmd = 'pnpm';
+    }
     logger.debug(`Using pnpm: ${cmd}`);
     cmd += ' install';
     cmd += ' --shrinkwrap-only';
diff --git a/lib/manager/npm/post-update/yarn.js b/lib/manager/npm/post-update/yarn.js
index 122d88214c..7dbae5a1f2 100644
--- a/lib/manager/npm/post-update/yarn.js
+++ b/lib/manager/npm/post-update/yarn.js
@@ -7,7 +7,7 @@ module.exports = {
   generateLockFile,
 };
 
-async function generateLockFile(cwd, env) {
+async function generateLockFile(cwd, env, binarySource) {
   logger.debug(`Spawning yarn install to create ${cwd}/yarn.lock`);
   let lockFile = null;
   let stdout;
@@ -52,6 +52,9 @@ async function generateLockFile(cwd, env) {
         }
       }
     }
+    if (binarySource) {
+      cmd = 'yarn';
+    }
     logger.debug(`Using yarn: ${cmd}`);
     cmd += ' install';
     cmd += ' --ignore-scripts';
diff --git a/test/workers/branch/lock-files/lerna.spec.js b/test/workers/branch/lock-files/lerna.spec.js
index 25f6700a25..06adb3ac97 100644
--- a/test/workers/branch/lock-files/lerna.spec.js
+++ b/test/workers/branch/lock-files/lerna.spec.js
@@ -28,11 +28,13 @@ describe('generateLockFiles()', () => {
     );
     exec.mockReturnValueOnce({});
     const skipInstalls = false;
+    const binarySource = 'global';
     const res = await lernaHelper.generateLockFiles(
       'npm',
       'some-dir',
       {},
-      skipInstalls
+      skipInstalls,
+      binarySource
     );
     expect(res.error).toBe(false);
   });
diff --git a/test/workers/branch/lock-files/npm.spec.js b/test/workers/branch/lock-files/npm.spec.js
index 3e6a131342..15a233b676 100644
--- a/test/workers/branch/lock-files/npm.spec.js
+++ b/test/workers/branch/lock-files/npm.spec.js
@@ -37,11 +37,13 @@ describe('generateLockFile', () => {
     });
     fs.readFile = jest.fn(() => 'package-lock-contents');
     const skipInstalls = false;
+    const binarySource = 'global';
     const res = await npmHelper.generateLockFile(
       'some-dir',
       {},
       'package-lock.json',
-      skipInstalls
+      skipInstalls,
+      binarySource
     );
     expect(fs.readFile.mock.calls.length).toEqual(1);
     expect(res.error).not.toBeDefined();
diff --git a/test/workers/branch/lock-files/pnpm.spec.js b/test/workers/branch/lock-files/pnpm.spec.js
index aca930976e..6cadb05f23 100644
--- a/test/workers/branch/lock-files/pnpm.spec.js
+++ b/test/workers/branch/lock-files/pnpm.spec.js
@@ -87,7 +87,11 @@ describe('generateLockFile', () => {
       stderror: '',
     });
     fs.readFile = jest.fn(() => 'package-lock-contents');
-    const res = await pnpmHelper.generateLockFile('some-dir');
+    const res = await pnpmHelper.generateLockFile(
+      'some-dir',
+      undefined,
+      'global'
+    );
     expect(fs.readFile.mock.calls.length).toEqual(1);
     expect(res.lockFile).toEqual('package-lock-contents');
   });
diff --git a/test/workers/branch/lock-files/yarn.spec.js b/test/workers/branch/lock-files/yarn.spec.js
index ce44c45953..3e442cb934 100644
--- a/test/workers/branch/lock-files/yarn.spec.js
+++ b/test/workers/branch/lock-files/yarn.spec.js
@@ -87,7 +87,11 @@ describe('generateLockFile', () => {
       stderror: '',
     });
     fs.readFile = jest.fn(() => 'package-lock-contents');
-    const res = await yarnHelper.generateLockFile('some-dir');
+    const res = await yarnHelper.generateLockFile(
+      'some-dir',
+      undefined,
+      'global'
+    );
     expect(fs.readFile.mock.calls.length).toEqual(1);
     expect(res.lockFile).toEqual('package-lock-contents');
   });
diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
index 5654110755..1272d80c04 100644
--- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
+++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
@@ -7,6 +7,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
@@ -78,6 +79,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
@@ -149,6 +151,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "lock-file-maintenance",
@@ -220,6 +223,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
@@ -291,6 +295,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "lock-file-maintenance",
@@ -362,6 +367,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
@@ -433,6 +439,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
@@ -504,6 +511,7 @@ Array [
     "automerge": false,
     "automergeComment": "automergeComment",
     "automergeType": "pr",
+    "binarySource": "bundled",
     "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
     "branchPrefix": "renovate/",
     "branchTopic": "{{{depNameSanitized}}}-{{{newMajor}}}{{#if isPatch}}.{{{newMinor}}}{{/if}}.x",
diff --git a/website/docs/self-hosted-configuration.md b/website/docs/self-hosted-configuration.md
index 51bbc56ecd..242756ccee 100644
--- a/website/docs/self-hosted-configuration.md
+++ b/website/docs/self-hosted-configuration.md
@@ -11,6 +11,10 @@ The below configuration options are applicable only if you are running your own
 
 Be cautious when using this option - it will run Renovate over _every_ repository that the bot account has access to.
 
+## binarySource
+
+Set this to 'global' if you wish Renovate to use globally-installed binaries (`npm`, `yarn`, etc) instead of using its bundled versions.
+
 ## endpoint
 
 ## exposeEnv
-- 
GitLab