From 16d36a15e2106b9d873974b9ba43d811e5ba1209 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@keylocation.sg>
Date: Tue, 29 Aug 2017 09:25:44 +0200
Subject: [PATCH] feat: add npmToken, npmrc and yarnrc configuration support
 (#753)

Renovate config can now support the fields `npmToken`, `npmrc` and `yarnrc`. `npmrc` and `yarnrc` (note no `.` prefix) can be used as an alternative to checking the respective files into the repo and have the same effect. `npmToken` is a shorter alternative and allows for just the npm token to be added, defaulting to the public npm registry.
---
 docs/configuration.md                         | 27 +++++++++++++++++++
 docs/faq.md                                   |  8 +++---
 lib/config/definitions.js                     | 19 +++++++++++++
 lib/config/massage.js                         |  3 +++
 lib/config/presets.js                         |  4 +--
 lib/logger/config-serializer.js               |  8 +++++-
 lib/workers/branch/lock-files.js              | 11 ++++++++
 lib/workers/package-file/index.js             |  4 +--
 lib/workers/package/index.js                  |  6 +----
 lib/workers/repository/apis.js                | 21 +++++++++------
 lib/workers/repository/upgrades.js            |  1 -
 .../config/__snapshots__/massage.spec.js.snap |  7 +++++
 test/config/massage.spec.js                   |  6 +++++
 test/workers/branch/lock-files.spec.js        |  4 ++-
 .../package/__snapshots__/index.spec.js.snap  |  8 ++++++
 .../__snapshots__/apis.spec.js.snap           |  4 ---
 test/workers/repository/apis.spec.js          |  9 ++++---
 17 files changed, 119 insertions(+), 31 deletions(-)

diff --git a/docs/configuration.md b/docs/configuration.md
index 9dc387908b..1b00948f7c 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -79,6 +79,9 @@ $ node renovate --help
     --platform <string>                  Platform type of repository
     --endpoint <string>                  Custom endpoint to use
     --token <string>                     Repository Auth Token
+    --npmrc <string>                     String copy of npmrc file. Use \n instead of line breaks
+    --yarnrc <string>                    String copy of yarnrc file. Use \n instead of line breaks
+    --autodiscover [boolean]             Autodiscover all repositories
     --autodiscover [boolean]             Autodiscover all repositories
     --github-app-id <integer>            GitHub App ID (enables GitHub App functionality if set)
     --github-app-key <string>            GitHub App Private Key (.pem file contents)
@@ -250,6 +253,30 @@ Obviously, you can't set repository or package file location with this method.
   <td>`RENOVATE_TOKEN`</td>
   <td>`--token`<td>
 </tr>
+<tr>
+  <td>`npmrc`</td>
+  <td>String copy of npmrc file. Use \n instead of line breaks</td>
+  <td>string</td>
+  <td><pre>null</pre></td>
+  <td>`RENOVATE_NPMRC`</td>
+  <td>`--npmrc`<td>
+</tr>
+<tr>
+  <td>`yarnrc`</td>
+  <td>String copy of yarnrc file. Use \n instead of line breaks</td>
+  <td>string</td>
+  <td><pre>null</pre></td>
+  <td>`RENOVATE_YARNRC`</td>
+  <td>`--yarnrc`<td>
+</tr>
+<tr>
+  <td>`autodiscover`</td>
+  <td>Autodiscover all repositories</td>
+  <td>boolean</td>
+  <td><pre>false</pre></td>
+  <td>`RENOVATE_AUTODISCOVER`</td>
+  <td>`--autodiscover`<td>
+</tr>
 <tr>
   <td>`autodiscover`</td>
   <td>Autodiscover all repositories</td>
diff --git a/docs/faq.md b/docs/faq.md
index de5b181ab2..e24072cd7d 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -33,12 +33,14 @@ If for example your repository default branch is `master` but your Pull Requests
 
 ### Support private npm modules
 
-If you are running your own Renovate instance, then the easiest way to support private modules is to make sure the appropriate credentials are in `~/.npmrc`;
+If you are running your own Renovate instance, then the easiest way to support private modules is to make sure the appropriate credentials are in `.npmrc` or `~/.npmrc`;
 
-If you are using hosted Renovate instance, and your repository `package.json` includes private modules, then you can:
+If you are using a hosted Renovate instance (such as the Renovate app), and your `package.json` includes private modules, then you can:
 
 1.  Commit an `.npmrc` file to the repository, and Renovate will use this, or
-2.  If using the [GitHub App hosted service](https://github.com/apps/renovate), authorize the npm user named "renovate" with read-only access to the relevant modules. This "renovate" account is used solely for the purpose of the renovate GitHub App.
+2.  Add the contents of your `.npmrc` file to the config field `npmrc` in your `renovate.json` or `package.json` renovate config
+3. Add a valid npm authToken to the config field `npmToken` in your `renovate.json` or `package.json` renovate config
+4.  If using the [GitHub App hosted service](https://github.com/apps/renovate), authorize the npm user named "renovate" with read-only access to the relevant modules. This "renovate" account is used solely for the purpose of the renovate GitHub App.
 
 ### Control renovate's schedule
 
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 146f2ec50b..2dca42af88 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -111,6 +111,25 @@ const options = [
     stage: 'repository',
     type: 'string',
   },
+  {
+    name: 'npmrc',
+    description: 'String copy of npmrc file. Use \\n instead of line breaks',
+    stage: 'branch',
+    type: 'string',
+  },
+  {
+    name: 'yarnrc',
+    description: 'String copy of yarnrc file. Use \\n instead of line breaks',
+    stage: 'branch',
+    type: 'string',
+  },
+  {
+    name: 'autodiscover',
+    description: 'Autodiscover all repositories',
+    stage: 'repository',
+    type: 'boolean',
+    default: false,
+  },
   {
     name: 'autodiscover',
     description: 'Autodiscover all repositories',
diff --git a/lib/config/massage.js b/lib/config/massage.js
index 80391a8cd5..dd66e43825 100644
--- a/lib/config/massage.js
+++ b/lib/config/massage.js
@@ -18,6 +18,9 @@ function massageConfig(config) {
     const val = config[key];
     if (allowedStrings.includes(key) && typeof val === 'string') {
       massagedConfig[key] = [val];
+    } else if (key === 'npmToken') {
+      massagedConfig.npmrc = `//registry.npmjs.org/:_authToken=${val}\n`;
+      delete massagedConfig.npmToken;
     } else if (isObject(val)) {
       massagedConfig[key] = massageConfig(val);
     } else if (Array.isArray(val)) {
diff --git a/lib/config/presets.js b/lib/config/presets.js
index 584c537fdd..0e76d809c3 100644
--- a/lib/config/presets.js
+++ b/lib/config/presets.js
@@ -64,8 +64,8 @@ async function resolveConfigPresets(
       }
     }
   }
-  logger.debug({ config: inputConfig }, 'Input config');
-  logger.debug({ config }, 'Resolved config');
+  logger.trace({ config: inputConfig }, 'Input config');
+  logger.trace({ config }, 'Resolved config');
   return config;
 }
 
diff --git a/lib/logger/config-serializer.js b/lib/logger/config-serializer.js
index 136b13b077..fff9cee8f2 100644
--- a/lib/logger/config-serializer.js
+++ b/lib/logger/config-serializer.js
@@ -3,7 +3,13 @@ const traverse = require('traverse');
 module.exports = configSerializer;
 
 function configSerializer(config) {
-  const redactedFields = ['token', 'githubAppKey'];
+  const redactedFields = [
+    'token',
+    'githubAppKey',
+    'npmToken',
+    'npmrc',
+    'yarnrc',
+  ];
   const functionFields = ['api', 'logger'];
   const templateFields = ['commitMessage', 'prTitle', 'prBody'];
   // eslint-disable-next-line array-callback-return
diff --git a/lib/workers/branch/lock-files.js b/lib/workers/branch/lock-files.js
index bef878c5f2..b8a703a3e7 100644
--- a/lib/workers/branch/lock-files.js
+++ b/lib/workers/branch/lock-files.js
@@ -77,6 +77,17 @@ function determineLockFileDirs(config) {
 
 async function writeExistingFiles(config) {
   const { logger } = config;
+  if (config.npmrc) {
+    logger.debug('Writing repo .npmrc');
+    await fs.outputFile(path.join(config.tmpDir.name, '.npmrc'), config.npmrc);
+  }
+  if (config.yarnrc) {
+    logger.debug('Writing repo .yarnrc');
+    await fs.outputFile(
+      path.join(config.tmpDir.name, '.yarnrc'),
+      config.yarnrc
+    );
+  }
   if (!config.packageFiles) {
     return;
   }
diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js
index b570401bdb..2eeecaaf13 100644
--- a/lib/workers/package-file/index.js
+++ b/lib/workers/package-file/index.js
@@ -39,7 +39,7 @@ async function renovatePackageFile(packageFileConfig) {
       packageFile: depTypeConfig.packageFile,
       depType: depTypeConfig.depType,
     });
-    logger.debug({ config: depTypeConfig }, 'depTypeConfig');
+    logger.trace({ config: depTypeConfig }, 'depTypeConfig');
     return configParser.filterConfig(depTypeConfig, 'depType');
   });
   logger.trace({ config: depTypeConfigs }, `depTypeConfigs`);
@@ -59,7 +59,7 @@ async function renovatePackageFile(packageFileConfig) {
       config.lockFileMaintenance
     );
     lockFileMaintenanceConf.type = 'lockFileMaintenance';
-    logger.debug(
+    logger.trace(
       { config: lockFileMaintenanceConf },
       `lockFileMaintenanceConf`
     );
diff --git a/lib/workers/package/index.js b/lib/workers/package/index.js
index a3101ba19f..5938f7804b 100644
--- a/lib/workers/package/index.js
+++ b/lib/workers/package/index.js
@@ -48,11 +48,7 @@ async function renovatePackage(config) {
         '. This will block *all* dependencies from being updated due to presence of lock file.';
     }
     results = [result];
-    if (config.depName[0] === '@') {
-      logger.info(result.message);
-    } else {
-      logger.warn(result.message);
-    }
+    logger.warn(result.message);
   }
   logger.debug({ results }, `${config.depName} lookup results`);
   // Flatten the result on top of config, add repositoryUrl
diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js
index 55bfb09168..69ad08c397 100644
--- a/lib/workers/repository/apis.js
+++ b/lib/workers/repository/apis.js
@@ -9,11 +9,10 @@ const presets = require('../../config/presets');
 // API
 const githubApi = require('../../api/github');
 const gitlabApi = require('../../api/gitlab');
-const npmApi = require('../../api/npm');
 
 module.exports = {
   detectSemanticCommits,
-  setNpmrc,
+  getNpmrc,
   initApis,
   mergeRenovateJson,
   checkForLerna,
@@ -37,16 +36,17 @@ async function detectSemanticCommits(config) {
 }
 
 // Check for .npmrc in repository and pass it to npm api if found
-async function setNpmrc(config) {
+async function getNpmrc(config) {
+  let npmrc;
   try {
-    const npmrcContent = await config.api.getFileContent('.npmrc');
-    if (npmrcContent) {
+    npmrc = await config.api.getFileContent('.npmrc');
+    if (npmrc) {
       config.logger.debug('Found .npmrc file in repository');
-      npmApi.setNpmrc(npmrcContent);
     }
   } catch (err) {
     config.logger.error('Failed to set .npmrc');
   }
+  return { ...config, npmrc };
 }
 
 async function checkForLerna(config) {
@@ -95,8 +95,7 @@ async function initApis(inputConfig, token) {
   if (config.semanticCommits === null) {
     config.semanticCommits = await module.exports.detectSemanticCommits(config);
   }
-  await module.exports.setNpmrc(config);
-  return config;
+  return module.exports.getNpmrc(config);
 }
 
 function migrateAndValidate(config, input) {
@@ -271,10 +270,16 @@ async function resolvePackageFiles(inputConfig) {
       path.join(path.dirname(packageFile.packageFile), '.npmrc'),
       config.baseBranch
     );
+    if (!packageFile.npmrc) {
+      delete packageFile.npmrc;
+    }
     packageFile.yarnrc = await config.api.getFileContent(
       path.join(path.dirname(packageFile.packageFile), '.yarnrc'),
       config.baseBranch
     );
+    if (!packageFile.yarnrc) {
+      delete packageFile.yarnrc;
+    }
     if (packageFile.content) {
       // check for workspaces
       if (
diff --git a/lib/workers/repository/upgrades.js b/lib/workers/repository/upgrades.js
index 07ff5ffaed..64d6a0a7be 100644
--- a/lib/workers/repository/upgrades.js
+++ b/lib/workers/repository/upgrades.js
@@ -146,7 +146,6 @@ function getPackageFileConfig(repoConfig, index) {
     repoConfig,
     packageFile
   );
-  repoConfig.logger.trace({ config: repoConfig }, 'repoConfig');
   packageFileConfig.logger = packageFileConfig.logger.child({
     repository: packageFileConfig.repository,
     packageFile: packageFileConfig.packageFile,
diff --git a/test/config/__snapshots__/massage.spec.js.snap b/test/config/__snapshots__/massage.spec.js.snap
index 1d7df63798..5b3b9202f9 100644
--- a/test/config/__snapshots__/massage.spec.js.snap
+++ b/test/config/__snapshots__/massage.spec.js.snap
@@ -1,3 +1,10 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`config/massage massageConfig massages npmToken 1`] = `
+Object {
+  "npmrc": "//registry.npmjs.org/:_authToken=some-token
+",
+}
+`;
+
 exports[`config/massage massageConfig returns empty 1`] = `Object {}`;
diff --git a/test/config/massage.spec.js b/test/config/massage.spec.js
index 8bcf47466d..fa8bb4ec5a 100644
--- a/test/config/massage.spec.js
+++ b/test/config/massage.spec.js
@@ -14,5 +14,11 @@ describe('config/massage', () => {
       const res = massage.massageConfig(config);
       expect(Array.isArray(res.schedule)).toBe(true);
     });
+    it('massages npmToken', () => {
+      const config = {
+        npmToken: 'some-token',
+      };
+      expect(massage.massageConfig(config)).toMatchSnapshot();
+    });
   });
 });
diff --git a/test/workers/branch/lock-files.spec.js b/test/workers/branch/lock-files.spec.js
index 05cdbd3bf5..16ba4ff814 100644
--- a/test/workers/branch/lock-files.spec.js
+++ b/test/workers/branch/lock-files.spec.js
@@ -186,9 +186,11 @@ describe('workers/branch/lock-files', () => {
       fs.remove = jest.fn();
     });
     it('returns if no packageFiles', async () => {
+      config.npmrc = 'some-npmrc';
+      config.yarnrc = 'some-yarnrc';
       delete config.packageFiles;
       await writeExistingFiles(config);
-      expect(fs.outputFile.mock.calls).toHaveLength(0);
+      expect(fs.outputFile.mock.calls).toHaveLength(2);
     });
     it('writes files and removes files', async () => {
       config.packageFiles = [
diff --git a/test/workers/package/__snapshots__/index.spec.js.snap b/test/workers/package/__snapshots__/index.spec.js.snap
index ec40674aae..3cf042d22d 100644
--- a/test/workers/package/__snapshots__/index.spec.js.snap
+++ b/test/workers/package/__snapshots__/index.spec.js.snap
@@ -5,6 +5,8 @@ Array [
   "description",
   "timezone",
   "schedule",
+  "npmrc",
+  "yarnrc",
   "packageFiles",
   "branchPrefix",
   "semanticCommits",
@@ -40,6 +42,8 @@ Array [
   "description",
   "timezone",
   "schedule",
+  "npmrc",
+  "yarnrc",
   "packageFiles",
   "branchPrefix",
   "semanticCommits",
@@ -153,6 +157,7 @@ This {{#if isGitHub}}PR{{else}}MR{{/if}} has been generated by [Renovate Bot](ht
     "labels": Array [],
     "lazyGrouping": true,
     "message": "Failed to look up dependency. This will block *all* dependencies from being updated due to presence of lock file.",
+    "npmrc": null,
     "packageFiles": Array [],
     "prBody": "This {{#if isGitHub}}Pull{{else}}Merge{{/if}} Request {{#if isRollback}}rolls back{{else}}updates{{/if}} dependency {{#if repositoryUrl}}[{{depName}}]({{repositoryUrl}}){{else}}\`{{depName}}\`{{/if}} from \`v{{currentVersion}}\` to \`v{{newVersion}}\`{{#if isRollback}}. This is necessary and important because \`v{{currentVersion}}\` cannot be found in the npm registry - probably because of it being unpublished.{{/if}}
 {{#if releases.length}}
@@ -220,6 +225,7 @@ This {{#if isGitHub}}PR{{else}}MR{{/if}} has been generated by [Renovate Bot](ht
     "timezone": null,
     "type": "error",
     "unpublishSafe": false,
+    "yarnrc": null,
   },
 ]
 `;
@@ -307,6 +313,7 @@ This {{#if isGitHub}}PR{{else}}MR{{/if}} has been generated by [Renovate Bot](ht
     "labels": Array [],
     "lazyGrouping": true,
     "message": "Failed to look up dependency",
+    "npmrc": null,
     "packageFiles": Array [],
     "prBody": "This {{#if isGitHub}}Pull{{else}}Merge{{/if}} Request {{#if isRollback}}rolls back{{else}}updates{{/if}} dependency {{#if repositoryUrl}}[{{depName}}]({{repositoryUrl}}){{else}}\`{{depName}}\`{{/if}} from \`v{{currentVersion}}\` to \`v{{newVersion}}\`{{#if isRollback}}. This is necessary and important because \`v{{currentVersion}}\` cannot be found in the npm registry - probably because of it being unpublished.{{/if}}
 {{#if releases.length}}
@@ -374,6 +381,7 @@ This {{#if isGitHub}}PR{{else}}MR{{/if}} has been generated by [Renovate Bot](ht
     "timezone": null,
     "type": "error",
     "unpublishSafe": false,
+    "yarnrc": null,
   },
 ]
 `;
diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap
index f3a1a1a96d..7af498776c 100644
--- a/test/workers/repository/__snapshots__/apis.spec.js.snap
+++ b/test/workers/repository/__snapshots__/apis.spec.js.snap
@@ -99,18 +99,14 @@ Array [
     "errors": Array [],
     "hasPackageLock": true,
     "hasYarnLock": true,
-    "npmrc": null,
     "packageFile": "package.json",
     "warnings": Array [],
-    "yarnrc": null,
   },
   Object {
     "content": Object {},
     "hasPackageLock": false,
     "hasYarnLock": false,
-    "npmrc": null,
     "packageFile": "a/package.json",
-    "yarnrc": null,
   },
 ]
 `;
diff --git a/test/workers/repository/apis.spec.js b/test/workers/repository/apis.spec.js
index 346eee59e4..24da3950d6 100644
--- a/test/workers/repository/apis.spec.js
+++ b/test/workers/repository/apis.spec.js
@@ -14,14 +14,14 @@ jest.mock('../../../lib/api/gitlab');
 jest.mock('../../../lib/api/npm');
 
 describe('workers/repository/apis', () => {
-  describe('setNpmrc(config)', () => {
+  describe('getNpmrc', () => {
     it('Skips if npmrc not found', async () => {
       const config = {
         api: {
           getFileContent: jest.fn(),
         },
       };
-      await apis.setNpmrc(config);
+      expect(await apis.getNpmrc(config)).toMatchObject(config);
     });
     it('Parses if npmrc found', async () => {
       const config = {
@@ -30,7 +30,8 @@ describe('workers/repository/apis', () => {
         },
         logger,
       };
-      await apis.setNpmrc(config);
+      const res = await apis.getNpmrc(config);
+      expect(res.npmrc).toEqual('a = b');
     });
     it('Catches errors', async () => {
       const config = {
@@ -41,7 +42,7 @@ describe('workers/repository/apis', () => {
         },
         logger,
       };
-      await apis.setNpmrc(config);
+      expect(await apis.getNpmrc(config)).toMatchObject(config);
     });
   });
   describe('detectSemanticCommits', () => {
-- 
GitLab