diff --git a/docs/configuration.md b/docs/configuration.md index b96fb23ab9b62836b3a31080537869193fafa729..38f56fb998664b9c01446f814e06f5b3b1c62b83 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -92,7 +92,7 @@ Obviously, you can't set repository or package file location with this method. |---------------------|---------------------------------------------------------|-----------------------------------------------------------|---------------------------|--------------------------|---------------------------| | Auth Token | GitHub Personal Access Token | | `token` | `GITHUB_TOKEN` | `--token` | | Repositories | List of Repositories | | `repositories` | `RENOVATE_REPOS` | Space-delimited arguments | -| Package Files | Package file location(s) | `package.json` | `repository.packageFiles` | `RENOVATE_PACKAGE_FILES` | `--package-files` | +| Package Files | Package file location(s) | `[]` | `repository.packageFiles` | `RENOVATE_PACKAGE_FILES` | `--package-files` | | Dependency Types | Sections of package.json to renovate | `dependencies`, `devDependencies`, `optionalDependencies` | `depTypes` | `RENOVATE_DEP_TYPES` | `--dep-types` | | Ignore Dependencies | Dependencies to be ignored | | `ignoreDeps` | `RENOVATE_IGNORE_DEPS` | `--ignore-deps` | | Labels | Labels to add to Pull Requests | | `labels` | `RENOVATE_LABELS` | `--labels` | diff --git a/docs/design-decisions.md b/docs/design-decisions.md index 1a9afb0e57e2ec2090c633f538f3fb9e68896ab3..1e7f456c7a46bd1893d0ab20dc8c7dc8fa812355 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -36,6 +36,12 @@ The following options apply globally: - Log Level +## Automatic discovery of package.json locations + +Default behaviour is to auto-discover all `package.json` locations in a repository and process them all. +Doing so means that "monorepos" are supported by default. +This can be overridden by the configuration option `packageFiles`, where you list the file paths manually (e.g. limit to just `package.json` in root of repository). + ## Separate Branches per dependency `renovate` will maintain separate branches per-dependency. So if 20 dependencies need updating, there will be at least 20 branches/PRs. Although this may seem undesirable, it was considered even less desirable if all 20 were in the same Pull Request and it's up to the users to determine which dependency upgrade(s) caused the build to fail. diff --git a/lib/api/github.js b/lib/api/github.js index 1cd1392b160383c364860af566ce5abc35f3743c..f8df50dff5b6407530fcb5bebf7e9af79e079d81 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -5,6 +5,8 @@ const config = {}; module.exports = { initRepo, + // Search + findFilePaths, // Branch createBranch, deleteBranch, @@ -24,6 +26,7 @@ module.exports = { // Initialize GitHub by getting base branch and SHA function initRepo(repoName) { + logger.debug(`initRepo(${repoName})`); config.repoName = repoName; return getRepoMetadata().then(getRepoBaseSHA) @@ -58,6 +61,18 @@ function initRepo(repoName) { } } +// Search + +// Returns an array of file paths in current repo matching the fileName +function findFilePaths(fileName) { + return ghGot(`search/code?q=repo:${config.repoName}+filename:${fileName}`) + .then((res) => { + const exactMatches = res.body.items.filter(item => item.name === fileName); + const filePaths = exactMatches.map(item => item.path); + return filePaths; + }); +} + // Branch function createBranch(branchName) { return ghGot.post(`repos/${config.repoName}/git/refs`, { diff --git a/lib/config/default.js b/lib/config/default.js index 334d632500284a08c7bc92a49634b7b1a31b6eb8..f3f105041a743d0c1b92ac5b9a77c99b74e7d0e1 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -3,6 +3,7 @@ const logger = require('winston'); const config = { logLevel: 'info', depTypes: ['dependencies', 'devDependencies', 'optionalDependencies'], + packageFiles: [], // Autodiscover ignoreDeps: [], labels: [], templates: { diff --git a/lib/config/index.js b/lib/config/index.js index eabcd9b04ad15313d9f720dd00a78ee837406309..ce80b8f40900299393df9fb7e68d1525ce611d2d 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -41,10 +41,10 @@ function parseConfigs() { config.repositories[index] = { repository: repo }; } }); - // Add 'package.json' if missing + // Set default packageFiles config.repositories.forEach((repo, index) => { if (!repo.packageFiles || !repo.packageFiles.length) { - config.repositories[index].packageFiles = ['package.json']; + config.repositories[index].packageFiles = config.packageFiles; } }); // Expand packageFile format diff --git a/lib/index.js b/lib/index.js index 2701965ad2642d6ee1c47764084ef9087af1ba2c..f2202a89f7dc49447db5886d24852aaa8721238d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,6 @@ const logger = require('./logger'); const config = require('./config'); +const github = require('./api/github'); // Require main source const worker = require('./worker'); @@ -18,13 +19,46 @@ function start() { // Get global config const globalConfig = config.getGlobalConfig(); - // Queue up each repo/package combination + // Queue repositories in sequence globalConfig.repositories.forEach((repo) => { - repo.packageFiles.forEach((packageFile) => { - const cascadedConfig = config.getCascadedConfig(repo, packageFile); - p = p.then(() => worker(repo.repository, packageFile.fileName, cascadedConfig)); - }); + p = p.then(() => processRepo(repo)); }); + + // Queue package files in sequence within a repo + function processRepo(repo) { + logger.debug(`Processing repository: ${JSON.stringify(repo)}`); + // Set GitHub token for this repo + process.env.GITHUB_TOKEN = repo.token || globalConfig.token; + // Initialize repo + return github.initRepo(repo.repository) + .then(() => ensurePackageFiles(repo)) + .then(getWorkers); + } + + // Ensure repo contains packageFiles + function ensurePackageFiles(repo) { + // Check for manually configured package files + if (repo.packageFiles.length) { + return Promise.resolve(repo); + } + // Otherwise, autodiscover filenames + return github.findFilePaths('package.json') + .then((fileNames) => { + const packageFiles = + fileNames.map(fileName => ({ fileName })); + return Object.assign(repo, { packageFiles }); + }); + } + + // Return a promise queue of package file workers + function getWorkers(repo) { + return repo.packageFiles.reduce((promise, packageFile) => { + const cascadedConfig = config.getCascadedConfig(repo, packageFile); + return promise.then(() => worker(repo.repository, packageFile.fileName, cascadedConfig)); + }, Promise.resolve()); + } + + // Process all promises p.then(() => { // eslint-disable-line promise/always-return logger.info('Renovate finished'); }) diff --git a/lib/worker.js b/lib/worker.js index 3f7c7937171c8d7af7c5146986b524ed1a0e9d06..fec7492aff4e2b590307f2f746f359c1d743f39f 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -14,14 +14,11 @@ function renovate(repoName, packageFile, setConfig) { // Initialize globals config = Object.assign({}, setConfig); config.packageFile = packageFile; - // We export the token back to ENV, on a per-repo basis - process.env.GITHUB_TOKEN = config.token; logger.info(`Processing ${repoName} ${packageFile}`); // Start the chain - return github.initRepo(repoName) - .then(() => github.getFileContents(packageFile)) + return github.getFileContents(packageFile) .then(checkforRenovateConfig) .then(contents => packageJson.extractDependencies(contents, config.depTypes)) .then(filterIgnoredDependencies) diff --git a/readme.md b/readme.md index fd8f98bbd5e31b9c3ad847e5095ba6266c2040d1..03757e4d28ebf6ce02c649696ba597a27ea7a1fc 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ ## Why - Creates or updates Pull Requests for each dependency that needs updating -- Supports multiple `package.json` files per repository +- Discovers and processes all `package.json` files in repository (supports monorepo architecture) - Supports multiple major versions per-dependency at once - Configurable via file, environment, CLI, and `package.json` - Self-hosted @@ -25,6 +25,7 @@ You need to select a GitHub user for `renovate` to assume the identity of. It's The script will need a GitHub Personal Access Token with "repo" permissions. You can find instructions for generating it here: https://help.github.com/articles/creating-an-access-token-for-command-line-use/ This token needs to be configured via file, environment variable, or CLI. See [docs/configuration.md](docs/configuration.md) for details. +The simplest way is to expose it as `GITHUB_TOKEN`. ## Usage (CLI)