diff --git a/bin/update-configuration-table.js b/bin/update-configuration-table.js new file mode 100644 index 0000000000000000000000000000000000000000..d68521c0508296c2199879b4c6fc9b6076b9c5a3 --- /dev/null +++ b/bin/update-configuration-table.js @@ -0,0 +1,32 @@ +#!/usr/bin/env node +const stringify = require('json-stringify-pretty-compact'); + +const definitions = require('../lib/config/definitions'); +const defaultsParser = require('../lib/config/defaults'); +const cliParser = require('../lib/config/cli'); +const envParser = require('../lib/config/env'); + +/* eslint-disable no-console */ +// Print table header +console.log('## Configuration Options'); +console.log(''); +console.log('| Name | Description | Type | Default value | Environment | CLI |'); +console.log('|------|-------------|------|---------------|-------------|-----|'); + +const options = definitions.getOptions(); +options.forEach((option) => { + let optionDefault = defaultsParser.getDefault(option); + if (optionDefault !== '') { + optionDefault = `\`${stringify(optionDefault)}\``; + } + let envName = envParser.getEnvName(option); + if (envName.length) { + envName = `\`${envName}\``; + } + let cliName = cliParser.getCliName(option); + if (cliName.length) { + cliName = `\`${cliName}\``; + } + console.log(`| \`${option.name}\` | ${option.description} | ${option.type} | ${optionDefault} | ${envName} | ${cliName} |`); +}); +/* eslint-enable no-console */ diff --git a/bin/update-docs.sh b/bin/update-docs.sh new file mode 100755 index 0000000000000000000000000000000000000000..9cb874f878638cf4695b9049cea19fdd2b4fd036 --- /dev/null +++ b/bin/update-docs.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +perl -0777 -i -pe 's/\n Usage:.*package-test\n/`node renovate --help`/se' readme.md +perl -0777 -i -pe 's/\n Usage:.*package-test\n/`node renovate --help`/se' docs/configuration.md +perl -0777 -i -pe 's/## Configuration Options.*//se' docs/configuration.md +node bin/update-configuration-table.js >> docs/configuration.md diff --git a/docs/configuration.md b/docs/configuration.md index 2c42c39065917f9a2ebf56a892347408ce13d473..3dc8fe9d21a029dd107f541d0e6f8d3296ca8863 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -56,24 +56,25 @@ $ node renovate --help Options: - -h, --help output usage information - -t, --token <token> GitHub Auth Token - -p, --package-files <list> List of package.json file names - -d, --dep-types <list> List of dependency types - -i, --ignore-deps <list> List of dependencies to ignore - -b, --labels <list> List of labels to add - -b, --assignees <list> List of assignees to add - -r, --ignore-future [true/false] Ignore versions tagged as "future" - -r, --ignore-unstable [true/false] Ignore versions with unstable semver - -r, --respect-latest [true/false] Ignore versions newer than dependency's "latest" - -r, --recreate-closed [true/false] Recreate PR even if same was previously closed - -r, --recreate-unmergeable [true/false] Recreate PR if existing branch is unmergeable - -l, --log-level <level> Log Level + -h, --help output usage information + --enabled [boolean] Enable or disable renovate + --token <string> GitHub 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 + --recreate-unmergeable [boolean] Close and recreate PR if it has a merge conflict + --labels <list> Labels to add to Pull Request + --assignees <list> Assignees for Pull Request + --log-level <string> Logging level Examples: $ renovate --token abc123 singapore/lint-condo - $ renovate --ignore-unstable=false -l verbose singapore/lint-condo + $ renovate --ignore-unstable=false --log-level verbose singapore/lint-condo $ renovate singapore/lint-condo singapore/package-test ``` @@ -98,19 +99,23 @@ Obviously, you can't set repository or package file location with this method. ## Configuration Options -| Option | Description | Default value | File | Environment | CLI | -|---------------------|---------------------------------------------------------|-----------------------------------------------------------|---------------------------|--------------------------|---------------------------| -| Token | GitHub Personal Access Token | | `token` | `GITHUB_TOKEN` | `--token` | -| Enabled | Enable/Disable renovate for this repo or file | true | `enabled` | | | -| Repositories | List of Repositories | | `repositories` | `RENOVATE_REPOS` | Space-delimited arguments | -| 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` | -| Ignore Future | Ignore versions tagged as "future" | `true` | `ignoreFuture` | `RENOVATE_IGNORE_FUTURE` | `--ignore-future` | -| Ignore Unstable | Ignore versions with unstable semver | `true` | `ignoreUnstable` | `RENOVATE_IGNORE_UNSTABLE` | `--ignore-unstable` | -| Respect latest | Respect the "latest" tag in npm and don't upgrade past it | `true` | `respectLatest` | `RENOVATE_RESPECT_LATEST` | `--respect-latest` | -| Recreate Closed | Create New PR even if same one was previously closed | `false` | `recreateClosed` | `RENOVATE_RECREATE_CLOSED` | `--recreate-closed` | -| Recreate Unmergeable | Close and recreate PR if existing one is unmergeable | `true` | `recreateUnmergeable` | `RENOVATE_RECREATE_UNMERGEABLE` | `--recreate-unmergeable` | -| Log Level | Log Level | `info` | `logLevel` | `LOG_LEVEL` | `--log-level` | -| Templates | Handlebars templates for commit, branch and PR | Multiple | `templates` | | | +| Name | Description | Type | Default value | Environment | CLI | +|------|-------------|------|---------------|-------------|-----| +| `enabled` | Enable or disable renovate | boolean | `true` | `RENOVATE_ENABLED` | `--enabled` | +| `token` | GitHub Auth Token | string | `null` | `GITHUB_TOKEN` | `--token` | +| `repositories` | GitHub repositories | list | `[]` | `RENOVATE_REPOSITORIES` | | +| `packageFiles` | Package file paths | list | `[]` | `RENOVATE_PACKAGE_FILES` | `--package-files` | +| `depTypes` | Dependency types | list | `["dependencies", "devDependencies", "optionalDependencies"]` | `RENOVATE_DEP_TYPES` | `--dep-types` | +| `ignoreDeps` | Dependencies to ignore | list | `[]` | `RENOVATE_IGNORE_DEPS` | `--ignore-deps` | +| `ignoreFuture` | Ignore versions tagged as "future" | boolean | `true` | `RENOVATE_IGNORE_FUTURE` | `--ignore-future` | +| `ignoreUnstable` | Ignore versions with unstable semver | boolean | `true` | `RENOVATE_IGNORE_UNSTABLE` | `--ignore-unstable` | +| `respectLatest` | Ignore versions newer than npm "latest" version | boolean | `true` | `RENOVATE_RESPECT_LATEST` | `--respect-latest` | +| `recreateClosed` | Recreate PRs even if same ones were closed previously | boolean | `false` | `RENOVATE_RECREATE_CLOSED` | `--recreate-closed` | +| `recreateUnmergeable` | Close and recreate PR if it has a merge conflict | boolean | `true` | `RENOVATE_RECREATE_UNMERGEABLE` | `--recreate-unmergeable` | +| `branchName` | Branch name template | string | `"renovate/{{depName}}-{{newVersionMajor}}.x"` | | | +| `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 isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}"` | | | +| `prBody` | Pull Request body template | string | `"This Pull Request updates dependency {{depName}} from version {{currentVersion}} to {{newVersion}}\n\n{{changelog}}"` | | | +| `labels` | Labels to add to Pull Request | list | `[]` | `RENOVATE_LABELS` | `--labels` | +| `assignees` | Assignees for Pull Request | list | `[]` | `RENOVATE_ASSIGNEES` | `--assignees` | +| `logLevel` | Logging level | string | `"info"` | `LOG_LEVEL` | `--log-level` | diff --git a/lib/config/cli.js b/lib/config/cli.js index 4f2e79f16aea5088842c40956172843d3a8c66f9..d37066c3858eb872485aabb239b3bbad74bb4f46 100644 --- a/lib/config/cli.js +++ b/lib/config/cli.js @@ -1,86 +1,68 @@ -const logger = require('winston'); -const program = require('commander'); +const clearRequire = require('clear-require'); +let commander = require('commander'); +const configDefinitions = require('./definitions'); -const config = {}; +module.exports = { + getCliName, + getConfig, +}; -program - .arguments('[repositories...]') - .option('-t, --token <token>', 'GitHub Auth Token') - .option('-p, --package-files <list>', 'List of package.json file names', list) - .option('-d, --dep-types <list>', 'List of dependency types', list) - .option('-i, --ignore-deps <list>', 'List of dependencies to ignore', list) - .option('-b, --labels <list>', 'List of labels to add', list) - .option('-b, --assignees <list>', 'List of assignees to add', list) - .option('-r, --ignore-future [true/false]', 'Ignore versions tagged as "future"', bool) - .option('-r, --ignore-unstable [true/false]', 'Ignore versions with unstable semver') - .option('-r, --respect-latest [true/false]', 'Ignore versions newer than dependency\'s "latest"') - .option('-r, --recreate-closed [true/false]', 'Recreate PR even if same was previously closed') - .option('-r, --recreate-unmergeable [true/false]', 'Recreate PR if existing branch is unmergeable') - .option('-l, --log-level <level>', 'Log Level') - .on('--help', () => { +function getCliName(option) { + if (option.cli === false) { + return ''; + } + const nameWithHyphens = option.name.replace(/([A-Z])/g, '-$1'); + return `--${nameWithHyphens.toLowerCase()}`; +} + +function getConfig(argv) { + clearRequire('commander'); + commander = require('commander'); // eslint-disable-line + const options = configDefinitions.getOptions(); + + const config = {}; + + const coersions = { + boolean: val => (val === 'true'), + list: val => val.split(',').map(el => el.trim()), + string: val => val, + }; + + let program = commander.arguments('[repositories...]'); + + options.forEach((option) => { + if (option.cli !== false) { + const param = `<${option.type}>`.replace('<boolean>', '[boolean]'); + const optionString = `${getCliName(option)} ${param}`; + program = program.option(optionString, option.description, coersions[option.type]); + } + }); + + /* istanbul ignore next */ + function helpConsole() { /* eslint-disable no-console */ console.log(' Examples:'); console.log(''); console.log(' $ renovate --token abc123 singapore/lint-condo'); - console.log(' $ renovate --ignore-unstable=false -l verbose singapore/lint-condo'); + console.log(' $ renovate --ignore-unstable=false --log-level verbose singapore/lint-condo'); console.log(' $ renovate singapore/lint-condo singapore/package-test'); - console.log(''); /* eslint-enable no-console */ - }) - .action((repositories) => { - config.repositories = repositories; - }) - .parse(process.argv); - -if (program.depTypes) { - config.depTypes = program.depTypes; -} -if (program.ignoreDeps) { - config.ignoreDeps = program.ignoreDeps; -} -if (program.labels) { - config.labels = program.labels; -} -if (program.assignees) { - config.assignees = program.assignees; -} -if (program.logLevel) { - config.logLevel = program.logLevel; -} -if (program.packageFiles) { - config.packageFiles = program.packageFiles; -} -if (program.ignoreFuture) { - config.ignoreFuture = program.ignoreFuture; -} -if (program.ignoreUnstable) { - config.ignoreUnstable = program.ignoreUnstable; -} -if (program.respectLatest) { - config.respectLatest = program.respectLatest; -} -if (program.recreateClosed) { - config.recreateClosed = program.recreateClosed; -} -if (program.recreateUnmergeable) { - config.recreateUnmergeable = program.recreateUnmergeable; -} -if (program.token) { - config.token = program.token; -} + } -module.exports = config; + program = program + .on('--help', helpConsole) + .action((repositories) => { + config.repositories = repositories; + }) + .parse(argv); -function list(val) { - return val.split(','); -} + options.forEach((option) => { + if (option.cli !== false) { + if (program[option.name] !== undefined) { + config[option.name] = program[option.name]; + } + } + }); -function bool(val) { - if (val === 'true') { - return true; - } else if (val === 'false') { - return false; - } - logger.error(`Boolean option must be true or false (is: "${val}")`); - return process.exit(1); + return config; } diff --git a/lib/config/default.js b/lib/config/default.js deleted file mode 100644 index 028fed68157d78a519f616e0de8b90a293754b77..0000000000000000000000000000000000000000 --- a/lib/config/default.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - enabled: true, - packageFiles: [], // Autodiscover - depTypes: ['dependencies', 'devDependencies', 'optionalDependencies'], - ignoreDeps: [], - assignees: [], - labels: [], - branchName: 'renovate/{{depName}}-{{newVersionMajor}}.x', - commitMessage: 'Update dependency {{depName}} to version {{newVersion}}', - prTitle: '{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}', - prBody: 'This Pull Request updates dependency {{depName}} from version {{currentVersion}} to {{newVersion}}\n\n{{changelog}}', - ignoreFuture: true, - ignoreUnstable: true, - respectLatest: true, - recreateClosed: false, - recreateUnmergeable: true, - logLevel: 'info', -}; diff --git a/lib/config/defaults.js b/lib/config/defaults.js new file mode 100644 index 0000000000000000000000000000000000000000..103213c7a7a64028a228732c7a8a34c906831db5 --- /dev/null +++ b/lib/config/defaults.js @@ -0,0 +1,27 @@ +const configDefinitions = require('./definitions'); + +module.exports = { + getDefault, + getConfig, +}; + +const defaultValues = { + boolean: true, + list: [], + string: null, +}; + +function getDefault(option) { + return option.default === undefined ? defaultValues[option.type] : option.default; +} + +function getConfig() { + const options = configDefinitions.getOptions(); + const config = {}; + + options.forEach((option) => { + config[option.name] = getDefault(option); + }); + + return config; +} diff --git a/lib/config/definitions.js b/lib/config/definitions.js new file mode 100644 index 0000000000000000000000000000000000000000..78ec52db3841b6f013dae4c4cbce85d4e2a5ebfa --- /dev/null +++ b/lib/config/definitions.js @@ -0,0 +1,125 @@ +// const logger = require('winston'); + +module.exports = { + getOptions, +}; + +const options = [ + { + name: 'enabled', + description: 'Enable or disable renovate', + type: 'boolean', + }, + { + name: 'token', + description: 'GitHub Auth Token', + type: 'string', + env: 'GITHUB_TOKEN', + }, + { + name: 'repositories', + description: 'GitHub repositories', + type: 'list', + cli: false, + }, + { + name: 'packageFiles', + description: 'Package file paths', + type: 'list', + }, + { + name: 'depTypes', + description: 'Dependency types', + type: 'list', + default: ['dependencies', 'devDependencies', 'optionalDependencies'], + }, + // Version behaviour + { + name: 'ignoreDeps', + description: 'Dependencies to ignore', + type: 'list', + }, + { + name: 'ignoreFuture', + description: 'Ignore versions tagged as "future"', + type: 'boolean', + }, + { + name: 'ignoreUnstable', + description: 'Ignore versions with unstable semver', + type: 'boolean', + }, + { + name: 'respectLatest', + description: 'Ignore versions newer than npm "latest" version', + type: 'boolean', + }, + // PR Behaviour + { + name: 'recreateClosed', + description: 'Recreate PRs even if same ones were closed previously', + type: 'boolean', + default: false, + }, + { + name: 'recreateUnmergeable', + description: 'Close and recreate PR if it has a merge conflict', + type: 'boolean', + }, + // String templates + { + name: 'branchName', + description: 'Branch name template', + type: 'string', + default: 'renovate/{{depName}}-{{newVersionMajor}}.x', + cli: false, + env: false, + }, + { + name: 'commitMessage', + description: 'Commit message template', + type: 'string', + default: 'Update dependency {{depName}} to version {{newVersion}}', + cli: false, + env: false, + }, + { + name: 'prTitle', + description: 'Pull Request title template', + type: 'string', + default: '{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}', + cli: false, + env: false, + }, + { + name: 'prBody', + description: 'Pull Request body template', + type: 'string', + default: 'This Pull Request updates dependency {{depName}} from version {{currentVersion}} to {{newVersion}}\n\n{{changelog}}', + cli: false, + env: false, + }, + // Pull Request options + { + name: 'labels', + description: 'Labels to add to Pull Request', + type: 'list', + }, + { + name: 'assignees', + description: 'Assignees for Pull Request', + type: 'list', + }, + // Debug options + { + name: 'logLevel', + description: 'Logging level', + type: 'string', + default: 'info', + env: 'LOG_LEVEL', + }, +]; + +function getOptions() { + return options; +} diff --git a/lib/config/env.js b/lib/config/env.js index 4e06e3f2870d5813d7fcd19b5d8634a4c9f81663..133e96fe83b75c03c0c0977ebf95b4b30a0426ac 100644 --- a/lib/config/env.js +++ b/lib/config/env.js @@ -1,59 +1,41 @@ -const logger = require('winston'); +const configDefinitions = require('./definitions'); -const config = {}; +module.exports = { + getEnvName, + getConfig, +}; -if (process.env.GITHUB_TOKEN) { - config.token = process.env.GITHUB_TOKEN; -} -if (process.env.RENOVATE_REPOS) { - config.repositories = list(process.env.RENOVATE_REPOS); -} -if (process.env.RENOVATE_PACKAGE_FILES) { - config.packageFiles = list(process.env.PACKAGE_FILES); -} -if (process.env.RENOVATE_DEP_TYPES) { - config.depTypes = list(process.env.RENOVATE_DEP_TYPES); -} -if (process.env.RENOVATE_IGNORE_DEPS) { - config.ignoreDeps = list(process.env.RENOVATE_IGNORE_DEPS); -} -if (process.env.RENOVATE_LABELS) { - config.labels = list(process.env.RENOVATE_LABELS); -} -if (process.env.RENOVATE_ASSIGNEES) { - config.assignees = list(process.env.RENOVATE_ASSIGNEES); -} -if (process.env.RENOVATE_IGNORE_FUTURE) { - config.ignoreFuture = bool(process.env.RENOVATE_IGNORE_FUTURE); -} -if (process.env.RENOVATE_IGNORE_UNSTABLE) { - config.ignoreUnstable = bool(process.env.RENOVATE_IGNORE_UNSTABLE); -} -if (process.env.RENOVATE_RESPECT_LATEST) { - config.respectLatest = bool(process.env.RENOVATE_RESPECT_LATEST); -} -if (process.env.RENOVATE_RECREATE_CLOSED) { - config.recreateClosed = bool(process.env.RENOVATE_RECREATE_CLOSED); -} -if (process.env.RENOVATE_RECREATE_UNMERGEABLE) { - config.recreateUnmergeable = bool(process.env.RENOVATE_RECREATE_UNMERGEABLE); -} -if (process.env.LOG_LEVEL) { - config.logLevel = process.env.LOG_LEVEL; +function getEnvName(option) { + if (option.env === false) { + return ''; + } + if (option.env) { + return option.env; + } + const nameWithUnderscores = option.name.replace(/([A-Z])/g, '_$1'); + return `RENOVATE_${nameWithUnderscores.toUpperCase()}`; } -module.exports = config; +function getConfig(env) { + const options = configDefinitions.getOptions(); -function list(val) { - return val.split(',').map(el => el.trim()); -} + const config = {}; -function bool(val) { - if (val === 'true') { - return true; - } else if (val === 'false') { - return false; - } - logger.error(`Boolean environment variable must be true or false (is: "${val}")`); - return process.exit(1); + const coersions = { + boolean: val => (val === 'true'), + list: val => val.split(',').map(el => el.trim()), + string: val => val, + }; + + options.forEach((option) => { + if (option.env !== false) { + const envName = getEnvName(option); + if (env[envName]) { + const coerce = coersions[option.type]; + config[option.name] = coerce(env[envName]); + } + } + }); + + return config; } diff --git a/lib/config/file.js b/lib/config/file.js index 44103039e1873631d240a0cafd444706eaa758ca..b103f6a9de720a7e9ba28e1130829c7da0e64288 100644 --- a/lib/config/file.js +++ b/lib/config/file.js @@ -1,21 +1,26 @@ const logger = require('winston'); -let configFile = process.env.RENOVATE_CONFIG_FILE || 'config'; -if (!isPathAbsolute(configFile)) { - configFile = `../../${configFile}`; -} +module.exports = { + getConfig, + isPathAbsolute, +}; -let config = {}; -try { - // eslint-disable-next-line global-require,import/no-dynamic-require - config = require(configFile); -} catch (err) { - // Do nothing - logger.verbose('Could not locate config file'); +function getConfig(env) { + let configFile = env.RENOVATE_CONFIG_FILE || 'config'; + if (!isPathAbsolute(configFile)) { + configFile = `../../${configFile}`; + } + let config = {}; + try { + // eslint-disable-next-line global-require,import/no-dynamic-require + config = require(configFile); + } catch (err) { + // Do nothing + logger.verbose('Could not locate config file'); + } + return config; } -module.exports = config; - function isPathAbsolute(path) { return /^(?:\/|[a-z]+:\/\/)/.test(path); } diff --git a/lib/config/index.js b/lib/config/index.js index e24189da4c95c9e15018a03bafa88831866bbc59..d39656391f290177088c4855c6d50578b09b978f 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -1,19 +1,27 @@ const logger = require('winston'); -const program = require('commander'); const stringify = require('json-stringify-pretty-compact'); +const defaultsParser = require('./defaults'); +const fileParser = require('./file'); +const cliParser = require('./cli'); +const envParser = require('./env'); + let config = null; -function parseConfigs() { +module.exports = { + parseConfigs, + getCascadedConfig, + getGlobalConfig, +}; + +function parseConfigs(env, argv) { logger.debug('Parsing configs'); // Get configs - /* eslint-disable global-require */ - const defaultConfig = require('./default'); - const fileConfig = require('./file'); - const cliConfig = require('./cli'); - const envConfig = require('./env'); - /* eslint-enable global-require */ + const defaultConfig = defaultsParser.getConfig(); + const fileConfig = fileParser.getConfig(env); + const cliConfig = cliParser.getConfig(argv); + const envConfig = envParser.getConfig(env); logger.debug(`Default config = ${redact(defaultConfig)}`); logger.debug(`File config = ${redact(fileConfig)}`); @@ -27,20 +35,13 @@ function parseConfigs() { // Set log level logger.level = config.logLevel; - // Save default templates - config.defaultTemplates = defaultConfig.templates; - // Check for token - if (typeof config.token === 'undefined') { - logger.error('A GitHub token must be configured'); - program.outputHelp(); - process.exit(1); + if (config.token === null) { + throw new Error('A GitHub token must be configured'); } // We need at least one repository defined if (!config.repositories || config.repositories.length === 0) { - logger.error('At least one repository must be configured'); - program.outputHelp(); - process.exit(1); + throw new Error('At least one repository must be configured'); } // Convert any repository strings to objects config.repositories.forEach((repo, index) => { @@ -69,8 +70,6 @@ function parseConfigs() { function getCascadedConfig(repo, packageFile) { const cascadedConfig = Object.assign({}, config, repo, packageFile); - // Fill in any missing templates with defaults - cascadedConfig.templates = Object.assign({}, config.defaultTemplates, cascadedConfig.templates); // Remove unnecessary fields delete cascadedConfig.repositories; delete cascadedConfig.repository; @@ -90,9 +89,3 @@ function redact(inputConfig) { const redactedConfig = Object.assign({}, inputConfig, tokenConfig); return stringify(redactedConfig); } - -module.exports = { - getCascadedConfig, - getGlobalConfig, - parseConfigs, -}; diff --git a/lib/index.js b/lib/index.js index 003968ed162ec7af9aedce6e8cbc6ac86c828f8a..fffc1d4451cfda87342acafac8e2539086b946a8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -12,7 +12,12 @@ module.exports = { function start() { // Parse config - config.parseConfigs(); + try { + config.parseConfigs(process.env, process.argv); + } catch (error) { + logger.error(error.message); + process.exit(-1); + } // Initialize our promise chain let p = Promise.resolve(); diff --git a/package.json b/package.json index b63f0b762dafe857023d392265a51ed29c801af7..10ae010243871c75e0af876e0a6635034fc16d1b 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "heroku-scheduler": "heroku addons:open scheduler", "istanbul": "istanbul cover _mocha test", "mocha": "mocha test", + "publish": "npm run update-docs && np", "renovate": "node renovate", - "test": "npm run eslint && npm run istanbul" + "test": "npm run eslint && npm run istanbul", + "update-docs": "bash bin/update-docs.sh" }, "repository": { "type": "git", @@ -34,6 +36,7 @@ }, "dependencies": { "changelog": "dylang/changelog#v1.2.0", + "clear-require": "1.0.1", "commander": "2.9.0", "gh-got": "5.0.0", "got": "6.7.1", @@ -51,6 +54,7 @@ "eslint-plugin-import": "2.2.0", "eslint-plugin-promise": "3.4.0", "istanbul": "0.4.5", - "mocha": "3.2.0" + "mocha": "3.2.0", + "np": "2.12.0" } } diff --git a/readme.md b/readme.md index 62b108b6b700cdf38f5fadd3f1b8ddd022362b4d..01b1cca561b0a9ddbc7bb658891e7a7d41f483fc 100644 --- a/readme.md +++ b/readme.md @@ -27,7 +27,7 @@ The script will need a GitHub Personal Access Token with "repo" permissions. You 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) +## Usage ``` $ node renovate --help @@ -36,24 +36,25 @@ $ node renovate --help Options: - -h, --help output usage information - -t, --token <token> GitHub Auth Token - -p, --package-files <list> List of package.json file names - -d, --dep-types <list> List of dependency types - -i, --ignore-deps <list> List of dependencies to ignore - -b, --labels <list> List of labels to add - -b, --assignees <list> List of assignees to add - -r, --ignore-future [true/false] Ignore versions tagged as "future" - -r, --ignore-unstable [true/false] Ignore versions with unstable semver - -r, --respect-latest [true/false] Ignore versions newer than dependency's "latest" - -r, --recreate-closed [true/false] Recreate PR even if same was previously closed - -r, --recreate-unmergeable [true/false] Recreate PR if existing branch is unmergeable - -l, --log-level <level> Log Level + -h, --help output usage information + --enabled [boolean] Enable or disable renovate + --token <string> GitHub 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 + --recreate-unmergeable [boolean] Close and recreate PR if it has a merge conflict + --labels <list> Labels to add to Pull Request + --assignees <list> Assignees for Pull Request + --log-level <string> Logging level Examples: $ renovate --token abc123 singapore/lint-condo - $ renovate --ignore-unstable=false -l verbose singapore/lint-condo + $ renovate --ignore-unstable=false --log-level verbose singapore/lint-condo $ renovate singapore/lint-condo singapore/package-test ``` diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 9ec8ec428d248ad9015babbfed097523094b90e6..1deb8028f830049b2c8994a9cfeca2360d42d983 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -2,4 +2,7 @@ module.exports = { 'env': { 'mocha': true, }, + 'rules': { + 'import/no-extraneous-dependencies': 0, + }, }; diff --git a/test/_fixtures/config/argv.js b/test/_fixtures/config/argv.js new file mode 100644 index 0000000000000000000000000000000000000000..e4944d56168fb16c35881294bc4bc5bd36786468 --- /dev/null +++ b/test/_fixtures/config/argv.js @@ -0,0 +1,4 @@ +module.exports = [ + '/usr/local/bin/node', + '/Users/me/github/renovate/renovate', +]; diff --git a/test/_fixtures/config/file.js b/test/_fixtures/config/file.js new file mode 100644 index 0000000000000000000000000000000000000000..54eda96d35fbca389300b0b85d6fda2622c7b51a --- /dev/null +++ b/test/_fixtures/config/file.js @@ -0,0 +1,20 @@ +module.exports = { + token: 'abcdefg', + logLevel: 'verbose', + repositories: [ + 'singapore/lint-condo', + { + repository: 'singapore/renovate', + packageFiles: ['package2.json'], + }, + { + repository: 'singapore/renovate', + packageFiles: [ + { + fileName: 'package.json', + labels: ['a'], + }, + ], + }, + ], +}; diff --git a/test/chai.js b/test/chai.js new file mode 100644 index 0000000000000000000000000000000000000000..41e09fc5389f881e86ec4f8b9711cb43f255d111 --- /dev/null +++ b/test/chai.js @@ -0,0 +1 @@ +require('chai').should(); diff --git a/test/config/cli.js b/test/config/cli.js new file mode 100644 index 0000000000000000000000000000000000000000..f583d1f118a57aa66b4077ad4f47932f8b3f376d --- /dev/null +++ b/test/config/cli.js @@ -0,0 +1,66 @@ +const cli = require('../../lib/config/cli.js'); +let argv = require('../_fixtures/config/argv'); + +describe('config/cli', () => { + describe('.getCliName(definition)', () => { + it('generates CLI value', () => { + const option = { + name: 'oneTwoThree', + }; + cli.getCliName(option).should.eql('--one-two-three'); + }); + }); + describe('.getConfig(argv)', () => { + it('returns empty argv', () => { + cli.getConfig(argv).should.eql({}); + }); + it('supports boolean no value', () => { + argv.push('--recreate-closed'); + cli.getConfig(argv).should.eql({ recreateClosed: true }); + argv = argv.slice(0, -1); + }); + it('supports boolean space true', () => { + argv.push('--recreate-closed'); + argv.push('true'); + cli.getConfig(argv).should.eql({ recreateClosed: true }); + argv = argv.slice(0, -2); + }); + it('supports boolean space false', () => { + argv.push('--recreate-closed'); + argv.push('false'); + cli.getConfig(argv).should.eql({ recreateClosed: false }); + argv = argv.slice(0, -2); + }); + it('supports boolean equals true', () => { + argv.push('--recreate-closed=true'); + cli.getConfig(argv).should.eql({ recreateClosed: true }); + argv = argv.slice(0, -1); + }); + it('supports boolean equals false', () => { + argv.push('--recreate-closed=false'); + cli.getConfig(argv).should.eql({ recreateClosed: false }); + argv = argv.slice(0, -1); + }); + it('supports list single', () => { + argv.push('--labels=a'); + cli.getConfig(argv).should.eql({ labels: ['a'] }); + argv = argv.slice(0, -1); + }); + it('supports list multiple', () => { + argv.push('--labels=a,b,c'); + cli.getConfig(argv).should.eql({ labels: ['a', 'b', 'c'] }); + argv = argv.slice(0, -1); + }); + it('supports string', () => { + argv.push('--token=a'); + cli.getConfig(argv).should.eql({ token: 'a' }); + argv = argv.slice(0, -1); + }); + it('supports repositories', () => { + argv.push('foo'); + argv.push('bar'); + cli.getConfig(argv).should.eql({ repositories: ['foo', 'bar'] }); + argv = argv.slice(0, -2); + }); + }); +}); diff --git a/test/config/env.js b/test/config/env.js new file mode 100644 index 0000000000000000000000000000000000000000..6e7f1b6a6189ad59308f7fb235e29b3e3417202a --- /dev/null +++ b/test/config/env.js @@ -0,0 +1,49 @@ +const env = require('../../lib/config/env.js'); + +describe('config/env', () => { + describe('.getConfig(env)', () => { + it('returns empty env', () => { + env.getConfig({}).should.eql({}); + }); + it('supports boolean true', () => { + const envParam = { RENOVATE_RECREATE_CLOSED: 'true' }; + env.getConfig(envParam).should.eql({ recreateClosed: true }); + }); + it('supports boolean false', () => { + const envParam = { RENOVATE_RECREATE_CLOSED: 'false' }; + env.getConfig(envParam).should.eql({ recreateClosed: false }); + }); + it('supports boolean nonsense as false', () => { + const envParam = { RENOVATE_RECREATE_CLOSED: 'foo' }; + env.getConfig(envParam).should.eql({ recreateClosed: false }); + }); + delete process.env.RENOVATE_RECREATE_CLOSED; + it('supports list single', () => { + const envParam = { RENOVATE_LABELS: 'a' }; + env.getConfig(envParam).should.eql({ labels: ['a'] }); + }); + it('supports list multiple', () => { + const envParam = { RENOVATE_LABELS: 'a,b,c' }; + env.getConfig(envParam).should.eql({ labels: ['a', 'b', 'c'] }); + }); + it('supports string', () => { + const envParam = { GITHUB_TOKEN: 'a' }; + env.getConfig(envParam).should.eql({ token: 'a' }); + }); + }); + describe('.getEnvName(definition)', () => { + it('returns existing env', () => { + const option = { + name: 'foo', + env: 'FOO', + }; + env.getEnvName(option).should.eql('FOO'); + }); + it('generates RENOVATE_ env', () => { + const option = { + name: 'oneTwoThree', + }; + env.getEnvName(option).should.eql('RENOVATE_ONE_TWO_THREE'); + }); + }); +}); diff --git a/test/config/file.js b/test/config/file.js new file mode 100644 index 0000000000000000000000000000000000000000..a6f03fb9cdefec829180a3a4e51e44dfcfa5a2dc --- /dev/null +++ b/test/config/file.js @@ -0,0 +1,13 @@ +const file = require('../../lib/config/file.js'); +const customConfig = require('../_fixtures/config/file'); + +describe('config/file', () => { + describe('.getConfig()', () => { + it('returns empty env', () => { + file.getConfig({}).should.eql({}); + }); + it('parses custom config file', () => { + file.getConfig({ RENOVATE_CONFIG_FILE: 'test/_fixtures/config/file.js' }).should.eql(customConfig); + }); + }); +}); diff --git a/test/config/index.js b/test/config/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fb01569a9486b380c9f3e68ad659b9559530d2c3 --- /dev/null +++ b/test/config/index.js @@ -0,0 +1,40 @@ +const configParser = require('../../lib/config/index.js'); +const defaultArgv = require('../_fixtures/config/argv'); +const should = require('chai').should(); + +describe('config/index', () => { + describe('.parseConfigs(env, defaultArgv)', () => { + it('throws for no token', () => { + const env = {}; + configParser.parseConfigs.bind(configParser, env, defaultArgv).should.throw('A GitHub token must be configured'); + }); + it('supports token in env', () => { + const env = { GITHUB_TOKEN: 'abc' }; + configParser.parseConfigs.bind(configParser, env, defaultArgv).should.throw('At least one repository must be configured'); + }); + it('supports token in CLI options', () => { + const env = {}; + const argv = defaultArgv.concat(['--token=abc']); + configParser.parseConfigs.bind(configParser, env, argv).should.throw('At least one repository must be configured'); + }); + it('supports repositories in CLI', () => { + const env = {}; + const argv = defaultArgv.concat(['--token=abc', 'foo']); + configParser.parseConfigs(env, argv); + const config = configParser.getGlobalConfig(); + should.exist(config.token); + should.exist(config.repositories); + should.exist(config.recreateClosed); + }); + it('gets cascaded config', () => { + const env = { RENOVATE_CONFIG_FILE: 'test/_fixtures/config/file.js' }; + configParser.parseConfigs(env, defaultArgv); + const config = configParser.getGlobalConfig(); + const repo = config.repositories.pop(); + should.exist(repo); + const cascadedConfig = configParser.getCascadedConfig(repo, null); + should.exist(cascadedConfig.token); + should.exist(cascadedConfig.recreateClosed); + }); + }); +}); diff --git a/test/helpers/package-json.js b/test/helpers/package-json.js index 651a7113f0aa6098efdb1f56e43bbcbc9cab5275..24e6ca6f335621aa8350c0029c4e1541668df7de 100644 --- a/test/helpers/package-json.js +++ b/test/helpers/package-json.js @@ -1,4 +1,3 @@ -const expect = require('chai').expect; const fs = require('fs'); const packageJson = require('../../lib/helpers/package-json'); @@ -33,19 +32,19 @@ describe('helpers/package-json', () => { const outputContent = fs.readFileSync('./test/_fixtures/package.json/outputs/011.json', 'utf8'); const testContent = packageJson.setNewValue(input01Content, 'dependencies', 'cheerio', '0.22.1'); - expect(testContent).to.equal(outputContent); + testContent.should.equal(outputContent); }); it('replaces only the first instance of a value', () => { const outputContent = fs.readFileSync('./test/_fixtures/package.json/outputs/012.json', 'utf8'); const testContent = packageJson.setNewValue(input01Content, 'devDependencies', 'angular-touch', '1.6.1'); - expect(testContent).to.equal(outputContent); + testContent.should.equal(outputContent); }); it('replaces only the second instance of a value', () => { const outputContent = fs.readFileSync('./test/_fixtures/package.json/outputs/013.json', 'utf8'); const testContent = packageJson.setNewValue(input01Content, 'devDependencies', 'angular-sanitize', '1.6.1'); - expect(testContent).to.equal(outputContent); + testContent.should.equal(outputContent); }); }); }); diff --git a/test/helpers/versions.js b/test/helpers/versions.js index 8c639cdb4a43e8b78b8c169d943fb755337a4afb..318c27fb0383f7acb5cb6c8e2b79020350ee3e18 100644 --- a/test/helpers/versions.js +++ b/test/helpers/versions.js @@ -1,10 +1,6 @@ -const chai = require('chai'); const versionsHelper = require('../../lib/helpers/versions'); - -chai.should(); - const qJson = require('../_fixtures/npm/01.json'); -const defaultConfig = require('../../lib/config/default'); +const defaultConfig = require('../../lib/config/defaults').getConfig(); describe('helpers/versions', () => { describe('.determineUpgrades(dep, currentVersion, defaultConfig)', () => {