From c356bb0349096e1efd772e23969e513c539a8b7b Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 30 Apr 2018 13:18:51 +0200
Subject: [PATCH] feat: custom filenames for package files (#1894)

Renovate now comes with a variety of package managers supported, each with their own filename pattern(s). These patterns are now exposed for user configuration through the new `fileMatch` list/array configuration option, which has been added to each manager (npm, bazel, docker-compose, etc). `fileMatch` is defined as a mergeable list, meaning that users can add to the default pattern to extend the files being detected.

Closes #799
---
 docs/adding-a-package-manager.md              | 16 +++---
 lib/config/definitions.js                     | 41 +++++++++++++---
 lib/config/validation.js                      | 18 +++++++
 lib/manager/bazel/index.js                    |  2 -
 lib/manager/buildkite/index.js                |  3 --
 lib/manager/circleci/index.js                 |  2 -
 lib/manager/docker-compose/index.js           |  2 -
 lib/manager/docker/index.js                   |  2 -
 lib/manager/index.js                          | 20 +++++---
 lib/manager/meteor/index.js                   |  2 -
 lib/manager/npm/index.js                      |  3 --
 lib/manager/nvm/index.js                      |  2 -
 lib/manager/pip_requirements/index.js         |  2 -
 lib/manager/travis/index.js                   |  2 -
 test/config/__snapshots__/index.spec.js.snap  | 49 ++++++++++++++++---
 .../__snapshots__/validation.spec.js.snap     | 13 +++++
 test/config/validation.spec.js                | 16 ++++++
 .../__snapshots__/resolve.spec.js.snap        |  9 ++++
 test/manager/index.spec.js                    | 38 +++++++++-----
 test/manager/resolve.spec.js                  |  2 +-
 .../2017-10-05-configuration-options.md       |  9 ++++
 21 files changed, 189 insertions(+), 64 deletions(-)

diff --git a/docs/adding-a-package-manager.md b/docs/adding-a-package-manager.md
index 41048820af..727b8b5ac8 100644
--- a/docs/adding-a-package-manager.md
+++ b/docs/adding-a-package-manager.md
@@ -22,7 +22,6 @@ The manager's `index.js` file then needs to export up to 7 functions or values:
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   resolvePackageFile,
@@ -34,15 +33,9 @@ module.exports = {
 
 This is used when more than one package manager share settings from a common language. e.g. docker-compose, circleci and gitlabci all specify "docker" as their language and inherit all config settings from there.
 
-##### filePattern
-
-`filePattern` is a javascript `RegExp` used to detect the manager's files within the repository.
-
-An example `filePattern` from Docker Compose is `(^|/)docker-compose[^/]*\\.ya?ml$`. You can see that it's designed to match files both in the root as well as in subdirectories, and to be flexible with matching yaml files that start with `docker-compose` but may have additional characters in the filename.
-
 ##### contentPattern (optional)
 
-`contentPattern` is only necessary if there's the possibility that some of the files matched by `filePattern` may not belong to that package manager, or maybe don't have any dependencies.
+`contentPattern` is only necessary if there's the possibility that some of the files matched by `fileMatch` may not belong to that package manager, or maybe don't have any dependencies.
 
 An example `contentPattern` is from Meteor.js: `(^|\\n)\\s*Npm.depends\\(\\s*{`. Because Meteor's `package.js` is not particularly "unique", it's quite possible that repositories will have one or more `package.js` files that have nothing to do with Meteor.js, so we filter out only the ones that include `Npm.depends` in it.
 
@@ -74,3 +67,10 @@ Therefore, there is the possibility that for some future package managers we may
 ##### updateDependency
 
 This function is the final one called for a manager. It's purpose is to patch the package file with the new version and return an updated file.
+
+##### fileMatch
+
+`fileMatch` is a javascript `RegExp` string or an exact filename string used to detect the manager's files within the repository.
+It is located within `lib/config/definitions.js` so that it can be configured by the user.
+
+An example `fileMatch` from Docker Compose is `(^|/)docker-compose[^/]*\\.ya?ml$`. You can see that it's designed to match files both in the root as well as in subdirectories, and to be flexible with matching yaml files that start with `docker-compose` but may have additional characters in the filename.
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index bcb6bf3100..c557a25735 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -793,12 +793,23 @@ const options = [
       'Requested reviewers for Pull Requests (username in GitHub/GitLab, email or username in VSTS)',
     type: 'list',
   },
+  {
+    name: 'fileMatch',
+    description: 'JS RegExp pattern for matching manager files',
+    type: 'list',
+    allowString: true,
+    mergeable: true,
+    cli: false,
+    env: false,
+  },
   {
     name: 'npm',
     description: 'Configuration object for npm package.json renovation',
     stage: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['(^|/)package.json$'],
+    },
     mergeable: true,
   },
   {
@@ -806,7 +817,9 @@ const options = [
     description: 'Configuration object for meteor package.js renovation',
     stage: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['(^|/)package.js$'],
+    },
     mergeable: true,
   },
   {
@@ -814,7 +827,9 @@ const options = [
     description: 'Configuration object for bazel WORKSPACE renovation',
     stage: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['(^|/)WORKSPACE$'],
+    },
     mergeable: true,
   },
   {
@@ -824,6 +839,7 @@ const options = [
     type: 'json',
     default: {
       enabled: false,
+      fileMatch: ['\\.buildkite/.+\\.yml$'],
       commitMessageTopic: 'buildkite plugin {{depName}}',
       commitMessageExtra: 'to {{newVersion}}',
       managerBranchPrefix: 'buildkite-',
@@ -858,7 +874,10 @@ const options = [
     description: 'Configuration object for .travis.yml node version renovation',
     stage: 'repository',
     type: 'json',
-    default: { enabled: false },
+    default: {
+      enabled: false,
+      fileMatch: ['^.travis.yml$'],
+    },
     mergeable: true,
     cli: false,
   },
@@ -867,7 +886,9 @@ const options = [
     description: 'Configuration object for .nvmrc files',
     state: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['^.nvmrc$'],
+    },
     mergeable: true,
     cli: false,
   },
@@ -877,6 +898,7 @@ const options = [
     stage: 'repository',
     type: 'json',
     default: {
+      fileMatch: ['(^|/)Dockerfile$'],
       managerBranchPrefix: 'docker-',
       commitMessageTopic: '{{{depName}}} Docker tag',
       prBody: template('prBody', 'docker'),
@@ -915,7 +937,9 @@ const options = [
       'Configuration object for Docker Compose renovation. Also inherits settings from `docker` object.',
     stage: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['(^|/)docker-compose[^/]*\\.ya?ml$'],
+    },
     mergeable: true,
     cli: false,
   },
@@ -925,7 +949,9 @@ const options = [
       'Configuration object for CircleCI yml renovation. Also inherits settings from `docker` object.',
     stage: 'repository',
     type: 'json',
-    default: {},
+    default: {
+      fileMatch: ['^.circleci/config.yml$'],
+    },
     mergeable: true,
     cli: false,
   },
@@ -936,6 +962,7 @@ const options = [
     type: 'json',
     default: {
       enabled: false,
+      fileMatch: ['(^|\\/)([\\w-]*)requirements.(txt|pip)$'],
     },
     mergeable: true,
     cli: false,
diff --git a/lib/config/validation.js b/lib/config/validation.js
index 3e71df5ae7..3eb263ad1c 100644
--- a/lib/config/validation.js
+++ b/lib/config/validation.js
@@ -208,6 +208,24 @@ async function validateConfig(config, isPreset, parentPath) {
                 });
               }
             }
+            if (key === 'fileMatch') {
+              try {
+                for (const fileMatch of val) {
+                  RegExp(fileMatch);
+                  if (!safe(fileMatch)) {
+                    errors.push({
+                      depName: 'Configuration Error',
+                      message: `Unsafe regExp for ${currentPath}: \`${fileMatch}\``,
+                    });
+                  }
+                }
+              } catch (e) {
+                errors.push({
+                  depName: 'Configuration Error',
+                  message: `Invalid regExp for ${currentPath}: \`${val}\``,
+                });
+              }
+            }
             if (
               (selectors.includes(key) || key === 'matchCurrentVersion') &&
               !(parentPath && parentPath.match(/p.*Rules\[\d+\]$/)) && // Inside a packageRule
diff --git a/lib/manager/bazel/index.js b/lib/manager/bazel/index.js
index 9dff093317..cf2a5e0eed 100644
--- a/lib/manager/bazel/index.js
+++ b/lib/manager/bazel/index.js
@@ -2,13 +2,11 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('(^|/)WORKSPACE$');
 const contentPattern = new RegExp('(^|\\n)git_repository\\(');
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   updateDependency,
 };
diff --git a/lib/manager/buildkite/index.js b/lib/manager/buildkite/index.js
index f5a02ded7a..0e96876493 100644
--- a/lib/manager/buildkite/index.js
+++ b/lib/manager/buildkite/index.js
@@ -2,11 +2,8 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('\\.buildkite/.+\\.yml$');
-
 module.exports = {
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   updateDependency,
 };
diff --git a/lib/manager/circleci/index.js b/lib/manager/circleci/index.js
index 2b674babc7..c70b4a9305 100644
--- a/lib/manager/circleci/index.js
+++ b/lib/manager/circleci/index.js
@@ -2,14 +2,12 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('../docker/package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('^.circleci/config.yml$');
 const contentPattern = new RegExp('(^|\\n)\\s*- image: ');
 const language = 'docker';
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   updateDependency,
diff --git a/lib/manager/docker-compose/index.js b/lib/manager/docker-compose/index.js
index 0618fe466e..32df37546d 100644
--- a/lib/manager/docker-compose/index.js
+++ b/lib/manager/docker-compose/index.js
@@ -3,14 +3,12 @@ const { getPackageUpdates } = require('../docker/package');
 const { resolvePackageFile } = require('./resolve');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('(^|/)docker-compose[^/]*\\.ya?ml$');
 const contentPattern = new RegExp('(^|\\n)\\s*image:');
 const language = 'docker';
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   resolvePackageFile,
diff --git a/lib/manager/docker/index.js b/lib/manager/docker/index.js
index 2e4b1e3258..eb6283fdc1 100644
--- a/lib/manager/docker/index.js
+++ b/lib/manager/docker/index.js
@@ -3,13 +3,11 @@ const { getPackageUpdates } = require('./package');
 const { resolvePackageFile } = require('./resolve');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('(^|/)Dockerfile$');
 const contentPattern = new RegExp('(^|\\n)FROM .+\\n', 'i');
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   resolvePackageFile,
   updateDependency,
diff --git a/lib/manager/index.js b/lib/manager/index.js
index 295aa70b19..4f510c1d7a 100644
--- a/lib/manager/index.js
+++ b/lib/manager/index.js
@@ -65,9 +65,13 @@ async function detectPackageFiles(config) {
       continue; // eslint-disable-line no-continue
     }
     const files = [];
-    const { filePattern } = managers[manager];
-    logger.debug(`Using ${manager} file pattern: ${filePattern.toString()}`);
-    const allfiles = fileList.filter(file => file.match(filePattern));
+    let allfiles = [];
+    for (const fileMatch of config[manager].fileMatch) {
+      logger.debug(`Using ${manager} file match: ${fileMatch}`);
+      allfiles = allfiles.concat(
+        fileList.filter(file => file.match(new RegExp(fileMatch)))
+      );
+    }
     logger.debug(`Found ${allfiles.length} files`);
     for (const file of allfiles) {
       const { contentPattern } = managers[manager];
@@ -154,10 +158,12 @@ async function getUpdatedPackageFiles(config) {
   };
 }
 
-function getManager(filename) {
+function getManager(config, filename) {
   for (const manager of managerList) {
-    if (filename.match(managers[manager].filePattern)) {
-      return manager;
+    for (const fileMatch of config[manager].fileMatch) {
+      if (filename.match(new RegExp(fileMatch))) {
+        return manager;
+      }
     }
   }
   return null;
@@ -180,7 +186,7 @@ async function resolvePackageFiles(config) {
   async function resolvePackageFile(p) {
     let packageFile = typeof p === 'string' ? { packageFile: p } : p;
     const fileName = packageFile.packageFile.split('/').pop();
-    packageFile.manager = packageFile.manager || getManager(fileName);
+    packageFile.manager = packageFile.manager || getManager(config, fileName);
     const { manager } = packageFile;
     if (!manager) {
       // Config error
diff --git a/lib/manager/meteor/index.js b/lib/manager/meteor/index.js
index 8b13499f40..6c097c1df0 100644
--- a/lib/manager/meteor/index.js
+++ b/lib/manager/meteor/index.js
@@ -2,13 +2,11 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('../npm/package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('(^|/)package.js$');
 const contentPattern = new RegExp('(^|\\n)\\s*Npm.depends\\(\\s*{');
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   updateDependency,
 };
diff --git a/lib/manager/npm/index.js b/lib/manager/npm/index.js
index 07c5fdb0ba..fd6cd40406 100644
--- a/lib/manager/npm/index.js
+++ b/lib/manager/npm/index.js
@@ -3,11 +3,8 @@ const { getPackageUpdates } = require('./package');
 const { resolvePackageFile } = require('./resolve');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('(^|/)package.json$');
-
 module.exports = {
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   resolvePackageFile,
   updateDependency,
diff --git a/lib/manager/nvm/index.js b/lib/manager/nvm/index.js
index abcd005bc5..0d35d7ba9f 100644
--- a/lib/manager/nvm/index.js
+++ b/lib/manager/nvm/index.js
@@ -2,12 +2,10 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('^.nvmrc$');
 const language = 'node';
 
 module.exports = {
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   updateDependency,
diff --git a/lib/manager/pip_requirements/index.js b/lib/manager/pip_requirements/index.js
index 96ffb634fa..9d4ee6fddc 100644
--- a/lib/manager/pip_requirements/index.js
+++ b/lib/manager/pip_requirements/index.js
@@ -2,14 +2,12 @@ const { packagePattern, extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
-const filePattern = /(^|\/)([\w-]*)requirements.(txt|pip)$/;
 const contentPattern = new RegExp(`^${packagePattern}==`);
 const language = 'python';
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   updateDependency,
diff --git a/lib/manager/travis/index.js b/lib/manager/travis/index.js
index 4752f04cc8..de6acfbee4 100644
--- a/lib/manager/travis/index.js
+++ b/lib/manager/travis/index.js
@@ -2,14 +2,12 @@ const { extractDependencies } = require('./extract');
 const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
-const filePattern = new RegExp('^.travis.yml$');
 const contentPattern = new RegExp('(^|\\n)node_js:\\n');
 const language = 'node';
 
 module.exports = {
   contentPattern,
   extractDependencies,
-  filePattern,
   getPackageUpdates,
   language,
   updateDependency,
diff --git a/test/config/__snapshots__/index.spec.js.snap b/test/config/__snapshots__/index.spec.js.snap
index 8243dc5251..feff51b331 100644
--- a/test/config/__snapshots__/index.spec.js.snap
+++ b/test/config/__snapshots__/index.spec.js.snap
@@ -15,7 +15,11 @@ Object {
   "automerge": false,
   "automergeType": "pr",
   "baseBranches": Array [],
-  "bazel": Object {},
+  "bazel": Object {
+    "fileMatch": Array [
+      "(^|/)WORKSPACE$",
+    ],
+  },
   "branchName": "{{{branchPrefix}}}{{{managerBranchPrefix}}}{{{branchTopic}}}",
   "branchPrefix": "renovate/",
   "branchTopic": "{{{depNameSanitized}}}-{{{newVersionMajor}}}.x",
@@ -23,11 +27,18 @@ Object {
     "commitMessageExtra": "to {{newVersion}}",
     "commitMessageTopic": "buildkite plugin {{depName}}",
     "enabled": false,
+    "fileMatch": Array [
+      "\\\\.buildkite/.+\\\\.yml$",
+    ],
     "managerBranchPrefix": "buildkite-",
     "prBody": "This Pull Request updates buildkite plugin {{#if repositoryUrl}}[{{{depName}}}]({{{repositoryUrl}}}){{else}}\`{{{depName}}}\`{{/if}} from \`{{{currentVersion}}}\` to \`{{{newVersion}}}\`.\\n\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{#if hasCommits}}\\n\\n<details>\\n<summary>Commits</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.hasCommits}}\\n#### v{{{release.version}}}\\n{{#each release.commits as |commit|}}\\n-   [\`{{commit.shortSha}}\`]({{commit.url}}) {{commit.message}}\\n{{/each}}\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
   },
   "bumpVersion": null,
-  "circleci": Object {},
+  "circleci": Object {
+    "fileMatch": Array [
+      "^.circleci/config.yml$",
+    ],
+  },
   "commitBody": null,
   "commitMessage": "{{{commitMessagePrefix}}} {{{commitMessageAction}}} {{{commitMessageTopic}}} {{{commitMessageExtra}}} {{{commitMessageSuffix}}}",
   "commitMessageAction": "Update",
@@ -53,6 +64,9 @@ Object {
       },
       "prBody": "This Pull Request updates Docker base image \`{{{depName}}}:{{{currentTag}}}\` to the latest digest (\`{{{newDigest}}}\`). For details on Renovate's Docker support, please visit https://renovatebot.com/docs/language-support/docker\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
+    "fileMatch": Array [
+      "(^|/)Dockerfile$",
+    ],
     "group": Object {
       "commitMessageTopic": "{{{groupName}}} Docker tags",
       "prBody": "This Pull Request updates Dockerfiles to use image digests.\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if repositoryUrl}}[{{upgrade.depName}}]({{upgrade.repositoryUrl}}){{else}}\`{{{depName}}}\`{{/if}}: \`{{upgrade.newDigest}}\`\\n{{/each}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
@@ -73,7 +87,11 @@ Object {
     },
     "prBody": "This Pull Request updates Docker base image \`{{{depName}}}\` from tag \`{{{currentTag}}}\` to new tag \`{{{newTag}}}\`. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/language-support/docker\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
   },
-  "docker-compose": Object {},
+  "docker-compose": Object {
+    "fileMatch": Array [
+      "(^|/)docker-compose[^/]*\\\\.ya?ml$",
+    ],
+  },
   "enabled": true,
   "enabledManagers": Array [],
   "encrypted": null,
@@ -82,6 +100,7 @@ Object {
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "exposeEnv": false,
+  "fileMatch": Array [],
   "forkMode": false,
   "gitAuthor": null,
   "gitPrivateKey": null,
@@ -119,7 +138,11 @@ Object {
   "major": Object {},
   "managerBranchPrefix": "",
   "matchCurrentVersion": null,
-  "meteor": Object {},
+  "meteor": Object {
+    "fileMatch": Array [
+      "(^|/)package.js$",
+    ],
+  },
   "minor": Object {},
   "mirrorMode": false,
   "multipleMajorPrs": false,
@@ -130,10 +153,18 @@ Object {
     "groupName": "Node.js",
     "lazyGrouping": false,
   },
-  "npm": Object {},
+  "npm": Object {
+    "fileMatch": Array [
+      "(^|/)package.json$",
+    ],
+  },
   "npmToken": null,
   "npmrc": null,
-  "nvm": Object {},
+  "nvm": Object {
+    "fileMatch": Array [
+      "^.nvmrc$",
+    ],
+  },
   "onboarding": true,
   "onboardingConfig": Object {},
   "packageFiles": Array [],
@@ -159,6 +190,9 @@ Object {
   "pinVersions": false,
   "pip_requirements": Object {
     "enabled": false,
+    "fileMatch": Array [
+      "(^|\\\\/)([\\\\w-]*)requirements.(txt|pip)$",
+    ],
   },
   "platform": "github",
   "prBody": "This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} dependency {{#if repositoryUrl}}[{{{depName}}}]({{{repositoryUrl}}}){{else}}\`{{{depName}}}\`{{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentVersion}}}\` to \`{{#unless isRange}}v{{/unless}}{{{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}}\\n{{#if hasTypes}}\\n\\nThis PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.\\n{{/if}}\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{#if hasCommits}}\\n\\n<details>\\n<summary>Commits</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.hasCommits}}\\n#### v{{{release.version}}}\\n{{#each release.commits as |commit|}}\\n-   [\`{{commit.shortSha}}\`]({{commit.url}}) {{commit.message}}\\n{{/each}}\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
@@ -189,6 +223,9 @@ Object {
   "token": null,
   "travis": Object {
     "enabled": false,
+    "fileMatch": Array [
+      "^.travis.yml$",
+    ],
   },
   "unpublishSafe": false,
   "unstablePattern": null,
diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap
index 232ac12e3e..dfdb6d1ecb 100644
--- a/test/config/__snapshots__/validation.spec.js.snap
+++ b/test/config/__snapshots__/validation.spec.js.snap
@@ -57,6 +57,19 @@ Array [
 ]
 `;
 
+exports[`config/validation validateConfig(config) errors for unsafe fileMatches 1`] = `
+Array [
+  Object {
+    "depName": "Configuration Error",
+    "message": "Invalid regExp for npm.fileMatch: \`abc ([a-z]+) ([a-z]+))\`",
+  },
+  Object {
+    "depName": "Configuration Error",
+    "message": "Unsafe regExp for docker.fileMatch: \`(x+x+)+y\`",
+  },
+]
+`;
+
 exports[`config/validation validateConfig(config) ignore packageRule nesting validation for presets 1`] = `Array []`;
 
 exports[`config/validation validateConfig(config) invalid matchCurrentVersion triggers an error 1`] = `
diff --git a/test/config/validation.spec.js b/test/config/validation.spec.js
index d010aaf597..a7c5ead5f2 100644
--- a/test/config/validation.spec.js
+++ b/test/config/validation.spec.js
@@ -123,5 +123,21 @@ describe('config/validation', () => {
       expect(errors).toMatchSnapshot();
       expect(errors).toHaveLength(1);
     });
+    it('errors for unsafe fileMatches', async () => {
+      const config = {
+        npm: {
+          fileMatch: ['abc ([a-z]+) ([a-z]+))'],
+        },
+        docker: {
+          fileMatch: ['(x+x+)+y'],
+        },
+      };
+      const { warnings, errors } = await configValidation.validateConfig(
+        config
+      );
+      expect(warnings).toHaveLength(0);
+      expect(errors).toHaveLength(2);
+      expect(errors).toMatchSnapshot();
+    });
   });
 });
diff --git a/test/manager/__snapshots__/resolve.spec.js.snap b/test/manager/__snapshots__/resolve.spec.js.snap
index d3abfc6251..b71a0751cd 100644
--- a/test/manager/__snapshots__/resolve.spec.js.snap
+++ b/test/manager/__snapshots__/resolve.spec.js.snap
@@ -11,6 +11,9 @@ Array [
       "version": "1.0.0",
     },
     "currentPackageJsonVersion": "1.0.0",
+    "fileMatch": Array [
+      "(^|/)package.json$",
+    ],
     "manager": "npm",
     "packageFile": "package.json",
   },
@@ -29,6 +32,9 @@ Array [
       "version": "0.0.1",
     },
     "currentPackageJsonVersion": "0.0.1",
+    "fileMatch": Array [
+      "(^|/)package.json$",
+    ],
     "manager": "npm",
     "npmShrinkwrap": "npm-shrinkwrap.json",
     "npmrc": "npmrc",
@@ -51,6 +57,9 @@ Array [
       "version": "0.0.1",
     },
     "currentPackageJsonVersion": "0.0.1",
+    "fileMatch": Array [
+      "(^|/)package.json$",
+    ],
     "manager": "npm",
     "packageFile": "package.json",
   },
diff --git a/test/manager/index.spec.js b/test/manager/index.spec.js
index 7c6edbe566..9eafe3964d 100644
--- a/test/manager/index.spec.js
+++ b/test/manager/index.spec.js
@@ -143,23 +143,35 @@ describe('manager', () => {
   });
   describe('getManager', () => {
     it('rejects unknown files', () => {
-      expect(manager.getManager('WORKSPACER')).toBe(null);
+      expect(manager.getManager(defaultConfig, 'WORKSPACER')).toBe(null);
     });
     it('detects files in root', () => {
-      expect(manager.getManager('WORKSPACE')).toBe('bazel');
-      expect(manager.getManager('Dockerfile')).toBe('docker');
-      expect(manager.getManager('package.js')).toBe('meteor');
-      expect(manager.getManager('package.json')).toBe('npm');
-      expect(manager.getManager('.nvmrc')).toBe('nvm');
-      expect(manager.getManager('.travis.yml')).toBe('travis');
+      expect(manager.getManager(defaultConfig, 'WORKSPACE')).toBe('bazel');
+      expect(manager.getManager(defaultConfig, 'Dockerfile')).toBe('docker');
+      expect(manager.getManager(defaultConfig, 'package.js')).toBe('meteor');
+      expect(manager.getManager(defaultConfig, 'package.json')).toBe('npm');
+      expect(manager.getManager(defaultConfig, '.nvmrc')).toBe('nvm');
+      expect(manager.getManager(defaultConfig, '.travis.yml')).toBe('travis');
     });
     it('detects nested files', () => {
-      expect(manager.getManager('foo/bar/WORKSPACE')).toBe('bazel');
-      expect(manager.getManager('backend/Dockerfile')).toBe('docker');
-      expect(manager.getManager('package/a/package.js')).toBe('meteor');
-      expect(manager.getManager('frontend/package.json')).toBe('npm');
-      expect(manager.getManager('subfolder-1/.nvmrc')).toBe(null);
-      expect(manager.getManager('subfolder-2/.travis.yml')).toBe(null);
+      expect(manager.getManager(defaultConfig, 'foo/bar/WORKSPACE')).toBe(
+        'bazel'
+      );
+      expect(manager.getManager(defaultConfig, 'backend/Dockerfile')).toBe(
+        'docker'
+      );
+      expect(manager.getManager(defaultConfig, 'package/a/package.js')).toBe(
+        'meteor'
+      );
+      expect(manager.getManager(defaultConfig, 'frontend/package.json')).toBe(
+        'npm'
+      );
+      expect(manager.getManager(defaultConfig, 'subfolder-1/.nvmrc')).toBe(
+        null
+      );
+      expect(manager.getManager(defaultConfig, 'subfolder-2/.travis.yml')).toBe(
+        null
+      );
     });
   });
   describe('getUpdatedPackageFiles', () => {
diff --git a/test/manager/resolve.spec.js b/test/manager/resolve.spec.js
index bb8c76bed5..bbc5a53a62 100644
--- a/test/manager/resolve.spec.js
+++ b/test/manager/resolve.spec.js
@@ -110,7 +110,7 @@ describe('manager/resolve', () => {
       platform.getFile.mockReturnValueOnce('# comment\nFROM node:8\n'); // Dockerfile
       platform.getFile.mockReturnValueOnce('image: node:8\n'); // Docker Compose
       platform.getFile.mockReturnValueOnce('# travis'); // .travis.yml
-      platform.getFile.mockReturnValueOnce('# WORKSPACE'); // Dockerfile
+      platform.getFile.mockReturnValueOnce('# WORKSPACE'); // Dockerfileyarn j
       platform.getFile.mockReturnValueOnce('8.9\n'); // Dockerfile
       const res = await resolvePackageFiles(config);
       expect(res.packageFiles).toHaveLength(7);
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index a68a761233..f6266c103d 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -439,6 +439,15 @@ Preset configs to use/extend.
 
 See https://renovateapp.com/docs/configuration-reference/config-presets for details.
 
+## fileMatch
+
+JS RegExp pattern for matching manager files.
+
+| name    | value |
+| ------- | ----- |
+| type    | list  |
+| default | []    |
+
 ## gitAuthor
 
 | name    | value  |
-- 
GitLab