From f8a10a9f4c4bf3ce62df2c14857066ca5fe742a5 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Fri, 29 Dec 2017 15:06:28 +0100
Subject: [PATCH] feat: pathRules
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds new configuration option “pathRules”. pathRules is an array of path rules. Each rule should contain a “paths” array which uses string or glob matching like the existing ignorePaths. If any of the paths in a rule matches a packageFile then the remaining configuration from the rule is applied to the packageFile.
---
 lib/config/definitions.js                     | 18 ++++++++++++
 lib/manager/resolve.js                        | 25 +++++++++++++++-
 test/manager/resolve.spec.js                  | 18 ++++++++++++
 .../2017-10-05-configuration-options.md       | 29 +++++++++++++++++++
 4 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 44be7fcbf6..ac701c84de 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -231,6 +231,24 @@ const options = [
     stage: 'repository',
     default: ['**/node_modules/**', '**/bower_components/**'],
   },
+  {
+    name: 'pathRules',
+    description:
+      'Apply config on a path-based basis. Consists of a paths array plus whatever other configuration objects to apply',
+    type: 'list',
+    stage: 'repository',
+    cli: false,
+    env: false,
+  },
+  {
+    name: 'paths',
+    description:
+      'List of strings or glob patterns to match against package files. Applicable inside pathRules only',
+    type: 'list',
+    stage: 'repository',
+    cli: false,
+    env: false,
+  },
   {
     name: 'dependencies',
     description: 'Configuration specifically for `package.json`>`dependencies`',
diff --git a/lib/manager/resolve.js b/lib/manager/resolve.js
index 12e467ac5f..9c94b56ba3 100644
--- a/lib/manager/resolve.js
+++ b/lib/manager/resolve.js
@@ -1,5 +1,6 @@
 const path = require('path');
 const upath = require('upath');
+const minimatch = require('minimatch');
 
 const { migrateAndValidate } = require('../config/migrate-validate');
 const presets = require('../config/presets');
@@ -163,7 +164,29 @@ async function resolvePackageFiles(config) {
   }
   logger.debug('queue');
   const queue = allPackageFiles.map(p => resolvePackageFile(p));
-  const packageFiles = (await Promise.all(queue)).filter(p => p !== null);
+  let packageFiles = (await Promise.all(queue)).filter(p => p !== null);
+
+  logger.debug('Checking against path rules');
+  packageFiles = packageFiles.map(pf => {
+    let packageFile = { ...pf };
+    for (const pathRule of config.pathRules) {
+      /* eslint-disable no-loop-func */
+      if (
+        pathRule.paths.some(
+          rulePath =>
+            packageFile.packageFile.includes(rulePath) ||
+            minimatch(packageFile.packageFile, rulePath)
+        )
+      ) {
+        logger.debug({ pathRule, packageFile }, 'Matched pathRule');
+        packageFile = mergeChildConfig(packageFile, pathRule);
+        delete packageFile.paths;
+      }
+      /* eslint-enable */
+    }
+    return packageFile;
+  });
+
   platform.ensureIssueClosing('Action Required: Fix Renovate Configuration');
   return checkMonorepos({ ...config, packageFiles });
 }
diff --git a/test/manager/resolve.spec.js b/test/manager/resolve.spec.js
index d6554aee48..7a640caef6 100644
--- a/test/manager/resolve.spec.js
+++ b/test/manager/resolve.spec.js
@@ -104,5 +104,23 @@ describe('manager/resolve', () => {
       const res = await resolvePackageFiles(config);
       expect(res.packageFiles).toMatchSnapshot();
     });
+    it('applies package rules', async () => {
+      config.pathRules = [
+        {
+          paths: ['examples/**'],
+          prTitle: 'abcdefg',
+        },
+      ];
+      config.packageFiles = ['package.json', 'examples/a/package.json'];
+      platform.getFileList.mockReturnValue([
+        'package.json',
+        'examples/a/package.json',
+      ]);
+      platform.getFile.mockReturnValue('{}');
+      const res = await resolvePackageFiles(config);
+      expect(res.packageFiles).toHaveLength(2);
+      expect(res.packageFiles[0].prTitle).not.toEqual('abcdefg');
+      expect(res.packageFiles[1].prTitle).toEqual('abcdefg');
+    });
   });
 });
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index a47a760b02..b826796848 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -617,6 +617,35 @@ Configuration specific for patch dependency updates.
 
 Add to this object if you wish to define rules that apply only to patch updates. See also `major` and `minor` configuration options.
 
+## pathRules
+
+Apply config on a path-based basis. Consists of a `paths` array plus whatever other configuration objects to apply.
+
+| name    | value |
+| ------- | ----- |
+| type    | list  |
+| default | []    |
+
+Path rules are convenient to use if you wish to apply configuration rules to certain package files without needing to configure them all in the `packageFiles` array. For example, if you have an `examples` directory and you want all updates to those examples to use the `chore` prefix instead of `fix`, then you could add this configuration:
+
+```json
+  "pathRules": [
+    {
+      "paths": ["examples/**"],
+      "extends": [":semanticCommitTypeAll(chore)"]
+    }
+  ]
+```
+
+## paths
+
+List of strings or glob patterns to match against package files. Applicable inside pathRules only.
+
+| name    | value |
+| ------- | ----- |
+| type    | list  |
+| default | []    |
+
 ## peerDependencies
 
 Configuration specific for `package.json > peerDependencies`.
-- 
GitLab