From d765b34c33f246d319e88fc17fadba27c3480cf4 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@keylocation.sg> Date: Tue, 12 Sep 2017 09:33:41 +0200 Subject: [PATCH] feat: renovate meteor package.js dependencies (#786) This feature adds support for renovating Meteor's `package.js` files. Meteor config is disabled by default so must be manually enabled to work. If enabled, Renovate uses GitHub's search API to look for any files named `package.js` that include the text `Npm.depends`. If so then the file is parsed using Regex to extract its dependencies and check them for updates. Closes #785 --- docs/configuration.md | 27 +++ lib/api/github.js | 8 +- lib/config/definitions.js | 9 + lib/workers/branch/lock-files.js | 13 +- lib/workers/branch/package-files.js | 26 ++- lib/workers/branch/package-js.js | 21 ++ lib/workers/dep-type/index.js | 40 ++-- lib/workers/package-file/index.js | 23 ++ lib/workers/pr/index.js | 20 +- lib/workers/repository/apis.js | 204 ++++++++++-------- lib/workers/repository/upgrades.js | 14 +- test/_fixtures/meteor/package-1.js | 26 +++ test/_fixtures/meteor/package-2.js | 26 +++ test/api/__snapshots__/github.spec.js.snap | 2 +- test/api/github.spec.js | 2 +- .../__snapshots__/package-js.spec.js.snap | 61 ++++++ test/workers/branch/package-files.spec.js | 10 +- test/workers/branch/package-js.spec.js | 49 +++++ test/workers/dep-type/index.spec.js | 12 ++ test/workers/package-file/index.spec.js | 25 +++ .../__snapshots__/apis.spec.js.snap | 10 + test/workers/repository/apis.spec.js | 28 ++- test/workers/repository/upgrades.spec.js | 5 +- 23 files changed, 521 insertions(+), 140 deletions(-) create mode 100644 lib/workers/branch/package-js.js create mode 100644 test/_fixtures/meteor/package-1.js create mode 100644 test/_fixtures/meteor/package-2.js create mode 100644 test/workers/branch/__snapshots__/package-js.spec.js.snap create mode 100644 test/workers/branch/package-js.spec.js diff --git a/docs/configuration.md b/docs/configuration.md index 1b00948f7c..7493a9020b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -74,6 +74,8 @@ $ node renovate --help --log-file <string> Log file path --log-file-level <string> Log file log level --onboarding [boolean] Require a Configuration PR first + --private-key <string> Server-side private key + --encrypted <json> A configuration object containing configuration encrypted with project key --timezone <string> [IANA Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) --onboarding [boolean] Require a Configuration PR first --platform <string> Platform type of repository @@ -109,6 +111,7 @@ $ node renovate --help --labels <list> Labels to add to Pull Request --assignees <list> Assignees for Pull Request --reviewers <list> Requested reviewers for Pull Requests (GitHub only) + --meteor <json> Configuration object for meteor package.js renovation -h, --help output usage information Examples: @@ -205,6 +208,22 @@ Obviously, you can't set repository or package file location with this method. <td>`RENOVATE_ONBOARDING`</td> <td>`--onboarding`<td> </tr> +<tr> + <td>`privateKey`</td> + <td>Server-side private key</td> + <td>string</td> + <td><pre>null</pre></td> + <td>`RENOVATE_PRIVATE_KEY`</td> + <td>`--private-key`<td> +</tr> +<tr> + <td>`encrypted`</td> + <td>A configuration object containing configuration encrypted with project key</td> + <td>json</td> + <td><pre>null</pre></td> + <td>`RENOVATE_ENCRYPTED`</td> + <td>`--encrypted`<td> +</tr> <tr> <td>`timezone`</td> <td>[IANA Time Zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)</td> @@ -685,3 +704,11 @@ Obviously, you can't set repository or package file location with this method. <td>`RENOVATE_REVIEWERS`</td> <td>`--reviewers`<td> </tr> +<tr> + <td>`meteor`</td> + <td>Configuration object for meteor package.js renovation</td> + <td>json</td> + <td><pre>{"enabled": true}</pre></td> + <td>`RENOVATE_METEOR`</td> + <td>`--meteor`<td> +</tr> diff --git a/lib/api/github.js b/lib/api/github.js index 92cd2ce1a6..95f9626b6e 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -227,9 +227,13 @@ async function setBaseBranch(branchName) { // Search // Returns an array of file paths in current repo matching the fileName -async function findFilePaths(fileName) { +async function findFilePaths(fileName, content) { let results = []; - let url = `search/code?q=repo:${config.repoName}+filename:${fileName}&per_page=100`; + let url = `search/code?q=`; + if (content) { + url += `${content}+`; + } + url += `repo:${config.repoName}+filename:${fileName}&per_page=100`; do { const res = await ghGotRetry(url); const exactMatches = res.body.items.filter(item => item.name === fileName); diff --git a/lib/config/definitions.js b/lib/config/definitions.js index 4f7193ef5b..bd0b8a4689 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -540,6 +540,15 @@ const options = [ description: 'Requested reviewers for Pull Requests (GitHub only)', type: 'list', }, + // meteor + { + name: 'meteor', + description: 'Configuration object for meteor package.js renovation', + stage: 'repository', + type: 'json', + default: { enabled: false }, + mergeable: true, + }, ]; function getOptions() { diff --git a/lib/workers/branch/lock-files.js b/lib/workers/branch/lock-files.js index b8a703a3e7..df205ed36a 100644 --- a/lib/workers/branch/lock-files.js +++ b/lib/workers/branch/lock-files.js @@ -96,11 +96,13 @@ async function writeExistingFiles(config) { config.tmpDir.name, path.dirname(packageFile.packageFile) ); - logger.debug(`Writing package.json to ${basedir}`); - await fs.outputFile( - path.join(basedir, 'package.json'), - JSON.stringify(packageFile.content) - ); + if (packageFile.packageFile.endsWith('package.json')) { + logger.debug(`Writing package.json to ${basedir}`); + await fs.outputFile( + path.join(basedir, 'package.json'), + JSON.stringify(packageFile.content) + ); + } if (packageFile.npmrc) { logger.debug(`Writing .npmrc to ${basedir}`); await fs.outputFile(path.join(basedir, '.npmrc'), packageFile.npmrc); @@ -112,7 +114,6 @@ async function writeExistingFiles(config) { packageFile.yarnrc.replace('--install.pure-lockfile true', '') ); } - logger.debug('Removing any previous lock files'); await fs.remove(path.join(basedir, 'yarn.lock')); await fs.remove(path.join(basedir, 'package-lock.json')); } diff --git a/lib/workers/branch/package-files.js b/lib/workers/branch/package-files.js index 8567f248a9..cde5433b43 100644 --- a/lib/workers/branch/package-files.js +++ b/lib/workers/branch/package-files.js @@ -1,4 +1,5 @@ const packageJsonHelper = require('./package-json'); +const packageJsHelper = require('./package-js'); module.exports = { getUpdatedPackageFiles, @@ -15,13 +16,24 @@ async function getUpdatedPackageFiles(config) { upgrade.packageFile, config.parentBranch )); - const newContent = packageJsonHelper.setNewValue( - existingContent, - upgrade.depType, - upgrade.depName, - upgrade.newVersion, - config.logger - ); + let newContent; + if (upgrade.packageFile.endsWith('package.json')) { + newContent = packageJsonHelper.setNewValue( + existingContent, + upgrade.depType, + upgrade.depName, + upgrade.newVersion, + config.logger + ); + } else { + newContent = packageJsHelper.setNewValue( + existingContent, + upgrade.depName, + upgrade.currentVersion, + upgrade.newVersion, + config.logger + ); + } if (newContent !== existingContent) { config.logger.debug('Updating packageFile content'); updatedPackageFiles[upgrade.packageFile] = newContent; diff --git a/lib/workers/branch/package-js.js b/lib/workers/branch/package-js.js new file mode 100644 index 0000000000..f6986ec45d --- /dev/null +++ b/lib/workers/branch/package-js.js @@ -0,0 +1,21 @@ +module.exports = { + setNewValue, +}; + +function setNewValue( + currentFileContent, + depName, + currentVersion, + newVersion, + logger +) { + logger.debug(`setNewValue: ${depName} = ${newVersion}`); + const regexReplace = new RegExp( + `('|")(${depName})('|"):(\\s+)('|")${currentVersion}('|")` + ); + const newFileContent = currentFileContent.replace( + regexReplace, + `$1$2$3:$4$5${newVersion}$6` + ); + return newFileContent; +} diff --git a/lib/workers/dep-type/index.js b/lib/workers/dep-type/index.js index d19d0f600f..a7ff97aef2 100644 --- a/lib/workers/dep-type/index.js +++ b/lib/workers/dep-type/index.js @@ -15,28 +15,36 @@ async function renovateDepType(packageContent, config) { logger.debug('depType is disabled'); return []; } - // Extract all dependencies from the package.json - const currentDeps = await packageJson.extractDependencies( - packageContent, - config.depType - ); - if (currentDeps.length === 0) { - return []; + let deps; + if (config.packageFile.endsWith('package.json')) { + // Extract all dependencies from the package.json + deps = await packageJson.extractDependencies( + packageContent, + config.depType + ); + logger.debug(`currentDeps length is ${deps.length}`); + logger.debug({ deps }, `currentDeps`); + } else if (config.packageFile.endsWith('package.js')) { + deps = packageContent + .match(/Npm\.depends\({([\s\S]*?)}\);/)[1] + .replace(/(\s|\\n|\\t|'|")/g, '') + .split(',') + .map(dep => dep.split(/:(.*)/)) + .map(arr => ({ + depType: 'npmDepends', + depName: arr[0], + currentVersion: arr[1], + })); } - logger.debug(`currentDeps length is ${currentDeps.length}`); - logger.debug({ currentDeps }, `currentDeps`); - // Filter out ignored dependencies - const filteredDeps = currentDeps.filter( + deps = deps.filter( dependency => config.ignoreDeps.indexOf(dependency.depName) === -1 && config.monorepoPackages.indexOf(dependency.depName) === -1 ); - logger.debug(`filteredDeps length is ${filteredDeps.length}`); - logger.debug({ filteredDeps }, `filteredDeps`); + logger.debug(`filtered deps length is ${deps.length}`); + logger.debug({ deps }, `filtered deps`); // Obtain full config for each dependency - const depConfigs = filteredDeps.map(dep => - module.exports.getDepConfig(config, dep) - ); + const depConfigs = deps.map(dep => module.exports.getDepConfig(config, dep)); logger.trace({ config: depConfigs }, `depConfigs`); // renovateDepType can return more than one upgrade each const pkgWorkers = depConfigs.map(depConfig => diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js index 2eeecaaf13..50fe84629b 100644 --- a/lib/workers/package-file/index.js +++ b/lib/workers/package-file/index.js @@ -1,11 +1,13 @@ const configParser = require('../../config'); const depTypeWorker = require('../dep-type'); +const packageWorker = require('../package'); const npmApi = require('../../api/npm'); let logger = require('../../logger'); module.exports = { renovatePackageFile, + renovateMeteorPackageFile, }; async function renovatePackageFile(packageFileConfig) { @@ -69,3 +71,24 @@ async function renovatePackageFile(packageFileConfig) { logger.info('Finished processing package file'); return upgrades; } + +async function renovateMeteorPackageFile(packageFileConfig) { + const config = { ...packageFileConfig }; + let upgrades = []; + logger = config.logger; + logger.info(`Processing meteor package file`); + + // Check if config is disabled + if (config.enabled === false) { + logger.info('packageFile is disabled'); + return upgrades; + } + const content = await packageFileConfig.api.getFileContent( + packageFileConfig.packageFile + ); + upgrades = upgrades.concat( + await depTypeWorker.renovateDepType(content, packageFileConfig) + ); + logger.info('Finished processing package file'); + return upgrades; +} diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js index 2a09224292..5ab329ea62 100644 --- a/lib/workers/pr/index.js +++ b/lib/workers/pr/index.js @@ -127,14 +127,18 @@ async function ensurePr(prConfig) { } const prTitle = handlebars.compile(config.prTitle)(config); - let prBodyMarkdown = handlebars.compile(config.prBody)(config); - const atUserRe = /[^`]@([a-z]+\/[a-z]+)/g; - prBodyMarkdown = prBodyMarkdown.replace(atUserRe, '@​$1'); - let prBody = converter.makeHtml(prBodyMarkdown); - // Public GitHub repos need links prevented - see #489 - prBody = prBody.replace(issueRe, '$1#​$2$3'); - const backTickRe = /`([^/]*?)`/g; - prBody = prBody.replace(backTickRe, '<code>$1</code>'); + let prBody; + do { + let prBodyMarkdown = handlebars.compile(config.prBody)(config); + const atUserRe = /[^`]@([a-z]+\/[a-z]+)/g; + prBodyMarkdown = prBodyMarkdown.replace(atUserRe, '@​$1'); + prBody = converter.makeHtml(prBodyMarkdown); + // Public GitHub repos need links prevented - see #489 + prBody = prBody.replace(issueRe, '$1#​$2$3'); + const backTickRe = /`([^/]*?)`/g; + prBody = prBody.replace(backTickRe, '<code>$1</code>'); + config.upgrades.pop(); + } while (prBody.length > 250000); try { // Check if existing PR exists diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js index f18eddbdd6..90756b09ff 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -272,6 +272,14 @@ async function detectPackageFiles(input) { config.packageFiles = ['package.json']; } } + if (config.meteor.enabled) { + const meteorPackageFiles = await config.api.findFilePaths( + 'package.js', + 'Npm.depends' + ); + logger.info(`Found ${meteorPackageFiles.length} meteor package files`); + config.packageFiles = config.packageFiles.concat(meteorPackageFiles); + } return config; } @@ -282,106 +290,118 @@ async function resolvePackageFiles(inputConfig) { for (let packageFile of config.packageFiles) { packageFile = typeof packageFile === 'string' ? { packageFile } : packageFile; - config.logger.debug(`Resolving packageFile ${JSON.stringify(packageFile)}`); - packageFile.content = await config.api.getFileJson( - packageFile.packageFile, - config.baseBranch - ); - packageFile.npmrc = await config.api.getFileContent( - path.join(path.dirname(packageFile.packageFile), '.npmrc'), - config.baseBranch - ); - if (!packageFile.npmrc) { - delete packageFile.npmrc; - } - packageFile.yarnrc = await config.api.getFileContent( - path.join(path.dirname(packageFile.packageFile), '.yarnrc'), - config.baseBranch - ); - if (!packageFile.yarnrc) { - delete packageFile.yarnrc; - } - if (packageFile.content) { - // check for workspaces - if ( - packageFile.packageFile === 'package.json' && - packageFile.content.workspaces - ) { - config.logger.info('Found yarn workspaces configuration'); - config.hasYarnWorkspaces = true; - } - // hoist renovate config if exists - if (packageFile.content.renovate) { - config.hasPackageJsonRenovateConfig = true; - config.logger.debug( - { - packageFile: packageFile.packageFile, - config: packageFile.content.renovate, - }, - `Found package.json renovate config` - ); - const migratedConfig = migrateAndValidate( - config, - packageFile.content.renovate - ); - config.logger.debug( - { config: migratedConfig }, - 'package.json migrated config' - ); - const resolvedConfig = await presets.resolveConfigPresets( - migratedConfig, - config.logger - ); - config.logger.debug( - { config: resolvedConfig }, - 'package.json resolved config' - ); - Object.assign(packageFile, resolvedConfig); - delete packageFile.content.renovate; - } else { - config.logger.debug( - { packageFile: packageFile.packageFile }, - `No renovate config` - ); + if (packageFile.packageFile.endsWith('package.json')) { + config.logger.debug( + `Resolving packageFile ${JSON.stringify(packageFile)}` + ); + packageFile.content = await config.api.getFileJson( + packageFile.packageFile, + config.baseBranch + ); + packageFile.npmrc = await config.api.getFileContent( + path.join(path.dirname(packageFile.packageFile), '.npmrc'), + config.baseBranch + ); + if (!packageFile.npmrc) { + delete packageFile.npmrc; } - // Detect if lock files are used - const yarnLockFileName = path.join( - path.dirname(packageFile.packageFile), - 'yarn.lock' + packageFile.yarnrc = await config.api.getFileContent( + path.join(path.dirname(packageFile.packageFile), '.yarnrc'), + config.baseBranch ); - if ( - await config.api.getFileContent(yarnLockFileName, config.baseBranch) - ) { - config.logger.debug( - { packageFile: packageFile.packageFile }, - 'Found yarn.lock' + if (!packageFile.yarnrc) { + delete packageFile.yarnrc; + } + if (packageFile.content) { + // check for workspaces + if ( + packageFile.packageFile === 'package.json' && + packageFile.content.workspaces + ) { + config.logger.info('Found yarn workspaces configuration'); + config.hasYarnWorkspaces = true; + } + // hoist renovate config if exists + if (packageFile.content.renovate) { + config.hasPackageJsonRenovateConfig = true; + config.logger.debug( + { + packageFile: packageFile.packageFile, + config: packageFile.content.renovate, + }, + `Found package.json renovate config` + ); + const migratedConfig = migrateAndValidate( + config, + packageFile.content.renovate + ); + config.logger.debug( + { config: migratedConfig }, + 'package.json migrated config' + ); + const resolvedConfig = await presets.resolveConfigPresets( + migratedConfig, + config.logger + ); + config.logger.debug( + { config: resolvedConfig }, + 'package.json resolved config' + ); + Object.assign(packageFile, resolvedConfig); + delete packageFile.content.renovate; + } else { + config.logger.debug( + { packageFile: packageFile.packageFile }, + `No renovate config` + ); + } + // Detect if lock files are used + const yarnLockFileName = path.join( + path.dirname(packageFile.packageFile), + 'yarn.lock' + ); + if ( + await config.api.getFileContent(yarnLockFileName, config.baseBranch) + ) { + config.logger.debug( + { packageFile: packageFile.packageFile }, + 'Found yarn.lock' + ); + packageFile.hasYarnLock = true; + } else { + packageFile.hasYarnLock = false; + } + const packageLockFileName = path.join( + path.dirname(packageFile.packageFile), + 'package-lock.json' ); - packageFile.hasYarnLock = true; + if ( + await config.api.getFileContent( + packageLockFileName, + config.baseBranch + ) + ) { + config.logger.debug( + { packageFile: packageFile.packageFile }, + 'Found package-lock.json' + ); + packageFile.hasPackageLock = true; + } else { + packageFile.hasPackageLock = false; + } } else { - packageFile.hasYarnLock = false; - } - const packageLockFileName = path.join( - path.dirname(packageFile.packageFile), - 'package-lock.json' - ); - if ( - await config.api.getFileContent(packageLockFileName, config.baseBranch) - ) { - config.logger.debug( + config.logger.warn( { packageFile: packageFile.packageFile }, - 'Found package-lock.json' + 'package file not found' ); - packageFile.hasPackageLock = true; - } else { - packageFile.hasPackageLock = false; + continue; // eslint-disable-line } - packageFiles.push(packageFile); - } else { - config.logger.warn( - { packageFile: packageFile.packageFile }, - 'package file not found' - ); + } else if (packageFile.packageFile.endsWith('package.js')) { + // meteor + packageFile = configParser.mergeChildConfig(config.meteor, packageFile); } + + packageFiles.push(packageFile); } config.packageFiles = packageFiles; return config; diff --git a/lib/workers/repository/upgrades.js b/lib/workers/repository/upgrades.js index 64d6a0a7be..aa7c58dc90 100644 --- a/lib/workers/repository/upgrades.js +++ b/lib/workers/repository/upgrades.js @@ -24,9 +24,17 @@ async function determineRepoUpgrades(config) { config, index ); - upgrades = upgrades.concat( - await packageFileWorker.renovatePackageFile(packageFileConfig) - ); + if (packageFileConfig.packageFile.endsWith('package.json')) { + logger.info('Renovating package.json dependencies'); + upgrades = upgrades.concat( + await packageFileWorker.renovatePackageFile(packageFileConfig) + ); + } else if (packageFileConfig.packageFile.endsWith('package.js')) { + logger.info('Renovating package.js (meteor) dependencies'); + upgrades = upgrades.concat( + await packageFileWorker.renovateMeteorPackageFile(packageFileConfig) + ); + } } return upgrades; } diff --git a/test/_fixtures/meteor/package-1.js b/test/_fixtures/meteor/package-1.js new file mode 100644 index 0000000000..97066f419b --- /dev/null +++ b/test/_fixtures/meteor/package-1.js @@ -0,0 +1,26 @@ +Package.describe({ + 'name': 'steffo:meteor-accounts-saml', + 'summary': 'SAML Login (SP) for Meteor. Works with OpenAM, OpenIDP and provides Single Logout.', + 'version': '0.0.1', + 'git': 'https://github.com/steffow/meteor-accounts-saml.git' +}); + +Package.on_use(function(api) { + api.use('rocketchat:lib', 'server'); + api.use('ecmascript'); + api.use(['routepolicy', 'webapp', 'underscore', 'service-configuration'], 'server'); + api.use(['http', 'accounts-base'], ['client', 'server']); + + api.add_files(['saml_server.js', 'saml_utils.js'], 'server'); + api.add_files(['saml_rocketchat.js'], 'server'); + api.add_files('saml_client.js', 'client'); +}); + +Npm.depends({ + 'xml2js': '0.2.0', + 'xml-crypto': '0.6.0', + 'xmldom': '0.1.19', + 'connect': '2.7.10', + 'xmlbuilder': '2.6.4', + 'querystring': '0.2.0' +}); diff --git a/test/_fixtures/meteor/package-2.js b/test/_fixtures/meteor/package-2.js new file mode 100644 index 0000000000..182fa7c9a0 --- /dev/null +++ b/test/_fixtures/meteor/package-2.js @@ -0,0 +1,26 @@ +Package.describe({ + "name": "steffo:meteor-accounts-saml", + "summary": "SAML Login (SP) for Meteor. Works with OpenAM, OpenIDP and provides Single Logout.", + "version": "0.0.1", + "git": "https://github.com/steffow/meteor-accounts-saml.git" +}); + +Package.on_use(function(api) { + api.use("rocketchat:lib", "server"); + api.use("ecmascript"); + api.use(["routepolicy", "webapp", "underscore", "service-configuration"], "server"); + api.use(["http", "accounts-base"], ["client", "server"]); + + api.add_files(["saml_server.js", "saml_utils.js"], "server"); + api.add_files(["saml_rocketchat.js"], "server"); + api.add_files("saml_client.js", "client"); +}); + +Npm.depends({ + "xml2js": "0.2.0", + "xml-crypto": "0.6.0", + "xmldom": "0.1.19", + "connect": "2.7.10", + "xmlbuilder": "2.6.4", + "querystring": "0.2.0" +}); diff --git a/test/api/__snapshots__/github.spec.js.snap b/test/api/__snapshots__/github.spec.js.snap index 20f10ceb34..81e25b43c5 100644 --- a/test/api/__snapshots__/github.spec.js.snap +++ b/test/api/__snapshots__/github.spec.js.snap @@ -542,7 +542,7 @@ Array [ }, ], Array [ - "search/code?q=repo:some/repo+filename:package.json&per_page=100", + "search/code?q=some-content+repo:some/repo+filename:package.json&per_page=100", undefined, ], ] diff --git a/test/api/github.spec.js b/test/api/github.spec.js index d70a024eea..726e584d91 100644 --- a/test/api/github.spec.js +++ b/test/api/github.spec.js @@ -612,7 +612,7 @@ describe('api/github', () => { ], }, })); - const files = await github.findFilePaths('package.json'); + const files = await github.findFilePaths('package.json', 'some-content'); expect(ghGot.mock.calls).toMatchSnapshot(); expect(files).toMatchSnapshot(); }); diff --git a/test/workers/branch/__snapshots__/package-js.spec.js.snap b/test/workers/branch/__snapshots__/package-js.spec.js.snap new file mode 100644 index 0000000000..cb7db04c12 --- /dev/null +++ b/test/workers/branch/__snapshots__/package-js.spec.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`workers/branch/package-js .setNewValue(currentFileContent, depName, currentVersion, newVersion, logger) handles alternative quotes and white space 1`] = ` +"Package.describe({ + \\"name\\": \\"steffo:meteor-accounts-saml\\", + \\"summary\\": \\"SAML Login (SP) for Meteor. Works with OpenAM, OpenIDP and provides Single Logout.\\", + \\"version\\": \\"0.0.1\\", + \\"git\\": \\"https://github.com/steffow/meteor-accounts-saml.git\\" +}); + +Package.on_use(function(api) { + api.use(\\"rocketchat:lib\\", \\"server\\"); + api.use(\\"ecmascript\\"); + api.use([\\"routepolicy\\", \\"webapp\\", \\"underscore\\", \\"service-configuration\\"], \\"server\\"); + api.use([\\"http\\", \\"accounts-base\\"], [\\"client\\", \\"server\\"]); + + api.add_files([\\"saml_server.js\\", \\"saml_utils.js\\"], \\"server\\"); + api.add_files([\\"saml_rocketchat.js\\"], \\"server\\"); + api.add_files(\\"saml_client.js\\", \\"client\\"); +}); + +Npm.depends({ + \\"xml2js\\": \\"0.2.0\\", + \\"xml-crypto\\": \\"0.6.0\\", + \\"xmldom\\": \\"0.22.1\\", + \\"connect\\": \\"2.7.10\\", + \\"xmlbuilder\\": \\"2.6.4\\", + \\"querystring\\": \\"0.2.0\\" +}); +" +`; + +exports[`workers/branch/package-js .setNewValue(currentFileContent, depName, currentVersion, newVersion, logger) replaces a dependency value 1`] = ` +"Package.describe({ + 'name': 'steffo:meteor-accounts-saml', + 'summary': 'SAML Login (SP) for Meteor. Works with OpenAM, OpenIDP and provides Single Logout.', + 'version': '0.0.1', + 'git': 'https://github.com/steffow/meteor-accounts-saml.git' +}); + +Package.on_use(function(api) { + api.use('rocketchat:lib', 'server'); + api.use('ecmascript'); + api.use(['routepolicy', 'webapp', 'underscore', 'service-configuration'], 'server'); + api.use(['http', 'accounts-base'], ['client', 'server']); + + api.add_files(['saml_server.js', 'saml_utils.js'], 'server'); + api.add_files(['saml_rocketchat.js'], 'server'); + api.add_files('saml_client.js', 'client'); +}); + +Npm.depends({ + 'xml2js': '0.2.0', + 'xml-crypto': '0.6.0', + 'xmldom': '0.22.1', + 'connect': '2.7.10', + 'xmlbuilder': '2.6.4', + 'querystring': '0.2.0' +}); +" +`; diff --git a/test/workers/branch/package-files.spec.js b/test/workers/branch/package-files.spec.js index 520145377a..60488b8aeb 100644 --- a/test/workers/branch/package-files.spec.js +++ b/test/workers/branch/package-files.spec.js @@ -1,4 +1,5 @@ const packageJsonHelper = require('../../../lib/workers/branch/package-json'); +const packageJsHelper = require('../../../lib/workers/branch/package-js'); const { getUpdatedPackageFiles, } = require('../../../lib/workers/branch/package-files'); @@ -15,6 +16,7 @@ describe('workers/branch/package-files', () => { logger, }; packageJsonHelper.setNewValue = jest.fn(); + packageJsHelper.setNewValue = jest.fn(); }); it('returns empty if lock file maintenance', async () => { config.upgrades = [{ type: 'lockFileMaintenance' }]; @@ -22,11 +24,17 @@ describe('workers/branch/package-files', () => { expect(res).toHaveLength(0); }); it('returns updated files', async () => { - config.upgrades = [{}, {}]; + config.upgrades = [ + { packageFile: 'package.json' }, + { packageFile: 'backend/package.json' }, + { packageFile: 'packages/foo/package.js' }, + ]; config.api.getFileContent.mockReturnValueOnce('old content 1'); config.api.getFileContent.mockReturnValueOnce('old content 2'); + config.api.getFileContent.mockReturnValueOnce('old content 3'); packageJsonHelper.setNewValue.mockReturnValueOnce('old content 1'); packageJsonHelper.setNewValue.mockReturnValueOnce('new content 2'); + packageJsHelper.setNewValue.mockReturnValueOnce('old content 3'); const res = await getUpdatedPackageFiles(config); expect(res).toHaveLength(1); }); diff --git a/test/workers/branch/package-js.spec.js b/test/workers/branch/package-js.spec.js new file mode 100644 index 0000000000..3dc3ddf421 --- /dev/null +++ b/test/workers/branch/package-js.spec.js @@ -0,0 +1,49 @@ +const fs = require('fs'); +const path = require('path'); +const packageJs = require('../../../lib/workers/branch/package-js'); +const logger = require('../../_fixtures/logger'); + +function readFixture(fixture) { + return fs.readFileSync( + path.resolve(__dirname, `../../_fixtures/meteor/${fixture}`), + 'utf8' + ); +} + +const input01Content = readFixture('package-1.js'); +const input02Content = readFixture('package-2.js'); + +describe('workers/branch/package-js', () => { + describe('.setNewValue(currentFileContent, depName, currentVersion, newVersion, logger)', () => { + it('replaces a dependency value', () => { + const testContent = packageJs.setNewValue( + input01Content, + 'xmldom', + '0.1.19', + '0.22.1', + logger + ); + expect(testContent).toMatchSnapshot(); + }); + it('handles alternative quotes and white space', () => { + const testContent = packageJs.setNewValue( + input02Content, + 'xmldom', + '0.1.19', + '0.22.1', + logger + ); + expect(testContent).toMatchSnapshot(); + }); + it('handles the case where the desired version is already supported', () => { + const testContent = packageJs.setNewValue( + input01Content, + 'query-string', + '0.2.0', + '0.2.0', + logger + ); + testContent.should.equal(input01Content); + }); + }); +}); diff --git a/test/workers/dep-type/index.spec.js b/test/workers/dep-type/index.spec.js index 65860a0271..025e19c862 100644 --- a/test/workers/dep-type/index.spec.js +++ b/test/workers/dep-type/index.spec.js @@ -1,3 +1,5 @@ +const path = require('path'); +const fs = require('fs'); const packageJson = require('../../../lib/workers/dep-type/package-json'); const pkgWorker = require('../../../lib/workers/package/index'); const depTypeWorker = require('../../../lib/workers/dep-type/index'); @@ -14,6 +16,7 @@ describe('lib/workers/dep-type/index', () => { let config; beforeEach(() => { config = { + packageFile: 'package.json', ignoreDeps: ['a', 'b'], monorepoPackages: ['e'], }; @@ -46,6 +49,15 @@ describe('lib/workers/dep-type/index', () => { const res = await depTypeWorker.renovateDepType({}, config); expect(res).toHaveLength(2); }); + it('returns upgrades for meteor', async () => { + config.packageFile = 'package.js'; + const content = fs.readFileSync( + path.resolve('test/_fixtures/meteor/package-1.js'), + 'utf8' + ); + const res = await depTypeWorker.renovateDepType(content, config); + expect(res).toHaveLength(6); + }); }); describe('getDepConfig(depTypeConfig, dep)', () => { const depTypeConfig = { diff --git a/test/workers/package-file/index.spec.js b/test/workers/package-file/index.spec.js index 453f1cd484..23de2716c0 100644 --- a/test/workers/package-file/index.spec.js +++ b/test/workers/package-file/index.spec.js @@ -40,4 +40,29 @@ describe('packageFileWorker', () => { expect(res).toHaveLength(1); }); }); + describe('renovateMeteorPackageFile(config)', () => { + let config; + beforeEach(() => { + config = { + ...defaultConfig, + api: { + getFileContent: jest.fn(), + }, + packageFile: 'package.js', + repoIsOnboarded: true, + logger, + }; + depTypeWorker.renovateDepType.mockReturnValue([]); + }); + it('returns empty if disabled', async () => { + config.enabled = false; + const res = await packageFileWorker.renovateMeteorPackageFile(config); + expect(res).toEqual([]); + }); + it('returns upgrades', async () => { + depTypeWorker.renovateDepType.mockReturnValueOnce([{}, {}]); + const res = await packageFileWorker.renovateMeteorPackageFile(config); + expect(res).toHaveLength(2); + }); + }); }); diff --git a/test/workers/repository/__snapshots__/apis.spec.js.snap b/test/workers/repository/__snapshots__/apis.spec.js.snap index 13465b626c..d21d5fc421 100644 --- a/test/workers/repository/__snapshots__/apis.spec.js.snap +++ b/test/workers/repository/__snapshots__/apis.spec.js.snap @@ -34,6 +34,13 @@ Array [ ] `; +exports[`workers/repository/apis detectPackageFiles(config) finds meteor package files 1`] = ` +Array [ + "package.json", + "modules/something/package.js", +] +`; + exports[`workers/repository/apis detectPackageFiles(config) ignores node modules 1`] = ` Array [ "package.json", @@ -100,5 +107,8 @@ Array [ "hasYarnLock": false, "packageFile": "a/package.json", }, + Object { + "packageFile": "module/package.js", + }, ] `; diff --git a/test/workers/repository/apis.spec.js b/test/workers/repository/apis.spec.js index bada3eef81..b8a04c5699 100644 --- a/test/workers/repository/apis.spec.js +++ b/test/workers/repository/apis.spec.js @@ -222,6 +222,9 @@ describe('workers/repository/apis', () => { 'backend/package.json', ]), }, + meteor: { + enabled: false, + }, logger, warnings: [], }; @@ -229,8 +232,28 @@ describe('workers/repository/apis', () => { expect(res).toMatchObject(config); expect(res.packageFiles).toMatchSnapshot(); }); + it('finds meteor package files', async () => { + const config = { + api: { + findFilePaths: jest.fn(), + }, + meteor: { + enabled: true, + }, + logger, + warnings: [], + }; + config.api.findFilePaths.mockReturnValueOnce(['package.json']); + config.api.findFilePaths.mockReturnValueOnce([ + 'modules/something/package.js', + ]); + const res = await apis.detectPackageFiles(config); + expect(res).toMatchObject(config); + expect(res.packageFiles).toMatchSnapshot(); + }); it('ignores node modules', async () => { const config = { + ...defaultConfig, ignorePaths: ['node_modules/'], api: { findFilePaths: jest.fn(() => [ @@ -248,6 +271,7 @@ describe('workers/repository/apis', () => { }); it('defaults to package.json if found', async () => { const config = { + ...defaultConfig, api: { findFilePaths: jest.fn(() => []), getFileJson: jest.fn(() => ({})), @@ -260,6 +284,7 @@ describe('workers/repository/apis', () => { }); it('returns empty if package.json not found', async () => { const config = { + ...defaultConfig, api: { findFilePaths: jest.fn(() => []), getFileJson: jest.fn(() => null), @@ -287,6 +312,7 @@ describe('workers/repository/apis', () => { expect(res.packageFiles).toEqual([]); }); it('includes files with content', async () => { + config.packageFiles.push('module/package.js'); config.api.getFileJson.mockReturnValueOnce({ renovate: {}, workspaces: [], @@ -299,7 +325,7 @@ describe('workers/repository/apis', () => { config.api.getFileContent.mockReturnValueOnce(null); config.api.getFileContent.mockReturnValueOnce(null); const res = await apis.resolvePackageFiles(config); - expect(res.packageFiles).toHaveLength(2); + expect(res.packageFiles).toHaveLength(3); expect(res.packageFiles).toMatchSnapshot(); }); }); diff --git a/test/workers/repository/upgrades.spec.js b/test/workers/repository/upgrades.spec.js index 1563b1eb8a..0adbcc7553 100644 --- a/test/workers/repository/upgrades.spec.js +++ b/test/workers/repository/upgrades.spec.js @@ -35,13 +35,14 @@ describe('workers/repository/upgrades', () => { packageFile: 'backend/package.json', }, { - packageFile: 'frontend/package.json', + packageFile: 'frontend/package.js', }, ]; packageFileWorker.renovatePackageFile.mockReturnValueOnce(['a']); packageFileWorker.renovatePackageFile.mockReturnValueOnce(['b', 'c']); + packageFileWorker.renovateMeteorPackageFile.mockReturnValueOnce(['d']); const res = await upgrades.determineRepoUpgrades(config); - expect(res.length).toBe(3); + expect(res).toHaveLength(4); }); }); describe('generateConfig(branchUpgrades)', () => { -- GitLab