From 701aaef972dad46121e1164ebd3e46433d7b54b9 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@keylocation.sg>
Date: Thu, 13 Apr 2017 22:39:46 +0200
Subject: [PATCH] Support yarn.lock maintenance (#105)

Closes #96

* Improve yarn logs

* Add processYarnLock

* Update worker.js

* Update worker.js

* Fix and update docs

* Make Yarn Maintenance strings into templates
---
 docs/configuration.md     | 44 ++++++++++++++++++++++-----------------
 lib/config/definitions.js | 39 ++++++++++++++++++++++++++++++++++
 lib/helpers/yarn.js       |  5 ++++-
 lib/worker.js             | 29 ++++++++++++++++++++++++++
 readme.md                 | 39 +++++++++++++++++-----------------
 5 files changed, 117 insertions(+), 39 deletions(-)

diff --git a/docs/configuration.md b/docs/configuration.md
index 345ca761d4..f0c978a5e3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -56,25 +56,26 @@ $ node renovate --help
 
   Options:
 
-    -h, --help                    output usage information
-    --enabled [boolean]           Enable or disable renovate
-    --onboarding [boolean]        Require a Configuration PR first
-    --platform <string>           Platform type of repository
-    --endpoint <string>           Custom endpoint to use
-    --token <string>              Repository Auth Token
-    --package-files <list>        Package file paths
-    --dep-types <list>            Dependency types
-    --ignore-deps <list>          Dependencies to ignore
-    --ignore-future [boolean]     Ignore versions tagged as "future"
-    --ignore-unstable [boolean]   Ignore versions with unstable semver
-    --respect-latest [boolean]    Ignore versions newer than npm "latest" version
-    --recreate-closed [boolean]   Recreate PRs even if same ones were closed previously
-    --rebase-stale-prs [boolean]  Rebase stale PRs (GitHub only)
-    --labels <list>               Labels to add to Pull Request
-    --assignees <list>            Assignees for Pull Request
-    --reviewers <list>            Requested reviewers for Pull Requests (GitHub only)
-    --pin-versions [boolean]      Convert ranged versions in package.json to pinned versions
-    --log-level <string>          Logging level
+    -h, --help                      output usage information
+    --enabled [boolean]             Enable or disable renovate
+    --onboarding [boolean]          Require a Configuration PR first
+    --platform <string>             Platform type of repository
+    --endpoint <string>             Custom endpoint to use
+    --token <string>                Repository Auth Token
+    --package-files <list>          Package file paths
+    --dep-types <list>              Dependency types
+    --ignore-deps <list>            Dependencies to ignore
+    --ignore-future [boolean]       Ignore versions tagged as "future"
+    --ignore-unstable [boolean]     Ignore versions with unstable semver
+    --respect-latest [boolean]      Ignore versions newer than npm "latest" version
+    --recreate-closed [boolean]     Recreate PRs even if same ones were closed previously
+    --rebase-stale-prs [boolean]    Rebase stale PRs (GitHub only)
+    --maintain-yarn-lock [boolean]  Keep yarn.lock updated in base branch (no monorepo support)
+    --labels <list>                 Labels to add to Pull Request
+    --assignees <list>              Assignees for Pull Request
+    --reviewers <list>              Requested reviewers for Pull Requests (GitHub only)
+    --pin-versions [boolean]        Convert ranged versions in package.json to pinned versions
+    --log-level <string>            Logging level
 
   Examples:
 
@@ -124,6 +125,11 @@ Obviously, you can't set repository or package file location with this method.
 | `commitMessage` | Commit message template | string | `"Update dependency {{depName}} to version {{newVersion}}"` |  |  |
 | `prTitle` | Pull Request title template | string | `"{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}}"` |  |  |
 | `prBody` | Pull Request body template | string | `"This Pull Request updates dependency {{depName}} from version `{{currentVersion}}` to `{{newVersion}}`\n\n{{changelog}}"` |  |  |
+| `maintainYarnLock` | Keep yarn.lock updated in base branch (no monorepo support) | boolean | `false` | `RENOVATE_MAINTAIN_YARN_LOCK` | `--maintain-yarn-lock` |
+| `yarnMaintenanceBranchName` | Branch name template when maintaining yarn.lock | string | `"renovate/yarn-lock"` |  |  |
+| `yarnMaintenanceCommitMessage` | Commit message template when maintaining yarn.lock | string | `"Renovate yarn.lock file"` |  |  |
+| `yarnMaintenancePrTitle` | Pull Request title template when maintaining yarn.lock | string | `"Renovate yarn.lock file"` |  |  |
+| `yarnMaintenancePrBody` | Pull Request body template when maintaining yarn.lock | string | `"This PR regenerates yarn.lock files based on the existing `package.json` files."` |  |  |
 | `labels` | Labels to add to Pull Request | list | `[]` | `RENOVATE_LABELS` | `--labels` |
 | `assignees` | Assignees for Pull Request | list | `[]` | `RENOVATE_ASSIGNEES` | `--assignees` |
 | `reviewers` | Requested reviewers for Pull Requests (GitHub only) | list | `[]` | `RENOVATE_REVIEWERS` | `--reviewers` |
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 331e946b1e..9a18d5b2e2 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -115,6 +115,45 @@ const options = [
     cli: false,
     env: false,
   },
+  // Yarn Lock Maintenance
+  {
+    name: 'maintainYarnLock',
+    description: 'Keep yarn.lock updated in base branch (no monorepo support)',
+    type: 'boolean',
+    default: false,
+  },
+  {
+    name: 'yarnMaintenanceBranchName',
+    description: 'Branch name template when maintaining yarn.lock',
+    type: 'string',
+    default: 'renovate/yarn-lock',
+    cli: false,
+    env: false,
+  },
+  {
+    name: 'yarnMaintenanceCommitMessage',
+    description: 'Commit message template when maintaining yarn.lock',
+    type: 'string',
+    default: 'Renovate yarn.lock file',
+    cli: false,
+    env: false,
+  },
+  {
+    name: 'yarnMaintenancePrTitle',
+    description: 'Pull Request title template when maintaining yarn.lock',
+    type: 'string',
+    default: 'Renovate yarn.lock file',
+    cli: false,
+    env: false,
+  },
+  {
+    name: 'yarnMaintenancePrBody',
+    description: 'Pull Request body template when maintaining yarn.lock',
+    type: 'string',
+    default: 'This PR regenerates yarn.lock files based on the existing `package.json` files.',
+    cli: false,
+    env: false,
+  },
   // Pull Request options
   {
     name: 'labels',
diff --git a/lib/helpers/yarn.js b/lib/helpers/yarn.js
index bab3841369..2163a50ee0 100644
--- a/lib/helpers/yarn.js
+++ b/lib/helpers/yarn.js
@@ -20,7 +20,10 @@ async function generateLockFile(newPackageJson, npmrcContent, yarnrcContent) {
     if (yarnrcContent) {
       fs.writeFileSync(path.join(tmpDir.name, '.yarnrc'), yarnrcContent);
     }
-    cp.spawnSync('yarn', ['install'], { cwd: tmpDir.name, shell: true });
+    logger.debug('Spawning yarn install');
+    const result = cp.spawnSync('yarn', ['install'], { cwd: tmpDir.name, shell: true });
+    logger.debug(String(result.stdout));
+    logger.debug(String(result.stderr));
     yarnLock = fs.readFileSync(path.join(tmpDir.name, 'yarn.lock'));
   } catch (error) {
     throw error;
diff --git a/lib/worker.js b/lib/worker.js
index e85a361d31..a68720fac2 100644
--- a/lib/worker.js
+++ b/lib/worker.js
@@ -1,4 +1,5 @@
 const logger = require('winston');
+const path = require('path');
 const stringify = require('json-stringify-pretty-compact');
 const githubApi = require('./api/github');
 const gitlabApi = require('./api/gitlab');
@@ -54,10 +55,38 @@ async function processPackageFile(repoName, packageFile, packageConfig) {
   // Find all upgrades for remaining dependencies
   const upgrades = await findUpgrades(dependencies, config);
   // Process all upgrades sequentially
+  if (config.maintainYarnLock) {
+    await maintainYarnLock(config);
+  }
   await processUpgradesSequentially(config, upgrades);
   logger.info(`${repoName} ${packageFile} done`);
 }
 
+async function maintainYarnLock(inputConfig) {
+  const packageContent = await inputConfig.api.getFileContent(inputConfig.packageFile);
+  const yarnLockFileName = path.join(path.dirname(inputConfig.packageFile), 'yarn.lock');
+  logger.debug(`Checking for ${yarnLockFileName}`);
+  const existingYarnLock = await inputConfig.api.getFileContent(yarnLockFileName);
+  if (!existingYarnLock) {
+    return;
+  }
+  logger.debug('Found existing yarn.lock file');
+  const newYarnLock = await branchWorker.getYarnLockFile(packageContent, inputConfig);
+  if (existingYarnLock === newYarnLock.contents) {
+    logger.debug('Yarn lock file does not need updating');
+    return;
+  }
+  logger.debug('Yarn lock needs updating');
+  // API will know whether to create new branch or not
+  const params = Object.assign({}, inputConfig);
+  const commitMessage = handlebars.compile(params.yarnMaintenanceCommitMessage)(params);
+  params.branchName = params.yarnMaintenanceBranchName;
+  params.prTitle = params.yarnMaintenancePrTitle;
+  params.prBody = params.yarnMaintenancePrBody;
+  await inputConfig.api.commitFilesToBranch(params.branchName, [newYarnLock], commitMessage);
+  prWorker.ensurePr(params);
+}
+
 async function findUpgrades(dependencies, inputConfig) {
   const allUpgrades = [];
   // findDepUpgrades can add more than one upgrade to allUpgrades
diff --git a/readme.md b/readme.md
index 0bef48a9c7..0e9f9568d9 100644
--- a/readme.md
+++ b/readme.md
@@ -38,25 +38,26 @@ $ node renovate --help
 
   Options:
 
-    -h, --help                    output usage information
-    --enabled [boolean]           Enable or disable renovate
-    --onboarding [boolean]        Require a Configuration PR first
-    --platform <string>           Platform type of repository
-    --endpoint <string>           Custom endpoint to use
-    --token <string>              Repository Auth Token
-    --package-files <list>        Package file paths
-    --dep-types <list>            Dependency types
-    --ignore-deps <list>          Dependencies to ignore
-    --ignore-future [boolean]     Ignore versions tagged as "future"
-    --ignore-unstable [boolean]   Ignore versions with unstable semver
-    --respect-latest [boolean]    Ignore versions newer than npm "latest" version
-    --recreate-closed [boolean]   Recreate PRs even if same ones were closed previously
-    --rebase-stale-prs [boolean]  Rebase stale PRs (GitHub only)
-    --labels <list>               Labels to add to Pull Request
-    --assignees <list>            Assignees for Pull Request
-    --reviewers <list>            Requested reviewers for Pull Requests (GitHub only)
-    --pin-versions [boolean]      Convert ranged versions in package.json to pinned versions
-    --log-level <string>          Logging level
+    -h, --help                      output usage information
+    --enabled [boolean]             Enable or disable renovate
+    --onboarding [boolean]          Require a Configuration PR first
+    --platform <string>             Platform type of repository
+    --endpoint <string>             Custom endpoint to use
+    --token <string>                Repository Auth Token
+    --package-files <list>          Package file paths
+    --dep-types <list>              Dependency types
+    --ignore-deps <list>            Dependencies to ignore
+    --ignore-future [boolean]       Ignore versions tagged as "future"
+    --ignore-unstable [boolean]     Ignore versions with unstable semver
+    --respect-latest [boolean]      Ignore versions newer than npm "latest" version
+    --recreate-closed [boolean]     Recreate PRs even if same ones were closed previously
+    --rebase-stale-prs [boolean]    Rebase stale PRs (GitHub only)
+    --maintain-yarn-lock [boolean]  Keep yarn.lock updated in base branch (no monorepo support)
+    --labels <list>                 Labels to add to Pull Request
+    --assignees <list>              Assignees for Pull Request
+    --reviewers <list>              Requested reviewers for Pull Requests (GitHub only)
+    --pin-versions [boolean]        Convert ranged versions in package.json to pinned versions
+    --log-level <string>            Logging level
 
   Examples:
 
-- 
GitLab