diff --git a/lib/api/github.js b/lib/api/github.js index 7fba3274cf65ddf1a655dc817cef49a28a7e0f2b..e78039155f41e9aeb38958c8f02ca53d596b8cfc 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -185,7 +185,6 @@ async function branchExists(branchName) { `repos/${config.repoName}/git/refs/heads/${branchName}` ); if (res.statusCode === 200) { - logger.debug(JSON.stringify(res.body)); if (Array.isArray(res.body)) { // This seems to happen if GitHub has partial matches, so we check ref const matchedBranch = res.body.some( diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 74ae5c47b761d8f039448cf3fcdee06eaaa8f476..66fffab66a59465f42c5d2b98ff0a92e8f0e5d83 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -105,6 +105,22 @@ const options = [ type: 'list', default: ['dependencies', 'devDependencies', 'optionalDependencies'], }, + // depType + { + name: 'ignoreDeps', + description: 'Dependencies to ignore', + type: 'list', + level: 'depType', + }, + { + name: 'packages', + description: 'Package Rules', + type: 'list', + level: 'depType', + cli: false, + env: false, + onboarding: false, + }, // Version behaviour { name: 'pinVersions', @@ -117,11 +133,6 @@ const options = [ 'If set to false, it will upgrade dependencies to latest release only, and not separate major/minor branches', type: 'boolean', }, - { - name: 'ignoreDeps', - description: 'Dependencies to ignore', - type: 'list', - }, { name: 'ignoreFuture', description: 'Ignore versions tagged as "future"', diff --git a/lib/config/index.js b/lib/config/index.js index a6a04ed1cb6ee34da6353306d6fdbb35b604f41e..dc8c3368a5194502a9e388f3548f6b57df092203 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -13,8 +13,7 @@ const githubAppHelper = require('../helpers/github-app'); module.exports = { parseConfigs, - getRepositoryConfig, - getPackageFileConfig, + filterConfig, }; async function parseConfigs(env, argv) { @@ -117,47 +116,21 @@ async function parseConfigs(env, argv) { return config; } -function getRepositoryConfig(globalConfig, index) { - let repository = globalConfig.repositories[index]; - if (typeof repository === 'string') { - repository = { repository }; - } - const repoConfig = Object.assign({}, globalConfig, repository); - for (const option of definitions.getOptions()) { - if (option.level === 'global') { - delete repoConfig[option.name]; - } - } - repoConfig.logger = logger.child({ - repository: repoConfig.repository, - }); - return repoConfig; -} - -function getPackageFileConfig(repoConfig, index) { - let packageFile = repoConfig.packageFiles[index]; - if (typeof packageFile === 'string') { - packageFile = { packageFile }; - } else if (packageFile.fileName) { - // Retained deprecated 'fileName' for backwards compatibility - // TODO: Remove in renovate 9 - packageFile.packageFile = packageFile.fileName; - delete packageFile.fileName; - } - const packageFileConfig = Object.assign({}, repoConfig, packageFile); +function filterConfig(inputConfig, filterLevel) { + const outputConfig = Object.assign({}, inputConfig); + const levelScores = { + package: 10, + depType: 20, + packageFile: 30, + repository: 40, + global: 50, + }; + const threshold = levelScores[filterLevel]; for (const option of definitions.getOptions()) { - if (option.level === 'repository') { - delete packageFileConfig[option.name]; + const optionScore = levelScores[option.level] || 0; + if (optionScore > threshold) { + delete outputConfig[option.name]; } } - repoConfig.logger.trace({ config: repoConfig }, 'repoConfig'); - packageFileConfig.logger = logger.child({ - repository: packageFileConfig.repository, - packageFile: packageFileConfig.packageFile, - }); - packageFileConfig.logger.trace( - { config: packageFileConfig }, - 'packageFileConfig' - ); - return packageFileConfig; + return outputConfig; } diff --git a/lib/helpers/github-app.js b/lib/helpers/github-app.js index a03a83518d9c7ececbf9e18c5be6fcf2247c42bb..aedfa32110f168f3dd1166ac192828557fc8ede0 100644 --- a/lib/helpers/github-app.js +++ b/lib/helpers/github-app.js @@ -52,7 +52,7 @@ async function getRepositories(config) { const installations = await ghApi.getInstallations(appToken); logger.info(`Found installations for ${installations.length} users`); for (const installation of installations) { - logger.debug(JSON.stringify(installation)); + logger.debug(`installation=${JSON.stringify(installation)}`); let installationRepos = await module.exports.getUserRepositories( appToken, installation.id diff --git a/lib/helpers/logger/pretty-stdout.js b/lib/helpers/logger/pretty-stdout.js index 9679d1bb2d722b611844e76798a17a891f4c970d..7fad24e171d25116af6d6c16bd9bc4998ebfc6ea 100644 --- a/lib/helpers/logger/pretty-stdout.js +++ b/lib/helpers/logger/pretty-stdout.js @@ -27,6 +27,7 @@ function getMeta(rec) { const metaFields = [ 'repository', 'packageFile', + 'depType', 'dependency', 'branch', ].filter(elem => rec[elem]); diff --git a/lib/helpers/package-json.js b/lib/helpers/package-json.js index a4ad33dc5eb929b6405782c77c56748e55ff4ff3..c9f1711edb5e2ce902b3fefe4cd7363932c4a407 100644 --- a/lib/helpers/package-json.js +++ b/lib/helpers/package-json.js @@ -1,28 +1,9 @@ const _ = require('lodash'); module.exports = { - extractDependencies, setNewValue, }; -// Returns an array of current dependencies -function extractDependencies(packageJson, sections) { - // loop through dependency types - return sections.reduce((allDeps, depType) => { - // loop through each dependency within a type - const depNames = packageJson[depType] - ? Object.keys(packageJson[depType]) - : []; - return allDeps.concat( - depNames.map(depName => ({ - depType, - depName, - currentVersion: packageJson[depType][depName].trim(), - })) - ); - }, []); -} - function setNewValue(currentFileContent, depType, depName, newVersion, logger) { logger.debug(`setNewValue: ${depType}.${depName} = ${newVersion}`); const parsedContents = JSON.parse(currentFileContent); diff --git a/lib/workers/branch.js b/lib/workers/branch.js index 691d0c1a27b59761310bd763f01fca3fb5ad93af..bd5cf53138820fe3201145bde84cbd12d1591c44 100644 --- a/lib/workers/branch.js +++ b/lib/workers/branch.js @@ -15,10 +15,10 @@ module.exports = { async function getParentBranch(branchName, config) { // Check if branch exists if ((await config.api.branchExists(branchName)) === false) { - logger.info(`Branch ${branchName} needs creating`); + logger.info(`Branch needs creating`); return undefined; } - logger.info(`Branch ${branchName} already exists`); + logger.info(`Branch already exists`); // Check if needs rebasing if ( config.rebaseStalePrs || @@ -26,7 +26,7 @@ async function getParentBranch(branchName, config) { ) { const isBranchStale = await config.api.isBranchStale(branchName); if (isBranchStale) { - logger.info(`Branch ${branchName} is stale and needs rebasing`); + logger.info(`Branch is stale and needs rebasing`); return undefined; } } @@ -35,7 +35,7 @@ async function getParentBranch(branchName, config) { const pr = await config.api.getBranchPr(branchName); // Decide if we need to rebase if (!pr) { - logger.debug(`No PR found for ${branchName}`); + logger.debug(`No PR found`); // We can't tell if this branch can be rebased so better not return branchName; } @@ -53,9 +53,9 @@ async function getParentBranch(branchName, config) { } } // Don't do anything different, but warn - logger.warn(`Branch ${branchName} is not mergeable but can't be rebased`); + logger.warn(`Branch is not mergeable but can't be rebased`); } - logger.debug(`Branch ${branchName} does not need rebasing`); + logger.debug(`Branch does not need rebasing`); return branchName; } @@ -158,7 +158,7 @@ async function ensureBranch(upgrades) { } } if (commitFiles.length) { - logger.debug(`Commit ${commitFiles.length} files to branch ${branchName}`); + logger.debug(`${commitFiles.length} file(s) to commit`); // API will know whether to create new branch or not await api.commitFilesToBranch( branchName, @@ -167,7 +167,7 @@ async function ensureBranch(upgrades) { parentBranch ); } else { - logger.debug(`No files to commit to branch ${branchName}`); + logger.debug(`No files to commit`); } if (!api.branchExists(branchName)) { // Return now if no branch exists @@ -181,18 +181,16 @@ async function ensureBranch(upgrades) { logger.debug('Checking if we can automerge branch'); const branchStatus = await api.getBranchStatus(branchName); if (branchStatus === 'success') { - logger.info(`Automerging branch ${branchName}`); + logger.info(`Automerging branch`); try { await api.mergeBranch(branchName, config.automergeType); } catch (err) { - logger.error(`Failed to automerge branch ${branchName}`); + logger.error(`Failed to automerge branch`); logger.debug(JSON.stringify(err)); throw err; } } else { - logger.debug( - `Branch status is "${branchStatus}" - skipping branch automerge` - ); + logger.debug(`Branch status is "${branchStatus}" - skipping automerge`); } // Return true as branch exists return true; @@ -211,7 +209,7 @@ async function updateBranch(upgrades) { }); logger.info( - `Branch '${branchName}' has ${upgrades.length} upgrade(s): ${upgrades.map( + `Branch has ${upgrades.length} upgrade(s): ${upgrades.map( upgrade => upgrade.depName )}` ); @@ -223,9 +221,7 @@ async function updateBranch(upgrades) { !upgrade0.recreateClosed && (await upgrade0.api.checkForClosedPr(branchName, prTitle)) ) { - logger.info( - `Skipping ${branchName} upgrade as matching closed PR already existed` - ); + logger.info(`Skipping branch as matching closed PR already existed`); return; } const branchCreated = await module.exports.ensureBranch(upgrades); @@ -236,7 +232,7 @@ async function updateBranch(upgrades) { } } } catch (error) { - logger.error(`Error updating branch ${branchName}: ${error}`); + logger.error(`Error updating branch: ${error}`); // Don't throw here - we don't want to stop the other renovations } } diff --git a/lib/workers/dep-type/index.js b/lib/workers/dep-type/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3f3be63c9b0629eae3e40e655a16b4c439525efa --- /dev/null +++ b/lib/workers/dep-type/index.js @@ -0,0 +1,76 @@ +const configParser = require('../../config'); +const pkgWorker = require('../package'); +const packageJson = require('./package-json'); +let logger = require('../../helpers/logger'); + +module.exports = { + findUpgrades, + getDepConfig, +}; + +async function findUpgrades(packageContent, config) { + logger = config.logger || logger; + logger.trace( + `findUpgrades(${JSON.stringify(packageContent)}, ${JSON.stringify(config)})` + ); + // Extract all dependencies from the package.json + const currentDeps = await packageJson.extractDependencies( + packageContent, + config.depType + ); + if (currentDeps.length === 0) { + return []; + } + logger.debug(`currentDeps=${JSON.stringify(currentDeps)}`); + // Filter out ignored dependencies + const filteredDeps = currentDeps.filter( + dependency => config.ignoreDeps.indexOf(dependency.depName) === -1 + ); + logger.debug(`filteredDeps=${JSON.stringify(filteredDeps)}`); + // Obtain full config for each dependency + const depConfigs = filteredDeps.map(dep => + module.exports.getDepConfig(config, dep) + ); + logger.debug(`depConfigs=${JSON.stringify(depConfigs)}`); + // findUpgrades can return more than one upgrade each + const pkgWorkers = depConfigs.map(depConfig => + pkgWorker.findUpgrades(depConfig) + ); + // Use Promise.all to execute npm queries in parallel + const allUpgrades = await Promise.all(pkgWorkers); + logger.debug(`allUpgrades=${JSON.stringify(allUpgrades)}`); + // Squash arrays into one + const combinedUpgrades = [].concat(...allUpgrades); + logger.debug(`combinedUpgrades=${JSON.stringify(combinedUpgrades)}`); + return combinedUpgrades; +} + +function getDepConfig(depTypeConfig, dep) { + const depConfig = Object.assign({}, depTypeConfig, dep); + // Apply any matching package rules + if (depConfig.packages) { + let packageRuleApplied = false; + depConfig.packages.forEach(packageConfig => { + // Apply at most 1 package fule + if (!packageRuleApplied) { + const pattern = + packageConfig.packagePattern || `^${packageConfig.packageName}$`; + const packageRegex = new RegExp(pattern); + if (depConfig.depName.match(packageRegex)) { + packageRuleApplied = true; + // Package rule config overrides any existing config + Object.assign(depConfig, packageConfig); + delete depConfig.packageName; + delete depConfig.packagePattern; + } + } + }); + } + depConfig.logger = logger.child({ + repository: depConfig.repository, + packageFile: depConfig.packageFile, + depType: depConfig.depType, + dependency: depConfig.depName, + }); + return configParser.filterConfig(depConfig, 'package'); +} diff --git a/lib/workers/dep-type/package-json.js b/lib/workers/dep-type/package-json.js new file mode 100644 index 0000000000000000000000000000000000000000..f469194eac419220844542c50beb6e6158659e23 --- /dev/null +++ b/lib/workers/dep-type/package-json.js @@ -0,0 +1,14 @@ +module.exports = { + extractDependencies, +}; + +function extractDependencies(packageJson, depType) { + const depNames = packageJson[depType] + ? Object.keys(packageJson[depType]) + : []; + return depNames.map(depName => ({ + depType, + depName, + currentVersion: packageJson[depType][depName].trim(), + })); +} diff --git a/lib/workers/global.js b/lib/workers/global.js index a55382ee044b07bef226599da2a7ccc95a32b45e..058593989c7f1f3e29b48e9135da9216bb3e210f 100644 --- a/lib/workers/global.js +++ b/lib/workers/global.js @@ -4,6 +4,7 @@ const repositoryWorker = require('./repository'); module.exports = { start, + getRepositoryConfig, }; async function start() { @@ -12,7 +13,7 @@ async function start() { const config = await configParser.parseConfigs(process.env, process.argv); // Iterate through repositories sequentially for (let index = 0; index < config.repositories.length; index += 1) { - const repoConfig = configParser.getRepositoryConfig(config, index); + const repoConfig = module.exports.getRepositoryConfig(config, index); repoConfig.logger.info('Renovating repository'); await repositoryWorker.renovateRepository(repoConfig); repoConfig.logger.info('Finished repository'); @@ -23,3 +24,15 @@ async function start() { logger.error(err); } } + +function getRepositoryConfig(globalConfig, index) { + let repository = globalConfig.repositories[index]; + if (typeof repository === 'string') { + repository = { repository }; + } + const repoConfig = Object.assign({}, globalConfig, repository); + repoConfig.logger = logger.child({ + repository: repoConfig.repository, + }); + return configParser.filterConfig(repoConfig, 'repository'); +} diff --git a/lib/workers/package-file.js b/lib/workers/package-file.js deleted file mode 100644 index 774b20139c4d94881d3f419b8e2de57595d0e37c..0000000000000000000000000000000000000000 --- a/lib/workers/package-file.js +++ /dev/null @@ -1,178 +0,0 @@ -// API -const npmApi = require('../api/npm'); -// Helpers -const packageJson = require('../helpers/package-json'); -const versionsHelper = require('../helpers/versions'); - -let logger = require('../helpers/logger'); - -module.exports = { - processPackageFile, - assignDepConfigs, - findUpgrades, - getDepTypeConfig, -}; - -// This function manages the queue per-package file -async function processPackageFile(config) { - // Initialize logger - logger = config.logger; - - logger.info(`Processing package file`); - - const packageContent = await config.api.getFileJson(config.packageFile); - - if (!packageContent) { - logger.warn('No package.json content found - skipping'); - return []; - } - - // Check for renovate config inside the package.json - if (packageContent.renovate) { - logger.debug( - { config: packageContent.renovate }, - 'package.json>renovate config' - ); - Object.assign(config, packageContent.renovate, { - renovateJsonPresent: true, - }); - } - // Now check if config is disabled - if (config.enabled === false) { - logger.info('Config is disabled. Skipping'); - return []; - } - - const depTypes = config.depTypes.map(depType => { - if (typeof depType === 'string') { - return depType; - } - return depType.depType; - }); - - // Extract all dependencies from the package.json - let dependencies = await packageJson.extractDependencies( - packageContent, - depTypes - ); - // Filter out ignored dependencies - dependencies = dependencies.filter( - dependency => config.ignoreDeps.indexOf(dependency.depName) === -1 - ); - dependencies = module.exports.assignDepConfigs(config, dependencies); - // Find all upgrades for remaining dependencies - const upgrades = await module.exports.findUpgrades(dependencies); - // Process all upgrades sequentially - if (config.maintainYarnLock) { - const upgrade = Object.assign({}, config, { - upgradeType: 'maintainYarnLock', - }); - upgrade.upgradeType = 'maintainYarnLock'; - upgrade.commitMessage = upgrade.yarnMaintenanceCommitMessage; - upgrade.branchName = upgrade.yarnMaintenanceBranchName; - upgrade.prTitle = upgrade.yarnMaintenancePrTitle; - upgrade.prBody = upgrade.yarnMaintenancePrBody; - upgrades.push(upgrade); - } - logger.info('Finished processing package file'); - return upgrades; -} - -// Add custom config for each dep -function assignDepConfigs(inputConfig, deps) { - return deps.map(dep => { - const returnDep = Object.assign({}, dep); - returnDep.config = Object.assign( - {}, - inputConfig, - getDepTypeConfig(inputConfig.depTypes, dep.depType) - ); - let packageRuleApplied = false; - if (returnDep.config.packages) { - // Loop through list looking for match - // Exit after first match - returnDep.config.packages.forEach(packageConfig => { - if (!packageRuleApplied) { - const pattern = - packageConfig.packagePattern || `^${packageConfig.packageName}$`; - const packageRegex = new RegExp(pattern); - if (dep.depName.match(packageRegex)) { - packageRuleApplied = true; - Object.assign(returnDep.config, packageConfig); - delete returnDep.config.packageName; - delete returnDep.config.packagePattern; - } - } - }); - } - // TODO: clean this up - delete returnDep.config.depType; - delete returnDep.config.depTypes; - delete returnDep.config.enabled; - delete returnDep.config.onboarding; - delete returnDep.config.endpoint; - delete returnDep.config.autodiscover; - delete returnDep.config.token; - delete returnDep.config.githubAppId; - delete returnDep.config.githubAppKey; - delete returnDep.config.packageFiles; - delete returnDep.config.logLevel; - delete returnDep.config.renovateJsonPresent; - delete returnDep.config.ignoreDeps; - delete returnDep.config.packages; - delete returnDep.config.maintainYarnLock; - delete returnDep.config.yarnMaintenanceBranchName; - delete returnDep.config.yarnMaintenanceCommitMessage; - delete returnDep.config.yarnMaintenancePrTitle; - delete returnDep.config.yarnMaintenancePrBody; - return returnDep; - }); -} - -async function findUpgrades(dependencies) { - const allUpgrades = []; - // findDepUpgrades can add more than one upgrade to allUpgrades - async function findDepUpgrades(dep) { - const npmDependency = await npmApi.getDependency(dep.depName, logger); - if (!npmDependency) { - // If dependency lookup fails then ignore it and keep going - return; - } - const upgrades = await versionsHelper.determineUpgrades( - npmDependency, - dep.currentVersion, - dep.config - ); - if (upgrades.length > 0) { - logger.info( - `Dependency ${dep.depName} has ${upgrades.length} upgrade(s) available: ${upgrades.map( - upgrade => upgrade.newVersion - )}` - ); - upgrades.forEach(upgrade => { - const upgradeObj = Object.assign({}, dep, dep.config, upgrade); - delete upgradeObj.config; - allUpgrades.push(upgradeObj); - }); - } else { - logger.debug(`${dep.depName}: No upgrades required`); - } - } - const promiseArray = dependencies.map(dep => findDepUpgrades(dep)); - // Use Promise.all to execute npm queries in parallel - await Promise.all(promiseArray); - // Return the upgrade array once all Promises are complete - return allUpgrades; -} - -function getDepTypeConfig(depTypes, depTypeName) { - let depTypeConfig = {}; - if (depTypes) { - depTypes.forEach(depType => { - if (typeof depType !== 'string' && depType.depType === depTypeName) { - depTypeConfig = depType; - } - }); - } - return depTypeConfig; -} diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ff276804132431f12093333f9d5835b3fcf9dd65 --- /dev/null +++ b/lib/workers/package-file/index.js @@ -0,0 +1,72 @@ +const configParser = require('../../config'); +const depTypeWorker = require('../dep-type'); +let logger = require('../../helpers/logger'); + +module.exports = { + findUpgrades, + getDepTypeConfig, +}; + +async function findUpgrades(config) { + logger = config.logger || logger; + logger.info(`Processing package file`); + const packageContent = await config.api.getFileJson(config.packageFile); + + if (!packageContent) { + logger.warn('No package.json content found - skipping'); + return []; + } + + // Check for renovate config inside the package.json + if (packageContent.renovate) { + logger.debug( + { config: packageContent.renovate }, + 'package.json>renovate config' + ); + // package.json>renovate config takes precedence over existing config + Object.assign(config, packageContent.renovate); + } else { + logger.debug('Package file has no renovate configuration'); + } + // Now check if config is disabled + if (config.enabled === false) { + logger.info('Config is disabled. Skipping'); + return []; + } + + const depTypeConfigs = config.depTypes.map(depType => + module.exports.getDepTypeConfig(config, depType) + ); + logger.debug(`depTypeConfigs=${JSON.stringify(depTypeConfigs)}`); + let upgrades = []; + for (const depTypeConfig of depTypeConfigs) { + upgrades = upgrades.concat( + await depTypeWorker.findUpgrades(packageContent, depTypeConfig) + ); + } + + if (config.maintainYarnLock) { + const upgrade = Object.assign({}, config, { + upgradeType: 'maintainYarnLock', + }); + upgrade.upgradeType = 'maintainYarnLock'; + upgrade.commitMessage = upgrade.yarnMaintenanceCommitMessage; + upgrade.branchName = upgrade.yarnMaintenanceBranchName; + upgrade.prTitle = upgrade.yarnMaintenancePrTitle; + upgrade.prBody = upgrade.yarnMaintenancePrBody; + upgrades.push(upgrade); + } + logger.info('Finished processing package file'); + return upgrades; +} + +function getDepTypeConfig(packageFileConfig, depType) { + let depTypeConfig = typeof depType === 'string' ? { depType } : depType; + depTypeConfig = Object.assign({}, packageFileConfig, depTypeConfig); + depTypeConfig.logger = logger.child({ + repository: depTypeConfig.repository, + packageFile: depTypeConfig.packageFile, + depType: depTypeConfig.depType, + }); + return configParser.filterConfig(depTypeConfig, 'depType'); +} diff --git a/lib/workers/package/index.js b/lib/workers/package/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6d065c377a8404cd1baf8206a303feb015a3b3ab --- /dev/null +++ b/lib/workers/package/index.js @@ -0,0 +1,32 @@ +const npmApi = require('../../api/npm'); +const versions = require('./versions'); + +let logger = require('../../helpers/logger'); + +module.exports = { + findUpgrades, +}; + +// Returns all upgrades for a given dependency config +async function findUpgrades(config) { + logger = config.logger || logger; + const npmDep = await npmApi.getDependency(config.depName, logger); + // If dependency lookup fails then warn and return + if (!npmDep) { + logger.warn(`Failed to look up dependency ${config.depName}`); + return []; + } + const upgrades = await versions.determineUpgrades(npmDep, config); + if (upgrades.length > 0) { + logger.info( + { dependency: config.depName }, + `${upgrades.length} upgrade(s) available: ${upgrades.map( + upgrade => upgrade.newVersion + )}` + ); + } else { + logger.debug(`${config.depName}: No upgrades required`); + } + // Flatten the config on top of upgrade + return upgrades.map(upgrade => Object.assign({}, config, upgrade)); +} diff --git a/lib/helpers/versions.js b/lib/workers/package/versions.js similarity index 91% rename from lib/helpers/versions.js rename to lib/workers/package/versions.js index 76c2da4836c0cea04fce9f4c79aa491f27084c0b..f6f226a562890a2c59342c80f0b771c6a14e80e9 100644 --- a/lib/helpers/versions.js +++ b/lib/workers/package/versions.js @@ -1,4 +1,4 @@ -const logger = require('../helpers/logger'); +const logger = require('../../helpers/logger'); const semver = require('semver'); const stable = require('semver-stable'); const _ = require('lodash'); @@ -12,14 +12,15 @@ module.exports = { isPastLatest, }; -function determineUpgrades(dep, currentVersion, config) { +function determineUpgrades(npmDep, config) { + const currentVersion = config.currentVersion; if (!isValidVersion(currentVersion)) { - logger.warn(`${dep.name} currentVersion ${currentVersion} is invalid`); + logger.warn(`${npmDep.name} currentVersion ${currentVersion} is invalid`); return []; } - const versions = dep.versions; + const versions = npmDep.versions; if (!versions || Object.keys(versions).length === 0) { - logger.warn(`${dep.name} - no versions`); + logger.warn(`${npmDep.name} - no versions`); return []; } const versionList = Object.keys(versions); @@ -59,8 +60,8 @@ function determineUpgrades(dep, currentVersion, config) { .reject( version => config.respectLatest && - isPastLatest(dep, version) && - !isPastLatest(dep, changeLogFromVersion) + isPastLatest(npmDep, version) && + !isPastLatest(npmDep, changeLogFromVersion) ) // Loop through all possible versions .forEach(newVersion => { @@ -187,10 +188,10 @@ function isFuture(version) { ); } -function isPastLatest(dep, version) { - if (dep['dist-tags'] && dep['dist-tags'].latest) { - return semver.gt(version, dep['dist-tags'].latest); +function isPastLatest(npmDep, version) { + if (npmDep['dist-tags'] && npmDep['dist-tags'].latest) { + return semver.gt(version, npmDep['dist-tags'].latest); } - logger.warn(`No dist-tags.latest for ${dep.name}`); + logger.warn(`No dist-tags.latest for ${npmDep.name}`); return false; } diff --git a/lib/workers/pr.js b/lib/workers/pr.js index d03d72f4437c84e516ae6c920ede9ce71070bda7..e1b8a448302d0dc09b6ad8c171108398c7faeb3c 100644 --- a/lib/workers/pr.js +++ b/lib/workers/pr.js @@ -149,7 +149,6 @@ async function checkAutoMerge(pr, config, logger) { logger.debug(`Checking #${pr.number} for automerge`); if (config.automergeEnabled && config.automergeType === 'pr') { logger.info('PR is configured for automerge'); - logger.debug(JSON.stringify(pr)); // Return if PR not ready for automerge if (pr.mergeable !== true || pr.mergeable_state === 'unstable') { logger.info('PR is not ready for merge'); diff --git a/lib/workers/repository/upgrades.js b/lib/workers/repository/upgrades.js index e9e52b77882f302008d029ff2dccbae62bc4c271..87bbec075cc5e016182f32d2c525226188185b9b 100644 --- a/lib/workers/repository/upgrades.js +++ b/lib/workers/repository/upgrades.js @@ -5,6 +5,7 @@ const packageFileWorker = require('../package-file'); module.exports = { determineRepoUpgrades, groupUpgradesByBranch, + getPackageFileConfig, }; async function determineRepoUpgrades(config) { @@ -15,12 +16,14 @@ async function determineRepoUpgrades(config) { let upgrades = []; // Iterate through repositories sequentially for (let index = 0; index < config.packageFiles.length; index += 1) { - const packageFileConfig = configParser.getPackageFileConfig(config, index); - packageFileConfig.logger.info('Renovating package file'); + const packageFileConfig = module.exports.getPackageFileConfig( + config, + index + ); + packageFileConfig.logger.info('Processing package file'); upgrades = upgrades.concat( - await packageFileWorker.processPackageFile(packageFileConfig) + await packageFileWorker.findUpgrades(packageFileConfig) ); - packageFileConfig.logger.info('Finished repository'); } return upgrades; } @@ -56,3 +59,26 @@ async function groupUpgradesByBranch(upgrades, logger) { logger.debug(`Returning ${Object.keys(branchUpgrades).length} branch(es)`); return branchUpgrades; } + +function getPackageFileConfig(repoConfig, index) { + let packageFile = repoConfig.packageFiles[index]; + if (typeof packageFile === 'string') { + packageFile = { packageFile }; + } else if (packageFile.fileName) { + // Retained deprecated 'fileName' for backwards compatibility + // TODO: Remove in renovate 9 + packageFile.packageFile = packageFile.fileName; + delete packageFile.fileName; + } + const packageFileConfig = Object.assign({}, repoConfig, packageFile); + repoConfig.logger.trace({ config: repoConfig }, 'repoConfig'); + packageFileConfig.logger = packageFileConfig.logger.child({ + repository: packageFileConfig.repository, + packageFile: packageFileConfig.packageFile, + }); + packageFileConfig.logger.trace( + { config: packageFileConfig }, + 'packageFileConfig' + ); + return configParser.filterConfig(packageFileConfig, 'packageFile'); +} diff --git a/test/config/__snapshots__/index.spec.js.snap b/test/config/__snapshots__/index.spec.js.snap deleted file mode 100644 index 5bc93770e556e47b65d868c523a39c1f561f32ad..0000000000000000000000000000000000000000 --- a/test/config/__snapshots__/index.spec.js.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`config/index .(config, index) handles object repos 1`] = ` -Object { - "foo": "bar", - "maintainYarnLock": true, - "repoField": "g", - "repository": "e/f", -} -`; - -exports[`config/index .(config, index) massages string repos 1`] = ` -Object { - "foo": "bar", - "maintainYarnLock": true, - "repository": "c/d", -} -`; diff --git a/test/config/index.spec.js b/test/config/index.spec.js index 3efd7b517719b9bf5398bd53a9961c1da31ead57..31cc0fb07a4914ac732275dc9bda950429b2c88d 100644 --- a/test/config/index.spec.js +++ b/test/config/index.spec.js @@ -160,38 +160,4 @@ describe('config/index', () => { expect(glGot.mock.calls.length).toBe(0); }); }); - describe('.(config, index)', () => { - let configParser; - beforeEach(() => { - configParser = require('../../lib/config/index.js'); - }); - const config = { - githubAppKey: 'foo', - maintainYarnLock: true, - foo: 'bar', - repositories: [ - 'c/d', - { - repository: 'e/f', - repoField: 'g', - }, - ], - }; - it('massages string repos', () => { - const res = configParser.getRepositoryConfig(config, 0); - expect(res.githubAppKey).not.toBeDefined(); - expect(res.maintainYarnLock).toBeDefined(); - expect(res.logger).toBeDefined(); - delete res.logger; - expect(res).toMatchSnapshot(); - }); - it('handles object repos', () => { - const res = configParser.getRepositoryConfig(config, 1); - expect(res.githubAppKey).not.toBeDefined(); - expect(res.maintainYarnLock).toBeDefined(); - expect(res.logger).toBeDefined(); - delete res.logger; - expect(res).toMatchSnapshot(); - }); - }); }); diff --git a/test/helpers/__snapshots__/package-json.spec.js.snap b/test/helpers/__snapshots__/platform.spec.js.snap similarity index 100% rename from test/helpers/__snapshots__/package-json.spec.js.snap rename to test/helpers/__snapshots__/platform.spec.js.snap diff --git a/test/helpers/package-json.spec.js b/test/helpers/package-json.spec.js deleted file mode 100644 index 60fd09ddc51586ea33548fd3c24a1fd6f98af12d..0000000000000000000000000000000000000000 --- a/test/helpers/package-json.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -const platformHelper = require('../../lib/helpers/platform'); - -describe('helpers/platform', () => { - describe('getApi(platform)', () => { - it('returns github', () => { - platformHelper.getApi('github'); - }); - it('returns gitlab', () => { - platformHelper.getApi('gitlab'); - }); - it('throws error', () => { - let e; - try { - platformHelper.getApi('foo'); - } catch (err) { - e = err; - } - expect(e).toMatchSnapshot(); - }); - }); -}); diff --git a/test/helpers/platform-json.spec.js b/test/helpers/platform-json.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..793cbbacb6e73da1da919a1ab5d88a6682e48317 --- /dev/null +++ b/test/helpers/platform-json.spec.js @@ -0,0 +1,61 @@ +const fs = require('fs'); +const path = require('path'); +const packageJson = require('../../lib/helpers/package-json'); +const logger = require('../_fixtures/logger'); + +function readFixture(fixture) { + return fs.readFileSync( + path.resolve(__dirname, `../_fixtures/package-json/${fixture}`), + 'utf8' + ); +} + +const input01Content = readFixture('inputs/01.json'); + +describe('helpers/package-json', () => { + describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => { + it('replaces a dependency value', () => { + const outputContent = readFixture('outputs/011.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'dependencies', + 'cheerio', + '0.22.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('replaces only the first instance of a value', () => { + const outputContent = readFixture('outputs/012.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-touch', + '1.6.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('replaces only the second instance of a value', () => { + const outputContent = readFixture('outputs/013.json'); + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-sanitize', + '1.6.1', + logger + ); + testContent.should.equal(outputContent); + }); + it('handles the case where the desired version is already supported', () => { + const testContent = packageJson.setNewValue( + input01Content, + 'devDependencies', + 'angular-touch', + '1.5.8', + logger + ); + testContent.should.equal(input01Content); + }); + }); +}); diff --git a/test/helpers/platform.spec.js b/test/helpers/platform.spec.js index 8b71735a81917a06ad75fd4a45377e9c79c2c626..60fd09ddc51586ea33548fd3c24a1fd6f98af12d 100644 --- a/test/helpers/platform.spec.js +++ b/test/helpers/platform.spec.js @@ -1,95 +1,21 @@ -const fs = require('fs'); -const path = require('path'); -const packageJson = require('../../lib/helpers/package-json'); -const logger = require('../_fixtures/logger'); +const platformHelper = require('../../lib/helpers/platform'); -const defaultTypes = [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', -]; - -function readFixture(fixture) { - return fs.readFileSync( - path.resolve(__dirname, `../_fixtures/package-json/${fixture}`), - 'utf8' - ); -} - -const input01Content = readFixture('inputs/01.json'); -const input02Content = readFixture('inputs/02.json'); - -describe('helpers/package-json', () => { - describe('.extractDependencies(packageJson, sections)', () => { - it('returns an array of correct length', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input01Content), - defaultTypes - ); - extractedDependencies.should.be.instanceof(Array); - extractedDependencies.should.have.length(10); - }); - it('each element contains non-null depType, depName, currentVersion', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input01Content), - defaultTypes - ); - extractedDependencies - .every(dep => dep.depType && dep.depName && dep.currentVersion) - .should.eql(true); - }); - it('supports null devDependencies', () => { - const extractedDependencies = packageJson.extractDependencies( - JSON.parse(input02Content), - defaultTypes - ); - extractedDependencies.should.be.instanceof(Array); - extractedDependencies.should.have.length(6); - }); - }); - describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => { - it('replaces a dependency value', () => { - const outputContent = readFixture('outputs/011.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'dependencies', - 'cheerio', - '0.22.1', - logger - ); - testContent.should.equal(outputContent); - }); - it('replaces only the first instance of a value', () => { - const outputContent = readFixture('outputs/012.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-touch', - '1.6.1', - logger - ); - testContent.should.equal(outputContent); +describe('helpers/platform', () => { + describe('getApi(platform)', () => { + it('returns github', () => { + platformHelper.getApi('github'); }); - it('replaces only the second instance of a value', () => { - const outputContent = readFixture('outputs/013.json'); - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-sanitize', - '1.6.1', - logger - ); - testContent.should.equal(outputContent); + it('returns gitlab', () => { + platformHelper.getApi('gitlab'); }); - it('handles the case where the desired version is already supported', () => { - const testContent = packageJson.setNewValue( - input01Content, - 'devDependencies', - 'angular-touch', - '1.5.8', - logger - ); - testContent.should.equal(input01Content); + it('throws error', () => { + let e; + try { + platformHelper.getApi('foo'); + } catch (err) { + e = err; + } + expect(e).toMatchSnapshot(); }); }); }); diff --git a/test/helpers/versions.spec.js b/test/helpers/versions.spec.js deleted file mode 100644 index 7c85dad9bc227af23ed79c1929a550074f9e9456..0000000000000000000000000000000000000000 --- a/test/helpers/versions.spec.js +++ /dev/null @@ -1,286 +0,0 @@ -const versionsHelper = require('../../lib/helpers/versions'); -const qJson = require('../_fixtures/npm/01.json'); -const helmetJson = require('../_fixtures/npm/02.json'); - -let defaultConfig; - -describe('helpers/versions', () => { - beforeEach(() => { - defaultConfig = require('../../lib/config/defaults').getConfig(); - }); - - describe('.determineUpgrades(dep, currentVersion, defaultConfig)', () => { - it('return empty if invalid current version', () => { - versionsHelper - .determineUpgrades(qJson, 'invalid', defaultConfig) - .should.have.length(0); - }); - it('return empty if null versions', () => { - const testDep = { - name: 'q', - }; - versionsHelper - .determineUpgrades(testDep, '1.0.0', defaultConfig) - .should.have.length(0); - }); - it('return empty if empty versions', () => { - const testDep = { - name: 'q', - versions: [], - }; - versionsHelper - .determineUpgrades(testDep, '1.0.0', defaultConfig) - .should.have.length(0); - }); - it('supports minor and major upgrades for tilde ranges', () => { - expect( - versionsHelper.determineUpgrades(qJson, '^0.4.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('returns only one update if grouping', () => { - defaultConfig.groupName = 'somegroup'; - expect( - versionsHelper.determineUpgrades(qJson, '^0.4.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('returns only one update if automerging any', () => { - defaultConfig.automerge = 'any'; - expect( - versionsHelper.determineUpgrades(qJson, '^0.4.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('returns both updates if automerging minor', () => { - defaultConfig.automerge = 'minor'; - expect( - versionsHelper.determineUpgrades(qJson, '^0.4.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('disables major release separation (major)', () => { - const config = Object.assign({}, defaultConfig, { - separateMajorReleases: false, - }); - expect( - versionsHelper.determineUpgrades(qJson, '^0.4.0', config) - ).toMatchSnapshot(); - }); - it('disables major release separation (minor)', () => { - const config = Object.assign({}, defaultConfig, { - separateMajorReleases: false, - }); - expect( - versionsHelper.determineUpgrades(qJson, '1.0.0', config) - ).toMatchSnapshot(); - }); - it('supports minor and major upgrades for ranged versions', () => { - expect( - versionsHelper.determineUpgrades(qJson, '~0.4.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('ignores pinning for ranges when other upgrade exists', () => { - expect( - versionsHelper.determineUpgrades(qJson, '~0.9.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('upgrades minor ranged versions', () => { - expect( - versionsHelper.determineUpgrades(qJson, '~1.0.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('pins minor ranged versions', () => { - expect( - versionsHelper.determineUpgrades(qJson, '^1.0.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('ignores minor ranged versions when not pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '^1.0.0', config) - ).toHaveLength(0); - }); - it('upgrades tilde ranges', () => { - expect( - versionsHelper.determineUpgrades(qJson, '~1.3.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('upgrades .x minor ranges', () => { - expect( - versionsHelper.determineUpgrades(qJson, '1.3.x', defaultConfig) - ).toMatchSnapshot(); - }); - it('upgrades tilde ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '~1.3.0', config) - ).toMatchSnapshot(); - }); - it('upgrades .x major ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '0.x', config) - ).toMatchSnapshot(); - }); - it('upgrades .x minor ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '1.3.x', config) - ).toMatchSnapshot(); - }); - it('upgrades shorthand major ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '0', config) - ).toMatchSnapshot(); - }); - it('upgrades shorthand minor ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '1.3', config) - ).toMatchSnapshot(); - }); - it('upgrades multiple tilde ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '~0.7.0', config) - ).toMatchSnapshot(); - }); - it('upgrades multiple caret ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '^0.7.0', config) - ).toMatchSnapshot(); - }); - it('ignores complex ranges when not pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '^0.7.0 || ^0.8.0', config) - ).toHaveLength(0); - }); - it('returns nothing for greater than ranges', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '>= 0.7.0', config) - ).toHaveLength(0); - }); - it('upgrades less than equal ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '<= 0.7.2', config) - ).toMatchSnapshot(); - }); - it('rejects less than ranges without pinning', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(qJson, '< 0.7.2', config) - ).toEqual([]); - }); - it('supports > latest versions if configured', () => { - const config = Object.assign({}, defaultConfig); - config.respectLatest = false; - expect( - versionsHelper.determineUpgrades(qJson, '1.4.1', config) - ).toMatchSnapshot(); - }); - it('supports future versions if configured', () => { - const config = Object.assign({}, defaultConfig); - config.ignoreFuture = false; - config.respectLatest = false; - expect( - versionsHelper.determineUpgrades(qJson, '1.4.1', config) - ).toMatchSnapshot(); - }); - it('supports future versions if already future', () => { - expect( - versionsHelper.determineUpgrades(qJson, '^2.0.0', defaultConfig) - ).toMatchSnapshot(); - }); - it('should ignore unstable versions if the current version is stable', () => { - versionsHelper - .determineUpgrades( - { - name: 'amazing-package', - versions: { - '1.0.0': {}, - '1.1.0-beta': {}, - }, - }, - '1.0.0', - defaultConfig - ) - .should.eql([]); - }); - it('should allow unstable versions if the current version is unstable', () => { - expect( - versionsHelper.determineUpgrades( - { - name: 'amazing-package', - versions: { - '1.0.0-beta': {}, - '1.1.0-beta': {}, - }, - }, - '1.0.0-beta', - defaultConfig - ) - ).toMatchSnapshot(); - }); - it('should treat zero zero tilde ranges as 0.0.x', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(helmetJson, '~0.0.34', config) - ).toEqual([]); - }); - it('should treat zero zero caret ranges as pinned', () => { - const config = Object.assign({}, defaultConfig, { pinVersions: false }); - expect( - versionsHelper.determineUpgrades(helmetJson, '^0.0.34', config) - ).toMatchSnapshot(); - }); - }); - describe('.isRange(input)', () => { - it('rejects simple semver', () => { - versionsHelper.isRange('1.2.3').should.eql(false); - }); - it('accepts tilde', () => { - versionsHelper.isRange('~1.2.3').should.eql(true); - }); - it('accepts caret', () => { - versionsHelper.isRange('^1.2.3').should.eql(true); - }); - }); - describe('.isValidVersion(input)', () => { - it('should support simple semver', () => { - versionsHelper.isValidVersion('1.2.3').should.eql(true); - }); - it('should support versions with dash', () => { - versionsHelper.isValidVersion('1.2.3-foo').should.eql(true); - }); - it('should reject versions without dash', () => { - versionsHelper.isValidVersion('1.2.3foo').should.eql(false); - }); - it('should support ranges', () => { - versionsHelper.isValidVersion('~1.2.3').should.eql(true); - versionsHelper.isValidVersion('^1.2.3').should.eql(true); - versionsHelper.isValidVersion('>1.2.3').should.eql(true); - }); - it('should reject github repositories', () => { - versionsHelper.isValidVersion('singapore/renovate').should.eql(false); - versionsHelper - .isValidVersion('singapore/renovate#master') - .should.eql(false); - versionsHelper - .isValidVersion('https://github.com/singapore/renovate.git') - .should.eql(false); - }); - }); - describe('.isPastLatest(dep, version)', () => { - it('should return false for less than', () => { - versionsHelper.isPastLatest(qJson, '1.0.0').should.eql(false); - }); - it('should return false for equal', () => { - versionsHelper.isPastLatest(qJson, '1.4.1').should.eql(false); - }); - it('should return true for greater than', () => { - versionsHelper.isPastLatest(qJson, '2.0.3').should.eql(true); - }); - }); -}); diff --git a/test/workers/__snapshots__/global.spec.js.snap b/test/workers/__snapshots__/global.spec.js.snap deleted file mode 100644 index 0aaa44f801f7194bb46aab23e002f99b09b99892..0000000000000000000000000000000000000000 --- a/test/workers/__snapshots__/global.spec.js.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`lib/workers/global processes repositories 1`] = ` -Array [ - Array [ - Object { - "foo": 1, - "repositories": Array [ - "a", - "b", - ], - }, - 0, - ], - Array [ - Object { - "foo": 1, - "repositories": Array [ - "a", - "b", - ], - }, - 1, - ], -] -`; diff --git a/test/workers/__snapshots__/package-file.spec.js.snap b/test/workers/__snapshots__/package-file.spec.js.snap deleted file mode 100644 index 0c7271025ec41f4ad7367532ec2004ee9ff07e24..0000000000000000000000000000000000000000 --- a/test/workers/__snapshots__/package-file.spec.js.snap +++ /dev/null @@ -1,215 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles depType config with override 1`] = ` -Array [ - Object { - "config": Object { - "foo": "beta", - }, - "depName": "a", - "depType": "dependencies", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles depType config without override 1`] = ` -Array [ - Object { - "config": Object { - "alpha": "beta", - "foo": "bar", - }, - "depName": "a", - "depType": "dependencies", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles multiple deps 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - }, - "depName": "a", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "b", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles non-regex package name 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "eslint", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "eslint-foo", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "a", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "also-eslint", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles package config 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "renovate", - ], - }, - "depName": "a", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles regex package pattern 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "eslint", - }, - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "eslint-foo", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "a", - }, - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "also-eslint", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles regex wildcard package pattern 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "eslint", - }, - Object { - "config": Object { - "foo": "bar", - "labels": Array [ - "eslint", - ], - }, - "depName": "eslint-foo", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "a", - }, - Object { - "config": Object { - "foo": "bar", - }, - "depName": "also-eslint", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) handles string deps 1`] = ` -Array [ - Object { - "config": Object { - "foo": "bar", - }, - "depName": "a", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) nested package config overrides depType and general config 1`] = ` -Array [ - Object { - "config": Object { - "foo": "gamma", - }, - "depName": "a", - "depType": "dependencies", - }, -] -`; - -exports[`packageFileWorker assignDepConfigs(inputConfig, deps) package config overrides depType and general config 1`] = ` -Array [ - Object { - "config": Object { - "foo": "gamma", - }, - "depName": "a", - "depType": "dependencies", - }, -] -`; - -exports[`packageFileWorker processPackageFile(config) extracts dependencies for each depType 1`] = ` -Array [ - Array [ - Object {}, - Array [ - "dependencies", - "devDependencies", - ], - ], -] -`; - -exports[`packageFileWorker processPackageFile(config) filters dependencies 1`] = ` -Array [ - "a", -] -`; diff --git a/test/workers/dep-type/index.spec.js b/test/workers/dep-type/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..39eaa352d2a431512e32f7b3568a36fa33491ea4 --- /dev/null +++ b/test/workers/dep-type/index.spec.js @@ -0,0 +1,77 @@ +const packageJson = require('../../../lib/workers/dep-type/package-json'); +const pkgWorker = require('../../../lib/workers/package/index'); +const depTypeWorker = require('../../../lib/workers/dep-type/index'); + +const logger = require('../../_fixtures/logger'); + +jest.mock('../../../lib/workers/dep-type/package-json'); +jest.mock('../../../lib/workers/package/index'); + +pkgWorker.findUpgrades = jest.fn(() => ['a']); + +describe('lib/workers/dep-type/index', () => { + describe('findUpgrades(packageContent, config)', () => { + let config; + beforeEach(() => { + config = { + ignoreDeps: ['a', 'b'], + }; + }); + it('returns empty if no deps found', async () => { + packageJson.extractDependencies.mockReturnValueOnce([]); + const res = await depTypeWorker.findUpgrades({}, config); + expect(res).toMatchObject([]); + }); + it('returns empty if all deps are filtered', async () => { + packageJson.extractDependencies.mockReturnValueOnce([ + { depName: 'a' }, + { depName: 'b' }, + ]); + const res = await depTypeWorker.findUpgrades({}, config); + expect(res).toMatchObject([]); + }); + it('returns combined upgrades if all deps are filtered', async () => { + packageJson.extractDependencies.mockReturnValueOnce([ + { depName: 'a' }, + { depName: 'c' }, + { depName: 'd' }, + ]); + const res = await depTypeWorker.findUpgrades({}, config); + expect(res).toHaveLength(2); + }); + }); + describe('getDepConfig(depTypeConfig, dep)', () => { + const depTypeConfig = { + foo: 'bar', + logger, + packages: [ + { + packageName: 'a', + x: 2, + }, + { + packagePattern: 'a', + y: 2, + }, + ], + }; + it('applies only one rule', () => { + const dep = { + depName: 'a', + }; + const res = depTypeWorker.getDepConfig(depTypeConfig, dep); + expect(res.x).toBe(2); + expect(res.y).toBeUndefined(); + expect(res.packages).toBeUndefined(); + }); + it('applies the second rule', () => { + const dep = { + depName: 'abc', + }; + const res = depTypeWorker.getDepConfig(depTypeConfig, dep); + expect(res.x).toBeUndefined(); + expect(res.y).toBe(2); + expect(res.packages).toBeUndefined(); + }); + }); +}); diff --git a/test/workers/dep-type/package-json.spec.js b/test/workers/dep-type/package-json.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ff9fe1531e5ee58983d0f3b248e92b5632cce7b8 --- /dev/null +++ b/test/workers/dep-type/package-json.spec.js @@ -0,0 +1,59 @@ +const fs = require('fs'); +const path = require('path'); +const packageJson = require('../../../lib/workers/dep-type/package-json'); + +function readFixture(fixture) { + return fs.readFileSync( + path.resolve(__dirname, `../../_fixtures/package-json/${fixture}`), + 'utf8' + ); +} + +const input01Content = readFixture('inputs/01.json'); +const input02Content = readFixture('inputs/02.json'); + +describe('helpers/package-json', () => { + describe('.extractDependencies(packageJson, depType)', () => { + it('returns an array of correct length (dependencies)', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input01Content), + 'dependencies' + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(6); + }); + it('returns an array of correct length (devDependencies)', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input01Content), + 'devDependencies' + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(4); + }); + it('each element contains non-null depType, depName, currentVersion', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input01Content), + 'dependencies' + ); + extractedDependencies + .every(dep => dep.depType && dep.depName && dep.currentVersion) + .should.eql(true); + }); + it('supports null devDependencies indirect', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input02Content), + 'dependencies' + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(6); + }); + it('supports null', () => { + const extractedDependencies = packageJson.extractDependencies( + JSON.parse(input02Content), + 'fooDependencies' + ); + extractedDependencies.should.be.instanceof(Array); + extractedDependencies.should.have.length(0); + }); + }); +}); diff --git a/test/workers/global.spec.js b/test/workers/global.spec.js index 5bb78253c1cc8bfad592eb8f1ccca987a8959310..64795391adfc99986eee59c0924ce4cb72265076 100644 --- a/test/workers/global.spec.js +++ b/test/workers/global.spec.js @@ -1,7 +1,6 @@ const globalWorker = require('../../lib/workers/global'); const repositoryWorker = require('../../lib/workers/repository'); const configParser = require('../../lib/config'); -const logger = require('../_fixtures/logger'); describe('lib/workers/global', () => { beforeEach(() => { @@ -21,13 +20,8 @@ describe('lib/workers/global', () => { foo: 1, repositories: ['a', 'b'], }); - configParser.getRepositoryConfig.mockReturnValue({ - repository: 'foo', - logger, - }); await globalWorker.start(); expect(configParser.parseConfigs.mock.calls.length).toBe(1); - expect(configParser.getRepositoryConfig.mock.calls).toMatchSnapshot(); expect(repositoryWorker.renovateRepository.mock.calls.length).toBe(2); }); it('catches errors', async () => { diff --git a/test/workers/package-file.spec.js b/test/workers/package-file.spec.js deleted file mode 100644 index 44d2f05920e8d671ac9ab35294651d210dfa0300..0000000000000000000000000000000000000000 --- a/test/workers/package-file.spec.js +++ /dev/null @@ -1,350 +0,0 @@ -const packageFileWorker = require('../../lib/workers/package-file'); -const npmApi = require('../../lib/api/npm'); -const versionsHelper = require('../../lib/helpers/versions'); -const packageJsonHelper = require('../../lib/helpers/package-json'); -const logger = require('../_fixtures/logger'); - -jest.mock('../../lib/workers/branch'); -jest.mock('../../lib/workers/pr'); -jest.mock('../../lib/api/npm'); -jest.mock('../../lib/helpers/versions'); - -describe('packageFileWorker', () => { - describe('findUpgrades(dependencies, config)', () => { - let config; - beforeEach(() => { - config = {}; - packageFileWorker.updateBranch = jest.fn(); - }); - it('handles null', async () => { - const allUpgrades = await packageFileWorker.findUpgrades([], config); - expect(allUpgrades).toMatchObject([]); - }); - it('handles one dep', async () => { - const dep = { - depName: 'foo', - currentVersion: '1.0.0', - }; - const upgrade = { newVersion: '1.1.0' }; - npmApi.getDependency = jest.fn(() => ({})); - versionsHelper.determineUpgrades = jest.fn(() => [upgrade]); - const allUpgrades = await packageFileWorker.findUpgrades([dep], config); - expect(allUpgrades).toMatchObject([Object.assign({}, dep, upgrade)]); - }); - it('handles no return', async () => { - const dep = { - depName: 'foo', - currentVersion: '1.0.0', - }; - const upgrade = { newVersion: '1.1.0' }; - npmApi.getDependency = jest.fn(() => ({})); - npmApi.getDependency.mockReturnValueOnce(null); - versionsHelper.determineUpgrades = jest.fn(() => [upgrade]); - const allUpgrades = await packageFileWorker.findUpgrades([dep], config); - expect(allUpgrades).toMatchObject([]); - }); - it('handles no upgrades', async () => { - const dep = { - depName: 'foo', - currentVersion: '1.0.0', - }; - npmApi.getDependency = jest.fn(() => ({})); - versionsHelper.determineUpgrades = jest.fn(() => []); - const allUpgrades = await packageFileWorker.findUpgrades([dep], config); - expect(allUpgrades).toMatchObject([]); - }); - }); - describe('assignDepConfigs(inputConfig, deps)', () => { - let config; - let deps; - beforeEach(() => { - config = {}; - deps = []; - }); - it('handles empty deps', () => { - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchObject([]); - }); - it('handles string deps', () => { - config.foo = 'bar'; - config.depTypes = ['dependencies', 'devDependencies']; - deps.push({ - depName: 'a', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles multiple deps', () => { - config.foo = 'bar'; - deps.push({ - depName: 'a', - }); - deps.push({ - depName: 'b', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles depType config without override', () => { - config.foo = 'bar'; - config.depTypes = [ - { - depType: 'dependencies', - alpha: 'beta', - }, - ]; - deps.push({ - depName: 'a', - depType: 'dependencies', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles depType config with override', () => { - config.foo = 'bar'; - config.depTypes = [ - { - depType: 'dependencies', - foo: 'beta', - }, - ]; - deps.push({ - depName: 'a', - depType: 'dependencies', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles package config', () => { - config.foo = 'bar'; - config.packages = [ - { - packageName: 'a', - labels: ['renovate'], - }, - ]; - deps.push({ - depName: 'a', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('package config overrides depType and general config', () => { - config.foo = 'bar'; - config.depTypes = [ - { - depType: 'dependencies', - foo: 'beta', - }, - ]; - config.packages = [ - { - packageName: 'a', - foo: 'gamma', - }, - ]; - deps.push({ - depName: 'a', - depType: 'dependencies', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('nested package config overrides depType and general config', () => { - config.foo = 'bar'; - config.depTypes = [ - { - depType: 'dependencies', - foo: 'beta', - packages: [ - { - packageName: 'a', - foo: 'gamma', - }, - ], - }, - ]; - deps.push({ - depName: 'a', - depType: 'dependencies', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles regex package pattern', () => { - config.foo = 'bar'; - config.packages = [ - { - packagePattern: 'eslint', - labels: ['eslint'], - }, - ]; - deps.push({ - depName: 'eslint', - }); - deps.push({ - depName: 'eslint-foo', - }); - deps.push({ - depName: 'a', - }); - deps.push({ - depName: 'also-eslint', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles regex wildcard package pattern', () => { - config.foo = 'bar'; - config.packages = [ - { - packagePattern: '^eslint', - labels: ['eslint'], - }, - ]; - deps.push({ - depName: 'eslint', - }); - deps.push({ - depName: 'eslint-foo', - }); - deps.push({ - depName: 'a', - }); - deps.push({ - depName: 'also-eslint', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - it('handles non-regex package name', () => { - config.foo = 'bar'; - config.packages = [ - { - packageName: 'eslint', - labels: ['eslint'], - }, - ]; - deps.push({ - depName: 'eslint', - }); - deps.push({ - depName: 'eslint-foo', - }); - deps.push({ - depName: 'a', - }); - deps.push({ - depName: 'also-eslint', - }); - const updatedDeps = packageFileWorker.assignDepConfigs(config, deps); - expect(updatedDeps).toMatchSnapshot(); - }); - }); - describe('getDepTypeConfig(depTypes, depTypeName)', () => { - it('handles empty depTypes', () => { - const depTypeConfig = packageFileWorker.getDepTypeConfig( - [], - 'dependencies' - ); - expect(depTypeConfig).toMatchObject({}); - }); - it('handles all strings', () => { - const depTypes = ['dependencies', 'devDependencies']; - const depTypeConfig = packageFileWorker.getDepTypeConfig( - depTypes, - 'dependencies' - ); - expect(depTypeConfig).toMatchObject({}); - }); - it('handles missed object', () => { - const depTypes = [ - 'dependencies', - { - depType: 'devDependencies', - foo: 'bar', - }, - ]; - const depTypeConfig = packageFileWorker.getDepTypeConfig( - depTypes, - 'dependencies' - ); - expect(depTypeConfig).toMatchObject({}); - }); - it('handles hit object', () => { - const depTypes = [ - { - depType: 'dependencies', - foo: 'bar', - }, - 'devDependencies', - ]; - const depTypeConfig = packageFileWorker.getDepTypeConfig( - depTypes, - 'dependencies' - ); - const expectedResult = { - foo: 'bar', - }; - expect(depTypeConfig).toMatchObject(expectedResult); - }); - }); - describe('processPackageFile(config)', () => { - let config; - beforeEach(() => { - packageFileWorker.assignDepConfigs = jest.fn(() => []); - packageFileWorker.findUpgrades = jest.fn(() => []); - packageJsonHelper.extractDependencies = jest.fn(() => []); - config = require('../../lib/config/defaults').getConfig(); - config.api = { - getFileJson: jest.fn(() => ({})), - }; - config.logger = logger; - }); - it('returns empty array if no package content', async () => { - config.api.getFileJson.mockReturnValueOnce(null); - const res = await packageFileWorker.processPackageFile(config); - expect(res).toEqual([]); - }); - it('returns empty array if config disabled', async () => { - config.api.getFileJson.mockReturnValueOnce({ - renovate: { - enabled: false, - }, - }); - const res = await packageFileWorker.processPackageFile(config); - expect(res).toEqual([]); - }); - it('extracts dependencies for each depType', async () => { - config.depTypes = [ - 'dependencies', - { - depType: 'devDependencies', - foo: 'bar', - }, - ]; - const res = await packageFileWorker.processPackageFile(config); - expect(res).toEqual([]); - expect( - packageJsonHelper.extractDependencies.mock.calls - ).toMatchSnapshot(); - }); - it('filters dependencies', async () => { - packageJsonHelper.extractDependencies.mockReturnValueOnce([ - { - depName: 'a', - }, - ]); - packageFileWorker.assignDepConfigs.mockReturnValueOnce(['a']); - packageFileWorker.findUpgrades.mockReturnValueOnce(['a']); - const res = await packageFileWorker.processPackageFile(config); - expect(res).toHaveLength(1); - expect(res).toMatchSnapshot(); - }); - it('maintains yarn.lock', async () => { - config.maintainYarnLock = true; - const res = await packageFileWorker.processPackageFile(config); - expect(res).toHaveLength(1); - }); - }); -}); diff --git a/test/workers/package-file/index.spec.js b/test/workers/package-file/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..231e52fe5107ed777f6f721241639e5a349fa1d1 --- /dev/null +++ b/test/workers/package-file/index.spec.js @@ -0,0 +1,52 @@ +const packageFileWorker = require('../../../lib/workers/package-file'); +const depTypeWorker = require('../../../lib/workers/dep-type'); + +jest.mock('../../../lib/workers/dep-type'); + +describe('packageFileWorker', () => { + describe('findUpgrades(config)', () => { + let config; + beforeEach(() => { + config = { + api: { + getFileJson: jest.fn(), + }, + depTypes: ['dependencies', 'devDependencies'], + }; + packageFileWorker.updateBranch = jest.fn(); + }); + it('handles null', async () => { + const allUpgrades = await packageFileWorker.findUpgrades(config); + expect(allUpgrades).toHaveLength(0); + }); + it('handles no renovate config', async () => { + config.enabled = false; + config.api.getFileJson.mockReturnValueOnce({}); + const res = await packageFileWorker.findUpgrades(config); + expect(res).toEqual([]); + }); + it('returns empty array if config disabled', async () => { + config.api.getFileJson.mockReturnValueOnce({ + renovate: { + enabled: false, + }, + }); + const res = await packageFileWorker.findUpgrades(config); + expect(res).toEqual([]); + }); + it('calls depTypeWorker', async () => { + config.api.getFileJson.mockReturnValueOnce({}); + depTypeWorker.findUpgrades.mockReturnValueOnce([{}]); + depTypeWorker.findUpgrades.mockReturnValueOnce([{}, {}]); + const res = await packageFileWorker.findUpgrades(config); + expect(res).toHaveLength(3); + }); + it('maintains yarn.lock', async () => { + config.api.getFileJson.mockReturnValueOnce({}); + config.maintainYarnLock = true; + depTypeWorker.findUpgrades.mockReturnValue([]); + const res = await packageFileWorker.findUpgrades(config); + expect(res).toHaveLength(1); + }); + }); +}); diff --git a/test/helpers/__snapshots__/versions.spec.js.snap b/test/workers/package/__snapshots__/versions.spec.js.snap similarity index 69% rename from test/helpers/__snapshots__/versions.spec.js.snap rename to test/workers/package/__snapshots__/versions.spec.js.snap index e5ea3885f7f4ba4f6725d420d935dce92fa7a23d..c69f30ba91042222678cf2d57aec5645ccd04853 100644 --- a/test/helpers/__snapshots__/versions.spec.js.snap +++ b/test/workers/package/__snapshots__/versions.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) disables major release separation (major) 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) disables major release separation (major) 1`] = ` Array [ Object { "automergeEnabled": false, @@ -14,7 +14,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) disables major release separation (minor) 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) disables major release separation (minor) 1`] = ` Array [ Object { "automergeEnabled": false, @@ -28,7 +28,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) ignores pinning for ranges when other upgrade exists 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) ignores pinning for ranges when other upgrade exists 1`] = ` Array [ Object { "automergeEnabled": false, @@ -42,7 +42,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) pins minor ranged versions 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) pins minor ranged versions 1`] = ` Array [ Object { "automergeEnabled": true, @@ -54,7 +54,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) returns both updates if automerging minor 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) returns both updates if automerging minor 1`] = ` Array [ Object { "automergeEnabled": true, @@ -77,7 +77,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) returns only one update if automerging any 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) returns only one update if automerging any 1`] = ` Array [ Object { "automergeEnabled": true, @@ -91,7 +91,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) returns only one update if grouping 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) returns only one update if grouping 1`] = ` Array [ Object { "automergeEnabled": false, @@ -105,7 +105,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) should allow unstable versions if the current version is unstable 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) should allow unstable versions if the current version is unstable 1`] = ` Array [ Object { "automergeEnabled": false, @@ -119,7 +119,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) should treat zero zero caret ranges as pinned 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) should treat zero zero caret ranges as pinned 1`] = ` Array [ Object { "automergeEnabled": false, @@ -134,7 +134,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports > latest versions if configured 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) supports > latest versions if configured 1`] = ` Array [ Object { "automergeEnabled": false, @@ -148,7 +148,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports future versions if already future 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) supports future versions if already future 1`] = ` Array [ Object { "automergeEnabled": true, @@ -160,7 +160,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports future versions if configured 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) supports future versions if configured 1`] = ` Array [ Object { "automergeEnabled": false, @@ -174,7 +174,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports minor and major upgrades for ranged versions 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) supports minor and major upgrades for ranged versions 1`] = ` Array [ Object { "automergeEnabled": false, @@ -197,7 +197,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports minor and major upgrades for tilde ranges 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) supports minor and major upgrades for tilde ranges 1`] = ` Array [ Object { "automergeEnabled": false, @@ -220,7 +220,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades .x major ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades .x major ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -235,7 +235,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades .x minor ranges 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades .x minor ranges 1`] = ` Array [ Object { "automergeEnabled": false, @@ -249,7 +249,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades .x minor ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades .x minor ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -264,7 +264,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades less than equal ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades less than equal ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -289,7 +289,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades minor ranged versions 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades minor ranged versions 1`] = ` Array [ Object { "automergeEnabled": false, @@ -303,7 +303,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades multiple caret ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades multiple caret ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -328,7 +328,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades multiple tilde ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades multiple tilde ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -353,7 +353,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades shorthand major ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades shorthand major ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -368,7 +368,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades shorthand minor ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades shorthand minor ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, @@ -383,7 +383,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades tilde ranges 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades tilde ranges 1`] = ` Array [ Object { "automergeEnabled": false, @@ -397,7 +397,7 @@ Array [ ] `; -exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) upgrades tilde ranges without pinning 1`] = ` +exports[`workers/package/versions .determineUpgrades(npmDep, config) upgrades tilde ranges without pinning 1`] = ` Array [ Object { "automergeEnabled": false, diff --git a/test/workers/package/index.spec.js b/test/workers/package/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..af3bc51bcf2303d80f9c4c2a98230db96af0b005 --- /dev/null +++ b/test/workers/package/index.spec.js @@ -0,0 +1,33 @@ +const npmApi = require('../../../lib/api/npm'); +const versions = require('../../../lib/workers/package/versions'); +const pkgWorker = require('../../../lib/workers/package/index'); + +jest.mock('../../../lib/workers/package/versions'); +jest.mock('../../../lib/api/npm'); + +describe('lib/workers/package/index', () => { + describe('findUpgrades(config)', () => { + let config; + beforeEach(() => { + config = { + depName: 'foo', + }; + }); + it('returns empty if no npm dep found', async () => { + const res = await pkgWorker.findUpgrades(config); + expect(res).toMatchObject([]); + }); + it('returns empty if no upgrades found', async () => { + npmApi.getDependency.mockReturnValueOnce({}); + versions.determineUpgrades.mockReturnValueOnce([]); + const res = await pkgWorker.findUpgrades(config); + expect(res).toMatchObject([]); + }); + it('returns array if upgrades found', async () => { + npmApi.getDependency.mockReturnValueOnce({}); + versions.determineUpgrades.mockReturnValueOnce([{}]); + const res = await pkgWorker.findUpgrades(config); + expect(res).toHaveLength(1); + }); + }); +}); diff --git a/test/workers/package/versions.spec.js b/test/workers/package/versions.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..29964bcfe55ffee04e8c42b2830d38c200d627d8 --- /dev/null +++ b/test/workers/package/versions.spec.js @@ -0,0 +1,246 @@ +const versions = require('../../../lib/workers/package/versions'); +const qJson = require('../../_fixtures/npm/01.json'); +const helmetJson = require('../../_fixtures/npm/02.json'); + +let config; + +describe('workers/package/versions', () => { + beforeEach(() => { + config = require('../../../lib/config/defaults').getConfig(); + }); + + describe('.determineUpgrades(npmDep, config)', () => { + it('return empty if invalid current version', () => { + config.currentVersion = 'invalid'; + versions.determineUpgrades(qJson, config).should.have.length(0); + }); + it('return empty if null versions', () => { + config.currentVersion = '1.0.0'; + const testDep = { + name: 'q', + }; + versions.determineUpgrades(testDep, config).should.have.length(0); + }); + it('return empty if empty versions', () => { + const testDep = { + name: 'q', + versions: [], + }; + config.currentVersion = '1.0.0'; + versions.determineUpgrades(testDep, config).should.have.length(0); + }); + it('supports minor and major upgrades for tilde ranges', () => { + config.currentVersion = '^0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('returns only one update if grouping', () => { + config.groupName = 'somegroup'; + config.currentVersion = '^0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('returns only one update if automerging any', () => { + config.automerge = 'any'; + config.currentVersion = '^0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('returns both updates if automerging minor', () => { + config.automerge = 'minor'; + config.currentVersion = '^0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('disables major release separation (major)', () => { + config.separateMajorReleases = false; + config.currentVersion = '^0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('disables major release separation (minor)', () => { + config.separateMajorReleases = false; + config.currentVersion = '1.0.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('supports minor and major upgrades for ranged versions', () => { + config.currentVersion = '~0.4.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('ignores pinning for ranges when other upgrade exists', () => { + config.currentVersion = '~0.9.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades minor ranged versions', () => { + config.currentVersion = '~1.0.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('pins minor ranged versions', () => { + config.currentVersion = '^1.0.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('ignores minor ranged versions when not pinning', () => { + config.pinVersions = false; + config.currentVersion = '^1.0.0'; + expect(versions.determineUpgrades(qJson, config)).toHaveLength(0); + }); + it('upgrades tilde ranges', () => { + config.currentVersion = '~1.3.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades .x minor ranges', () => { + config.currentVersion = '1.3.x'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades tilde ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '~1.3.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades .x major ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '0.x'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades .x minor ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '1.3.x'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades shorthand major ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades shorthand minor ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '1.3'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades multiple tilde ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '~0.7.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('upgrades multiple caret ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '^0.7.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('ignores complex ranges when not pinning', () => { + config.pinVersions = false; + config.currentVersion = '^0.7.0 || ^0.8.0'; + expect(versions.determineUpgrades(qJson, config)).toHaveLength(0); + }); + it('returns nothing for greater than ranges', () => { + config.pinVersions = false; + config.currentVersion = '>= 0.7.0'; + expect(versions.determineUpgrades(qJson, config)).toHaveLength(0); + }); + it('upgrades less than equal ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '<= 0.7.2'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('rejects less than ranges without pinning', () => { + config.pinVersions = false; + config.currentVersion = '< 0.7.2'; + expect(versions.determineUpgrades(qJson, config)).toEqual([]); + }); + it('supports > latest versions if configured', () => { + config.respectLatest = false; + config.currentVersion = '1.4.1'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('supports future versions if configured', () => { + config.ignoreFuture = false; + config.respectLatest = false; + config.currentVersion = '1.4.1'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('supports future versions if already future', () => { + config.currentVersion = '^2.0.0'; + expect(versions.determineUpgrades(qJson, config)).toMatchSnapshot(); + }); + it('should ignore unstable versions if the current version is stable', () => { + config.currentVersion = '1.0.0'; + versions + .determineUpgrades( + { + name: 'amazing-package', + versions: { + '1.0.0': {}, + '1.1.0-beta': {}, + }, + }, + config + ) + .should.eql([]); + }); + it('should allow unstable versions if the current version is unstable', () => { + config.currentVersion = '1.0.0-beta'; + expect( + versions.determineUpgrades( + { + name: 'amazing-package', + versions: { + '1.0.0-beta': {}, + '1.1.0-beta': {}, + }, + }, + config + ) + ).toMatchSnapshot(); + }); + it('should treat zero zero tilde ranges as 0.0.x', () => { + config.pinVersions = false; + config.currentVersion = '~0.0.34'; + expect(versions.determineUpgrades(helmetJson, config)).toEqual([]); + }); + it('should treat zero zero caret ranges as pinned', () => { + config.pinVersions = false; + config.currentVersion = '^0.0.34'; + expect(versions.determineUpgrades(helmetJson, config)).toMatchSnapshot(); + }); + }); + describe('.isRange(input)', () => { + it('rejects simple semver', () => { + versions.isRange('1.2.3').should.eql(false); + }); + it('accepts tilde', () => { + versions.isRange('~1.2.3').should.eql(true); + }); + it('accepts caret', () => { + versions.isRange('^1.2.3').should.eql(true); + }); + }); + describe('.isValidVersion(input)', () => { + it('should support simple semver', () => { + versions.isValidVersion('1.2.3').should.eql(true); + }); + it('should support versions with dash', () => { + versions.isValidVersion('1.2.3-foo').should.eql(true); + }); + it('should reject versions without dash', () => { + versions.isValidVersion('1.2.3foo').should.eql(false); + }); + it('should support ranges', () => { + versions.isValidVersion('~1.2.3').should.eql(true); + versions.isValidVersion('^1.2.3').should.eql(true); + versions.isValidVersion('>1.2.3').should.eql(true); + }); + it('should reject github repositories', () => { + versions.isValidVersion('singapore/renovate').should.eql(false); + versions.isValidVersion('singapore/renovate#master').should.eql(false); + versions + .isValidVersion('https://github.com/singapore/renovate.git') + .should.eql(false); + }); + }); + describe('.isPastLatest(dep, version)', () => { + it('should return false for less than', () => { + versions.isPastLatest(qJson, '1.0.0').should.eql(false); + }); + it('should return false for equal', () => { + versions.isPastLatest(qJson, '1.4.1').should.eql(false); + }); + it('should return true for greater than', () => { + versions.isPastLatest(qJson, '2.0.3').should.eql(true); + }); + }); +}); diff --git a/test/workers/repository/__snapshots__/onboarding.spec.js.snap b/test/workers/repository/__snapshots__/onboarding.spec.js.snap index fd5c58b180779777a25fea7e3774fb9a85e795f3..680ece21f54bf1ef8f0c68fa05d9fb4a2421d2b7 100644 --- a/test/workers/repository/__snapshots__/onboarding.spec.js.snap +++ b/test/workers/repository/__snapshots__/onboarding.spec.js.snap @@ -10,9 +10,9 @@ Array [ \\"enabled\\": true, \\"packageFiles\\": [], \\"depTypes\\": [\\"dependencies\\", \\"devDependencies\\", \\"optionalDependencies\\"], + \\"ignoreDeps\\": [], \\"pinVersions\\": true, \\"separateMajorReleases\\": true, - \\"ignoreDeps\\": [], \\"rebaseStalePrs\\": false, \\"prCreation\\": \\"immediate\\", \\"automerge\\": \\"none\\", diff --git a/test/workers/repository/upgrades.spec.js b/test/workers/repository/upgrades.spec.js index 02d338eb7c4d2a327cc5d6e32ef1f639c0b0bf25..2a27d13f80a6ec97cd5655733fa8260c6602e31f 100644 --- a/test/workers/repository/upgrades.spec.js +++ b/test/workers/repository/upgrades.spec.js @@ -24,7 +24,7 @@ describe('workers/repository/upgrades', () => { packageFile: 'backend/package.json', }, ]; - packageFileWorker.processPackageFile.mockReturnValue([]); + packageFileWorker.findUpgrades.mockReturnValue([]); const res = await upgrades.determineRepoUpgrades(config); expect(res.length).toBe(0); }); @@ -38,8 +38,8 @@ describe('workers/repository/upgrades', () => { fileName: 'frontend/package.json', }, ]; - packageFileWorker.processPackageFile.mockReturnValueOnce(['a']); - packageFileWorker.processPackageFile.mockReturnValueOnce(['b', 'c']); + packageFileWorker.findUpgrades.mockReturnValueOnce(['a']); + packageFileWorker.findUpgrades.mockReturnValueOnce(['b', 'c']); const res = await upgrades.determineRepoUpgrades(config); expect(res.length).toBe(3); });