diff --git a/lib/config/definitions.js b/lib/config/definitions.js index bf73c78ba023d79260dbeae7214001da7d459b36..7f7865dfbd2a19be67ed7bd6bfb0d76054f18241 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1652,6 +1652,21 @@ const options = [ mergeable: true, cli: false, }, + { + name: 'homebrew', + releaseStatus: 'beta', + description: 'Configuration object for homebrew', + stage: 'package', + type: 'object', + default: { + enabled: true, + commitMessageTopic: 'Homebrew Formula {{depName}}', + managerBranchPrefix: 'homebrew-', + fileMatch: ['^Formula/[^/]+[.]rb$'], + }, + mergeable: true, + cli: false, + }, { name: 'hostRules', description: 'Host rules/configuration including credentials', diff --git a/lib/manager/homebrew/extract.js b/lib/manager/homebrew/extract.js new file mode 100644 index 0000000000000000000000000000000000000000..acd417bcd1126f51b62ab060882ee8ca682ea5bc --- /dev/null +++ b/lib/manager/homebrew/extract.js @@ -0,0 +1,207 @@ +const semver = require('../../versioning/semver'); +const { skip, isSpace, removeComments } = require('./util'); + +module.exports = { + extractPackageFile, + parseUrlPath, +}; + +// TODO: Maybe check if quotes/double-quotes are balanced +function extractPackageFile(content) { + logger.trace('extractPackageFile()'); + /* + 1. match "class className < Formula" + 2. extract className + 3. extract url field (get depName from url) + 4. extract sha256 field + */ + const cleanContent = removeComments(content); + const className = extractClassName(cleanContent); + if (!className) { + logger.debug('Invalid class definition'); + return null; + } + const url = extractUrl(cleanContent); + if (!url) { + logger.debug('Invalid URL field'); + } + const urlPathResult = parseUrlPath(url); + let skipReason; + let currentValue = null; + let ownerName = null; + let repoName = null; + if (urlPathResult) { + currentValue = urlPathResult.currentValue; + ownerName = urlPathResult.ownerName; + repoName = urlPathResult.repoName; + } else { + logger.debug('Error: Unsupported URL field'); + skipReason = 'unsupported-url'; + } + const sha256 = extractSha256(cleanContent); + if (!sha256 || sha256.length !== 64) { + logger.debug('Error: Invalid sha256 field'); + skipReason = 'invalid-sha256'; + } + const dep = { + depName: `${ownerName}/${repoName}`, + url, + sha256, + ownerName, + repoName, + currentValue, + datasource: 'github', + }; + if (skipReason) { + dep.skipReason = skipReason; + if (skipReason === 'unsupported-url') { + dep.depName = className; + dep.datasource = null; + } + } + const deps = [dep]; + return { deps }; +} + +function extractSha256(content) { + const sha256RegExp = /(^|\s)sha256(\s)/; + let i = content.search(sha256RegExp); + if (isSpace(content[i])) { + i += 1; + } + return parseSha256(i, content); +} + +function parseSha256(idx, content) { + let i = idx; + i += 'sha256'.length; + i = skip(i, content, c => { + return isSpace(c); + }); + if (content[i] !== '"' && content[i] !== "'") { + return null; + } + i += 1; + let j = i; + j = skip(i, content, c => { + return c !== '"' && c !== "'"; + }); + const sha256 = content.slice(i, j); + return sha256; +} + +function extractUrl(content) { + const urlRegExp = /(^|\s)url(\s)/; + let i = content.search(urlRegExp); + // content.search() returns -1 if not found + if (i === -1) { + return null; + } + /* istanbul ignore else */ + if (isSpace(content[i])) { + i += 1; + } + return parseUrl(i, content); +} + +function parseUrlPath(urlStr) { + if (!urlStr) { + return null; + } + try { + const url = new URL(urlStr); + if (url.hostname !== 'github.com') { + return null; + } + let s = url.pathname.split('/'); + s = s.filter(val => val); + const ownerName = s[0]; + const repoName = s[1]; + let currentValue; + if (s[2] === 'archive') { + currentValue = s[3]; + const targz = currentValue.slice( + currentValue.length - 7, + currentValue.length + ); + if (targz === '.tar.gz') { + currentValue = currentValue.substring(0, currentValue.length - 7); + } + } else if (s[2] === 'releases' && s[3] === 'download') { + currentValue = s[4]; + } + if (!currentValue) { + return null; + } + if (!semver.isValid(currentValue)) { + return null; + } + return { currentValue, ownerName, repoName }; + } catch (_) { + return null; + } +} + +function parseUrl(idx, content) { + let i = idx; + i += 'url'.length; + i = skip(i, content, c => { + return isSpace(c); + }); + const chr = content[i]; + if (chr !== '"' && chr !== "'") { + return null; + } + i += 1; + let j = i; + j = skip(i, content, c => { + return c !== '"' && c !== "'" && !isSpace(c); + }); + const url = content.slice(i, j); + return url; +} + +function extractClassName(content) { + const classRegExp = /(^|\s)class\s/; + let i = content.search(classRegExp); + if (isSpace(content[i])) { + i += 1; + } + return parseClassHeader(i, content); +} + +/* This function parses the "class className < Formula" header + and returns the className and index of the character just after the header */ +function parseClassHeader(idx, content) { + let i = idx; + i += 'class'.length; + i = skip(i, content, c => { + return isSpace(c); + }); + // Skip all non space and non '<' characters + let j = skip(i, content, c => { + return !isSpace(c) && c !== '<'; + }); + const className = content.slice(i, j); + i = j; + // Skip spaces + i = skip(i, content, c => { + return isSpace(c); + }); + if (content[i] === '<') { + i += 1; + } else { + return null; + } // Skip spaces + i = skip(i, content, c => { + return isSpace(c); + }); + // Skip non-spaces + j = skip(i, content, c => { + return !isSpace(c); + }); + if (content.slice(i, j) !== 'Formula') { + return null; + } + return className; +} diff --git a/lib/manager/homebrew/index.js b/lib/manager/homebrew/index.js new file mode 100644 index 0000000000000000000000000000000000000000..749cae6f4db0ec27bb7cde5845780752ce144ac9 --- /dev/null +++ b/lib/manager/homebrew/index.js @@ -0,0 +1,7 @@ +const { extractPackageFile } = require('./extract'); +const { updateDependency } = require('./update'); + +module.exports = { + extractPackageFile, + updateDependency, +}; diff --git a/lib/manager/homebrew/update.js b/lib/manager/homebrew/update.js new file mode 100644 index 0000000000000000000000000000000000000000..201823405ea6bea8d36bf78512eec673c827be8d --- /dev/null +++ b/lib/manager/homebrew/update.js @@ -0,0 +1,183 @@ +const got = require('got'); +const crypto = require('crypto'); +const { coerce } = require('semver'); +const { parseUrlPath } = require('./extract'); +const { skip, isSpace, removeComments } = require('./util'); + +module.exports = { + updateDependency, +}; + +// TODO: Refactor +async function updateDependency(content, upgrade) { + logger.trace('updateDependency()'); + /* + 1. Update url field + 2. Update sha256 field + */ + let newContent = content; + let newUrl; + let file; + // Example urls: + // "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + // "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" + const oldParsedUrlPath = parseUrlPath(upgrade.url); + if (!oldParsedUrlPath) { + logger.debug( + `Failed to update - upgrade.url is invalid ${upgrade.depName}` + ); + return content; + } + try { + newUrl = `https://github.com/${upgrade.ownerName}/${ + upgrade.repoName + }/releases/download/${upgrade.newValue}/${upgrade.repoName}-${coerce( + upgrade.newValue + )}.tar.gz`; + file = (await got(newUrl, { encoding: null })).body; + } catch (errOuter) { + logger.debug( + `Failed to download release download for ${ + upgrade.depName + } - trying archive instead` + ); + try { + newUrl = `https://github.com/${upgrade.ownerName}/${ + upgrade.repoName + }/archive/${upgrade.newValue}.tar.gz`; + file = (await got(newUrl, { encoding: null })).body; + } catch (errInner) { + logger.debug( + `Failed to download archive download for ${ + upgrade.depName + } - update failed` + ); + return content; + } + } + const newParsedUrlPath = parseUrlPath(newUrl); + if (!newParsedUrlPath) { + logger.debug(`Failed to update url for dependency ${upgrade.depName}`); + return content; + } + if (upgrade.newValue !== newParsedUrlPath.currentValue) { + logger.debug(`Failed to update url for dependency ${upgrade.depName}`); + return content; + } + const newSha256 = crypto + .createHash('sha256') + .update(file) + .digest('hex'); + newContent = updateUrl(content, upgrade.url, newUrl); + if (!newContent) { + logger.debug(`Failed to update url for dependency ${upgrade.depName}`); + return content; + } + newContent = updateSha256(newContent, upgrade.sha256, newSha256); + if (!newContent) { + logger.debug(`Failed to update sha256 for dependency ${upgrade.depName}`); + return content; + } + return newContent; +} + +function updateUrl(content, oldUrl, newUrl) { + const urlRegExp = /(^|\s)url(\s)/; + let i = content.search(urlRegExp); + if (i === -1) { + return null; + } + if (isSpace(content[i])) { + i += 1; + } + let newContent = replaceUrl(i, content, oldUrl, newUrl); + const testContent = getUrlTestContent(content, oldUrl, newUrl); + if (!newContent || !testContent) { + return null; + } + while (removeComments(newContent) !== testContent) { + i += 'url'.length; + i += content.substring(i).search(urlRegExp); + if (isSpace(content[i])) { + i += 1; + } + newContent = replaceUrl(i, content, oldUrl, newUrl); + } + return newContent; +} + +function getUrlTestContent(content, oldUrl, newUrl) { + const urlRegExp = /(^|\s)url(\s)/; + const cleanContent = removeComments(content); + let j = cleanContent.search(urlRegExp); + if (isSpace(cleanContent[j])) { + j += 1; + } + const testContent = replaceUrl(j, cleanContent, oldUrl, newUrl); + return testContent; +} + +function replaceUrl(idx, content, oldUrl, newUrl) { + let i = idx; + i += 'url'.length; + i = skip(i, content, c => isSpace(c)); + const chr = content[i]; + if (chr !== '"' && chr !== "'") { + return null; + } + i += 1; + const newContent = + content.substring(0, i) + content.substring(i).replace(oldUrl, newUrl); + return newContent; +} + +function updateSha256(content, oldSha256, newSha256) { + const sha256RegExp = /(^|\s)sha256(\s)/; + let i = content.search(sha256RegExp); + if (i === -1) { + return null; + } + if (isSpace(content[i])) { + i += 1; + } + let newContent = replaceSha256(i, content, oldSha256, newSha256); + const testContent = getSha256TestContent(content, oldSha256, newSha256); + if (!newContent || !testContent) { + return null; + } + while (removeComments(newContent) !== testContent) { + i += 'sha256'.length; + i += content.substring(i).search(sha256RegExp); + if (isSpace(content[i])) { + i += 1; + } + newContent = replaceSha256(i, content, oldSha256, newSha256); + } + return newContent; +} + +function getSha256TestContent(content, oldSha256, newSha256) { + const sha256RegExp = /(^|\s)sha256(\s)/; + const cleanContent = removeComments(content); + let j = cleanContent.search(sha256RegExp); + if (isSpace(cleanContent[j])) { + j += 1; + } + const testContent = replaceSha256(j, cleanContent, oldSha256, newSha256); + return testContent; +} + +function replaceSha256(idx, content, oldSha256, newSha256) { + let i = idx; + i += 'sha256'.length; + i = skip(i, content, c => isSpace(c)); + const chr = content[i]; + if (chr !== '"' && chr !== "'") { + return null; + } + i += 1; + const newContent = + content.substring(0, i) + + content.substring(i).replace(oldSha256, newSha256); + return newContent; +} diff --git a/lib/manager/homebrew/util.js b/lib/manager/homebrew/util.js new file mode 100644 index 0000000000000000000000000000000000000000..bb52b1af02b2240d226b802a180a33a36b6af595 --- /dev/null +++ b/lib/manager/homebrew/util.js @@ -0,0 +1,69 @@ +module.exports = { + skip, + isSpace, + removeComments, +}; + +function skip(idx, content, cond) { + let i = idx; + while (i < content.length) { + if (!cond(content[i])) { + return i; + } + i += 1; + } + return i; +} + +function isSpace(c) { + return /\s/.test(c); +} + +function removeComments(content) { + let newContent = removeLineComments(content); + newContent = removeMultiLineComments(newContent); + return newContent; +} + +// Remove line comments starting with # +function removeLineComments(content) { + let newContent = ''; + let comment = false; + for (let i = 0; i < content.length; i += 1) { + const c = content[i]; + if (c === '#') { + comment = true; + } + if (comment) { + if (c === '\n') { + comment = false; + } + } + if (!comment) { + newContent += c; + } + } + return newContent; +} + +// Remove multi-line comments enclosed between =begin and =end +function removeMultiLineComments(content) { + const beginRegExp = /(^|\n)=begin\s/; + const endRegExp = /(^|\n)=end\s/; + let newContent = content; + let i = newContent.search(beginRegExp); + let j = newContent.search(endRegExp); + while (i !== -1 && j !== -1) { + if (newContent[i] === '\n') { + i += 1; + } + if (newContent[j] === '\n') { + j += 1; + } + j += '=end'.length; + newContent = newContent.substring(0, i) + newContent.substring(j); + i = newContent.search(beginRegExp); + j = newContent.search(endRegExp); + } + return newContent; +} diff --git a/lib/manager/index.js b/lib/manager/index.js index 6f0c41e100d961ac258307fbfebc0ce195c29b58..dd21164895812dcc45c3dfbb57b2345ddfd148ec 100644 --- a/lib/manager/index.js +++ b/lib/manager/index.js @@ -28,6 +28,7 @@ const managerList = [ 'terraform', 'travis', 'ruby-version', + 'homebrew', ]; const managers = {}; for (const manager of managerList) { diff --git a/renovate-schema.json b/renovate-schema.json index ac1b83caaa6d3b969075c5da7dc6e13a00f6acbc..b869e7581f8c1f1d1a6e4649eb3a008a51ba155a 100644 --- a/renovate-schema.json +++ b/renovate-schema.json @@ -1136,6 +1136,17 @@ }, "$ref": "#" }, + "homebrew": { + "description": "Configuration object for homebrew", + "type": "object", + "default": { + "enabled": true, + "commitMessageTopic": "Homebrew Formula {{depName}}", + "managerBranchPrefix": "homebrew-", + "fileMatch": ["^Formula/[^/]+[.]rb$"] + }, + "$ref": "#" + }, "hostRules": { "description": "Host rules/configuration including credentials", "type": "array" diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap index e54d4067e587428af702c98c2b2bacd0f7ba4619..925298d2d17c4232df4f98ca35edb99c2ff3ad76 100644 --- a/test/config/__snapshots__/validation.spec.js.snap +++ b/test/config/__snapshots__/validation.spec.js.snap @@ -87,7 +87,7 @@ Array [ "depName": "Configuration Error", "message": "packageRules: You have included an unsupported manager in a package rule. Your list: foo. - Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, terraform, travis, ruby-version).", + Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, terraform, travis, ruby-version, homebrew).", }, ] `; diff --git a/test/manager/homebrew/__snapshots__/extract.spec.js.snap b/test/manager/homebrew/__snapshots__/extract.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..b0f7e161fa00f980eb49e9caf074efd47cfb7f9b --- /dev/null +++ b/test/manager/homebrew/__snapshots__/extract.spec.js.snap @@ -0,0 +1,219 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`lib/manager/homebrew/extract extractPackageFile() extracts "archive" github dependency 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.8.2", + "datasource": "github", + "depName": "bazelbuild/bazel-watcher", + "ownerName": "bazelbuild", + "repoName": "bazel-watcher", + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() extracts "releases" github dependency 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.16.1", + "datasource": "github", + "depName": "aide/aide", + "ownerName": "aide", + "repoName": "aide", + "sha256": "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7", + "url": "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() handles no space before class header 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.8.2", + "datasource": "github", + "depName": "bazelbuild/bazel-watcher", + "ownerName": "bazelbuild", + "repoName": "bazel-watcher", + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips github dependency with wrong format 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Acmetool", + "ownerName": null, + "repoName": null, + "sha256": "6f2cf5cfb987a2df2f791c162209039804fd8fd12692da69f52153ec9668e9ca", + "skipReason": "unsupported-url", + "url": "https://github.com/hlandau/acme.git", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Ibazel", + "ownerName": null, + "repoName": null, + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "skipReason": "unsupported-url", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.not_tar.not_gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 2`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Ibazel", + "ownerName": null, + "repoName": null, + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "skipReason": "unsupported-url", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/vInvalid.version.2.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 3`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Ibazel", + "ownerName": null, + "repoName": null, + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "skipReason": "unsupported-url", + "url": null, + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 4`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Ibazel", + "ownerName": null, + "repoName": null, + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "skipReason": "unsupported-url", + "url": "invalid_url", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if sha256 field is invalid 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.8.2", + "datasource": "github", + "depName": "bazelbuild/bazel-watcher", + "ownerName": "bazelbuild", + "repoName": "bazel-watcher", + "sha256": "26f5125218fad2741d3caf937b0229", + "skipReason": "invalid-sha256", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if there is no sha256 field 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.8.2", + "datasource": "github", + "depName": "bazelbuild/bazel-watcher", + "ownerName": "bazelbuild", + "repoName": "bazel-watcher", + "sha256": null, + "skipReason": "invalid-sha256", + "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips if there is no url field 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Ibazel", + "ownerName": null, + "repoName": null, + "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4", + "skipReason": "unsupported-url", + "url": null, + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips sourceforge dependency 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Aalib", + "ownerName": null, + "repoName": null, + "sha256": "fbddda9230cf6ee2a4f5706b4b11e2190ae45f5eda1f0409dc4f99b35e0a70ee", + "skipReason": "unsupported-url", + "url": "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz", + }, + ], +} +`; + +exports[`lib/manager/homebrew/extract extractPackageFile() skips sourceforge dependency 2`] = ` +Object { + "deps": Array [ + Object { + "currentValue": null, + "datasource": null, + "depName": "Aap", + "ownerName": null, + "repoName": null, + "sha256": "3f53b2fc277756042449416150acc477f29de93692944f8a77e8cef285a1efd8", + "skipReason": "unsupported-url", + "url": "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip", + }, + ], +} +`; diff --git a/test/manager/homebrew/__snapshots__/update.spec.js.snap b/test/manager/homebrew/__snapshots__/update.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..c307d2d924d56960b4a46a04cb32e122f499e964 --- /dev/null +++ b/test/manager/homebrew/__snapshots__/update.spec.js.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/homebrew/update updates "archive" github dependency 1`] = ` +"# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the \\"License\\"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an \\"AS IS\\" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +=begin + url \\"https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz\\" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' +=end +# url \\"https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz\\" +# sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + +$sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'; +class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url \\"https://github.com/bazelbuild/bazel-watcher/releases/download/v0.9.3/bazel-watcher-0.9.3.tar.gz\\" + + # To generate run: + # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum + sha256 '9c96cc68155bd283282123413ecaafe52b12ea5f2585c3c383ce6141f779a58f' + + bottle :unneeded + + depends_on \\"bazelbuild/tap/bazel\\" => :build + + def install + system 'bazel', 'build', '--config=release', '--verbose_failures', '--experimental_platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64', '//ibazel:ibazel' + bin.install 'bazel-bin/ibazel/darwin_amd64_pure_stripped/ibazel' => 'ibazel' + end + + test do + # Since ibazel loops in most cases the quickest check of valididty + # I can think of is to get the version output which happens when + # invoked without any arguments. + system bin / 'ibazel' + end +end +" +`; + +exports[`manager/homebrew/update updates "releases" github dependency 1`] = ` +"=begin + url \\"https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz\\" + sha256 \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\" +=end +# url \\"https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz\\" +# sha256 \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\" + +$sha256 = \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\" +class Aide < Formula + desc \\"File and directory integrity checker\\" + homepage \\"https://aide.github.io/\\" + url \\"https://github.com/aide/aide/releases/download/v0.17.7/aide-0.17.7.tar.gz\\" + sha256 \\"337c78a56c8dde1a42eb767bf89f16eb3ee4207e4817954899a3f0f293c6ad6b\\" + + bottle do + cellar :any + sha256 \\"53b1dfabc76d6e54db56ec24f7f91b6cc9dcdd18210d17d2df92f86225fb9c9f\\" => :mojave + sha256 \\"79a2d4ce92526516891c844a4852161d39421f9dc31d2eba5ea0e48d79496053\\" => :high_sierra + sha256 \\"b626fcf7e52a0ea66fbed58bdc00cb08484f7bce8e84e61edf6740fbad7fabc5\\" => :sierra + end + + head do + url \\"https://github.com/aide/aide.git\\" + depends_on \\"autoconf\\" => :build + depends_on \\"automake\\" => :build + end + + depends_on \\"libgcrypt\\" + depends_on \\"libgpg-error\\" + depends_on \\"pcre\\" + + def install + system \\"sh\\", \\"./autogen.sh\\" if build.head? + + system \\"./configure\\", \\"--disable-lfs\\", + \\"--disable-static\\", + \\"--with-curl\\", + \\"--with-zlib\\", + \\"--sysconfdir=#{etc}\\", + \\"--prefix=#{prefix}\\" + + system \\"make\\", \\"install\\" + end + + test do + (testpath/\\"aide.conf\\").write <<~EOS + database = file:/var/lib/aide/aide.db + database_out = file:/var/lib/aide/aide.db.new + database_new = file:/var/lib/aide/aide.db.new + gzip_dbout = yes + summarize_changes = yes + grouped = yes + verbose = 7 + database_attrs = sha256 + /etc p+i+u+g+sha256 + EOS + system \\"#{bin}/aide\\", \\"--config-check\\", \\"-c\\", \\"aide.conf\\" + end +end +" +`; diff --git a/test/manager/homebrew/_fixtures/aalib.rb b/test/manager/homebrew/_fixtures/aalib.rb new file mode 100644 index 0000000000000000000000000000000000000000..3532c35eb2bd182eee404f47716e8168e8c8aabf --- /dev/null +++ b/test/manager/homebrew/_fixtures/aalib.rb @@ -0,0 +1,45 @@ +=begin + url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz" +=end +# url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz" + +class Aalib < Formula + desc "Portable ASCII art graphics library" + homepage "https://aa-project.sourceforge.io/aalib/" + url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz" + sha256 "fbddda9230cf6ee2a4f5706b4b11e2190ae45f5eda1f0409dc4f99b35e0a70ee" + revision 1 + + bottle do + cellar :any_skip_relocation + sha256 "a19ebbf86362d9a90900dd0b4013ebed778cd8681c0b3ed122c8bbaa04b11cbe" => :mojave + sha256 "b2c5467ff9182645676381967b8dc89878f88900b19bed34ef432fd3257aa2a0" => :high_sierra + sha256 "2c2d05720ca991422e4c27e3f770c29024c5197871cba67404f4e72a3cfaf002" => :sierra + sha256 "9b3f19e5da28fb682aeb1fe40f1747d1b532490dd50262978aaefcb7afbc8804" => :el_capitan + sha256 "9e08dd4e3545b05353f3158e4e756c20a301bef295b72183e1fd5fb1d6d8e897" => :yosemite + end + + # Fix malloc/stdlib issue on macOS + # Fix underquoted definition of AM_PATH_AALIB in aalib.m4 + patch do + url "https://raw.githubusercontent.com/Homebrew/formula-patches/6e23dfb/aalib/1.4rc5.patch" + sha256 "54aeff2adaea53902afc2660afb9534675b3ea522c767cbc24a5281080457b2c" + end + + def install + ENV.ncurses_define + system "./configure", "--disable-debug", + "--disable-dependency-tracking", + "--prefix=#{prefix}", + "--mandir=#{man}", + "--infodir=#{info}", + "--enable-shared=yes", + "--enable-static=yes", + "--without-x" + system "make", "install" + end + + test do + system "script", "-q", "/dev/null", bin/"aainfo" + end +end diff --git a/test/manager/homebrew/_fixtures/aap.rb b/test/manager/homebrew/_fixtures/aap.rb new file mode 100644 index 0000000000000000000000000000000000000000..486d648185da13bb9760424eccd82f07467bca00 --- /dev/null +++ b/test/manager/homebrew/_fixtures/aap.rb @@ -0,0 +1,35 @@ +=begin +url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip" +=end +# url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip" + +class Aap < Formula + desc "Make-like tool to download, build, and install software" + homepage "http://www.a-a-p.org" + url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip" + sha256 "3f53b2fc277756042449416150acc477f29de93692944f8a77e8cef285a1efd8" + + bottle do + cellar :any_skip_relocation + rebuild 1 + sha256 "533ff4c588ea7e7369570eb3ae7de191d9b62ed9161acbdeb0b07cb8d328aec7" => :mojave + sha256 "4fa43a4a0294b7e9040e13bf843e70fbeb189c2e505315dd574fbc9ed43bd060" => :high_sierra + sha256 "6e42400eb31e15dae56452b347925eb20faae1b996d94173b8711bd080e4a182" => :sierra + sha256 "32c30b38a37147754c5abfe9a801777b4a798af6dbcce2f15e1693c6027f0fbe" => :el_capitan + sha256 "15472b5a56a90d2d83c3ab24eba09e3644867857d8a7c547c82e6937beff3344" => :yosemite + sha256 "b141c07f091f90bd883148bf0e3c093d90fc0be7c4f8e7d07df9ae7cae684862" => :mavericks + end + + depends_on "python@2" # does not support Python 3 + + def install + # Aap is designed to install using itself + system "./aap", "install", "PREFIX=#{prefix}", "MANSUBDIR=share/man" + end + + test do + # A dummy target definition + (testpath/"main.aap").write("dummy:\n\t:print OK\n") + system "#{bin}/aap", "dummy" + end +end diff --git a/test/manager/homebrew/_fixtures/acmetool.rb b/test/manager/homebrew/_fixtures/acmetool.rb new file mode 100644 index 0000000000000000000000000000000000000000..c6d855447f06cc43e1ad620e6f65fbcd3e59ee01 --- /dev/null +++ b/test/manager/homebrew/_fixtures/acmetool.rb @@ -0,0 +1,195 @@ +require "language/go" + +=begin + url "https://github.com/hlandau/acme.git", +=end +# url "https://github.com/hlandau/acme.git", + +class Acmetool < Formula + desc "Automatic certificate acquisition tool for ACME (Let's Encrypt)" + homepage "https://github.com/hlandau/acme" + url "https://github.com/hlandau/acme.git", + :tag => "v0.0.67", + :revision => "221ea15246f0bbcf254b350bee272d43a1820285" + + bottle do + sha256 "6f2cf5cfb987a2df2f791c162209039804fd8fd12692da69f52153ec9668e9ca" => :mojave + sha256 "c4ff2b08c70560072307d64272f105bcd66c05983efbf1e278de9e5012047738" => :high_sierra + sha256 "7c77a51f12ec154cd5a82f066d547c70f8970a4c5046adf2ab99c600c930a9d5" => :sierra + sha256 "8f9a190bbda5a5cd209cf7f45bcdfff9c504a7e368458b435c78b1c25c8cb54b" => :el_capitan + end + + depends_on "go" => :build + + go_resource "github.com/alecthomas/template" do + url "https://github.com/alecthomas/template.git", + :revision => "a0175ee3bccc567396460bf5acd36800cb10c49c" + end + + go_resource "github.com/alecthomas/units" do + url "https://github.com/alecthomas/units.git", + :revision => "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" + end + + go_resource "github.com/coreos/go-systemd" do + url "https://github.com/coreos/go-systemd.git", + :revision => "cc4f39464dc797b91c8025330de585294c2a6950" + end + + go_resource "github.com/hlandau/buildinfo" do + url "https://github.com/hlandau/buildinfo.git", + :revision => "337a29b5499734e584d4630ce535af64c5fe7813" + end + + go_resource "github.com/hlandau/dexlogconfig" do + url "https://github.com/hlandau/dexlogconfig.git", + :revision => "244f29bd260884993b176cd14ef2f7631f6f3c18" + end + + go_resource "github.com/hlandau/goutils" do + url "https://github.com/hlandau/goutils.git", + :revision => "0cdb66aea5b843822af6fdffc21286b8fe8379c4" + end + + go_resource "github.com/hlandau/xlog" do + url "https://github.com/hlandau/xlog.git", + :revision => "197ef798aed28e08ed3e176e678fda81be993a31" + end + + go_resource "github.com/jmhodges/clock" do + url "https://github.com/jmhodges/clock.git", + :revision => "880ee4c335489bc78d01e4d0a254ae880734bc15" + end + + go_resource "github.com/mattn/go-isatty" do + url "https://github.com/mattn/go-isatty.git", + :revision => "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" + end + + go_resource "github.com/mattn/go-runewidth" do + url "https://github.com/mattn/go-runewidth.git", + :revision => "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d" + end + + go_resource "github.com/mitchellh/go-wordwrap" do + url "https://github.com/mitchellh/go-wordwrap.git", + :revision => "ad45545899c7b13c020ea92b2072220eefad42b8" + end + + go_resource "github.com/ogier/pflag" do + url "https://github.com/ogier/pflag.git", + :revision => "45c278ab3607870051a2ea9040bb85fcb8557481" + end + + go_resource "github.com/peterhellberg/link" do + url "https://github.com/peterhellberg/link.git", + :revision => "8768c6d4dc563b4a09f58ecda04997024452c057" + end + + go_resource "github.com/satori/go.uuid" do + url "https://github.com/satori/go.uuid.git", + :revision => "36e9d2ebbde5e3f13ab2e25625fd453271d6522e" + end + + go_resource "github.com/shiena/ansicolor" do + url "https://github.com/shiena/ansicolor.git", + :revision => "a422bbe96644373c5753384a59d678f7d261ff10" + end + + go_resource "golang.org/x/crypto" do + url "https://go.googlesource.com/crypto.git", + :revision => "a6600008915114d9c087fad9f03d75087b1a74df" + end + + go_resource "golang.org/x/net" do + url "https://go.googlesource.com/net.git", + :revision => "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + end + + go_resource "golang.org/x/sys" do + url "https://go.googlesource.com/sys.git", + :revision => "2c42eef0765b9837fbdab12011af7830f55f88f0" + end + + go_resource "golang.org/x/text" do + url "https://go.googlesource.com/text.git", + :revision => "e19ae1496984b1c655b8044a65c0300a3c878dd3" + end + + go_resource "gopkg.in/alecthomas/kingpin.v2" do + url "https://gopkg.in/alecthomas/kingpin.v2.git", + :revision => "947dcec5ba9c011838740e680966fd7087a71d0d" + end + + go_resource "gopkg.in/cheggaaa/pb.v1" do + url "https://gopkg.in/cheggaaa/pb.v1.git", + :revision => "43d64de27312b32812ca7e994fa0bb03ccf08fdf" + end + + go_resource "gopkg.in/hlandau/configurable.v1" do + url "https://gopkg.in/hlandau/configurable.v1.git", + :revision => "41496864a1fe3e0fef2973f22372b755d2897402" + end + + go_resource "gopkg.in/hlandau/easyconfig.v1" do + url "https://gopkg.in/hlandau/easyconfig.v1.git", + :revision => "7589cb96edce2f94f8c1e6eb261f8c2b06220fe7" + end + + go_resource "gopkg.in/hlandau/service.v2" do + url "https://gopkg.in/hlandau/service.v2.git", + :revision => "b64b3467ebd16f64faec1640c25e318efc0c0d7b" + end + + go_resource "gopkg.in/hlandau/svcutils.v1" do + url "https://gopkg.in/hlandau/svcutils.v1.git", + :revision => "c25dac49e50cbbcbef8c81b089f56156f4067729" + end + + go_resource "gopkg.in/square/go-jose.v1" do + url "https://gopkg.in/square/go-jose.v1.git", + :revision => "aa2e30fdd1fe9dd3394119af66451ae790d50e0d" + end + + go_resource "gopkg.in/tylerb/graceful.v1" do + url "https://gopkg.in/tylerb/graceful.v1.git", + :revision => "4654dfbb6ad53cb5e27f37d99b02e16c1872fbbb" + end + + go_resource "gopkg.in/yaml.v2" do + url "https://gopkg.in/yaml.v2.git", + :revision => "d670f9405373e636a5a2765eea47fac0c9bc91a4" + end + + def install + ENV["GOPATH"] = buildpath + + (buildpath/"src/github.com/hlandau").mkpath + ln_sf buildpath, buildpath/"src/github.com/hlandau/acme" + Language::Go.stage_deps resources, buildpath/"src" + + cd "cmd/acmetool" do + # https://github.com/hlandau/acme/blob/master/_doc/PACKAGING-PATHS.md + ldflags = %W[ + -X github.com/hlandau/acme/storage.RecommendedPath=#{var}/lib/acmetool + -X github.com/hlandau/acme/hooks.DefaultPath=#{lib}/hooks + -X github.com/hlandau/acme/responder.StandardWebrootPath=#{var}/run/acmetool/acme-challenge + #{Utils.popen_read("#{buildpath}/src/github.com/hlandau/buildinfo/gen")} + ] + system "go", "build", "-o", bin/"acmetool", "-ldflags", ldflags.join(" ") + end + + (man8/"acmetool.8").write Utils.popen_read(bin/"acmetool", "--help-man") + + doc.install Dir["_doc/*"] + end + + def post_install + (var/"lib/acmetool").mkpath + (var/"run/acmetool").mkpath + end + + test do + assert_match version.to_s, shell_output("#{bin}/acmetool --version", 2) + end +end diff --git a/test/manager/homebrew/_fixtures/aide.rb b/test/manager/homebrew/_fixtures/aide.rb new file mode 100644 index 0000000000000000000000000000000000000000..5e910779af3ac7b7e049eb4388a332e019dd513a --- /dev/null +++ b/test/manager/homebrew/_fixtures/aide.rb @@ -0,0 +1,59 @@ +=begin + url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" + sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7" +=end +# url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" +# sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7" + +$sha256 = "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7" +class Aide < Formula + desc "File and directory integrity checker" + homepage "https://aide.github.io/" + url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz" + sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7" + + bottle do + cellar :any + sha256 "53b1dfabc76d6e54db56ec24f7f91b6cc9dcdd18210d17d2df92f86225fb9c9f" => :mojave + sha256 "79a2d4ce92526516891c844a4852161d39421f9dc31d2eba5ea0e48d79496053" => :high_sierra + sha256 "b626fcf7e52a0ea66fbed58bdc00cb08484f7bce8e84e61edf6740fbad7fabc5" => :sierra + end + + head do + url "https://github.com/aide/aide.git" + depends_on "autoconf" => :build + depends_on "automake" => :build + end + + depends_on "libgcrypt" + depends_on "libgpg-error" + depends_on "pcre" + + def install + system "sh", "./autogen.sh" if build.head? + + system "./configure", "--disable-lfs", + "--disable-static", + "--with-curl", + "--with-zlib", + "--sysconfdir=#{etc}", + "--prefix=#{prefix}" + + system "make", "install" + end + + test do + (testpath/"aide.conf").write <<~EOS + database = file:/var/lib/aide/aide.db + database_out = file:/var/lib/aide/aide.db.new + database_new = file:/var/lib/aide/aide.db.new + gzip_dbout = yes + summarize_changes = yes + grouped = yes + verbose = 7 + database_attrs = sha256 + /etc p+i+u+g+sha256 + EOS + system "#{bin}/aide", "--config-check", "-c", "aide.conf" + end +end diff --git a/test/manager/homebrew/_fixtures/ibazel.rb b/test/manager/homebrew/_fixtures/ibazel.rb new file mode 100644 index 0000000000000000000000000000000000000000..edac288dbf4d1b258a4caf4adca313ce80a0ffee --- /dev/null +++ b/test/manager/homebrew/_fixtures/ibazel.rb @@ -0,0 +1,47 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +=begin + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' +=end +# url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" +# sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + +$sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'; +class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + + # To generate run: + # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + + bottle :unneeded + + depends_on "bazelbuild/tap/bazel" => :build + + def install + system 'bazel', 'build', '--config=release', '--verbose_failures', '--experimental_platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64', '//ibazel:ibazel' + bin.install 'bazel-bin/ibazel/darwin_amd64_pure_stripped/ibazel' => 'ibazel' + end + + test do + # Since ibazel loops in most cases the quickest check of valididty + # I can think of is to get the version output which happens when + # invoked without any arguments. + system bin / 'ibazel' + end +end diff --git a/test/manager/homebrew/extract.spec.js b/test/manager/homebrew/extract.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..f5bd5fd101c101234af58d8cafe93b464c28b80c --- /dev/null +++ b/test/manager/homebrew/extract.spec.js @@ -0,0 +1,185 @@ +const fs = require('fs'); +const { extractPackageFile } = require('../../../lib/manager/homebrew/extract'); + +const aalib = fs.readFileSync( + 'test/manager/homebrew/_fixtures/aalib.rb', + 'utf8' +); +const aap = fs.readFileSync('test/manager/homebrew/_fixtures/aap.rb', 'utf8'); +const acmetool = fs.readFileSync( + 'test/manager/homebrew/_fixtures/acmetool.rb', + 'utf8' +); +const aide = fs.readFileSync('test/manager/homebrew/_fixtures/aide.rb', 'utf8'); +const ibazel = fs.readFileSync( + 'test/manager/homebrew/_fixtures/ibazel.rb', + 'utf8' +); + +describe('lib/manager/homebrew/extract', () => { + describe('extractPackageFile()', () => { + it('skips sourceforge dependency', () => { + const res = extractPackageFile(aalib); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips sourceforge dependency', () => { + const res = extractPackageFile(aap); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips github dependency with wrong format', () => { + const res = extractPackageFile(acmetool); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('extracts "releases" github dependency', () => { + const res = extractPackageFile(aide); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBeUndefined(); + expect(res).toMatchSnapshot(); + }); + it('extracts "archive" github dependency', () => { + const res = extractPackageFile(ibazel); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBeUndefined(); + expect(res).toMatchSnapshot(); + }); + it('handles no space before class header', () => { + const content = `class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBeUndefined(); + expect(res).toMatchSnapshot(); + }); + it('returns null for invalid class header', () => { + const content = ` + class Ibazel !?# Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + expect(extractPackageFile(content)).toBeNull(); + }); + it('returns null for invalid class header', () => { + const content = ` + class Ibazel < NotFormula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + expect(extractPackageFile(content)).toBeNull(); + }); + it('skips if there is no url field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + not_url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips if invalid url field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.not_tar.not_gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips if invalid url field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/vInvalid.version.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips if invalid url field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url ??https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips if invalid url field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "invalid_url" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('unsupported-url'); + expect(res).toMatchSnapshot(); + }); + it('skips if there is no sha256 field', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + not_sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('invalid-sha256'); + expect(res).toMatchSnapshot(); + }); + it('skips if sha256 field is invalid', () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b0229' + end + `; + const res = extractPackageFile(content); + expect(res).not.toBeNull(); + expect(res.deps[0].skipReason).toBe('invalid-sha256'); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/test/manager/homebrew/update.spec.js b/test/manager/homebrew/update.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6a3b39894fea38de21d18a2dc13a41f437dd36ab --- /dev/null +++ b/test/manager/homebrew/update.spec.js @@ -0,0 +1,237 @@ +const got = require('got'); +const fs = require('fs'); +const { updateDependency } = require('../../../lib/manager/homebrew/update'); + +jest.mock('got'); + +const aide = fs.readFileSync('test/manager/homebrew/_fixtures/aide.rb', 'utf8'); +const ibazel = fs.readFileSync( + 'test/manager/homebrew/_fixtures/ibazel.rb', + 'utf8' +); + +describe('manager/homebrew/update', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + it('updates "releases" github dependency', async () => { + const upgrade = { + currentValue: 'v0.16.1', + depName: 'Aide', + ownerName: 'aide', + repoName: 'aide', + sha256: + '0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7', + url: + 'https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz', + newValue: 'v0.17.7', + }; + got.mockReturnValueOnce({ body: 'some_content_1' }); + const newContent = await updateDependency(aide, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).not.toBe(aide); + expect(newContent).toMatchSnapshot(); + }); + it('updates "archive" github dependency', async () => { + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockReturnValueOnce({ body: 'some_content_2' }); + const newContent = await updateDependency(ibazel, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).not.toBe(ibazel); + expect(newContent).toMatchSnapshot(); + }); + it('returns unchanged content if got function throws errors', async () => { + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + throw new Error('Request failed'); + }); + const newContent = await updateDependency(ibazel, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(ibazel); + }); + it('returns unchanged content if url field in upgrade object is invalid', async () => { + const content = ibazel; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'invalid_url', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if repoName in upgrade object is invalid', async () => { + const content = ibazel; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'invalid/repo/name', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got + .mockImplementationOnce(() => { + throw Error('Request failed'); + }) + .mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if repoName in upgrade object is wrong', async () => { + const content = ibazel; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'wrong-version/archive/v10.2.3.tar.gz', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got + .mockImplementationOnce(() => { + throw Error('Request failed'); + }) + .mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if url field in Formula file is invalid', async () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url ???https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if url field in Formula file is missing', async () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if sha256 field in Formula file is invalid', async () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + sha256 ???26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4' + end + `; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); + it('returns unchanged content if sha256 field in Formula file is missing', async () => { + const content = ` + class Ibazel < Formula + desc 'IBazel is a tool for building Bazel targets when source files change.' + homepage 'https://github.com/bazelbuild/bazel-watcher' + url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz" + end + `; + const upgrade = { + currentValue: 'v0.8.2', + depName: 'Ibazel', + ownerName: 'bazelbuild', + repoName: 'bazel-watcher', + sha256: + '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4', + url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz', + newValue: 'v0.9.3', + }; + got.mockImplementationOnce(() => { + return { body: 'some_content' }; + }); + const newContent = await updateDependency(content, upgrade); + expect(newContent).not.toBeNull(); + expect(newContent).toBe(content); + }); +}); diff --git a/test/manager/homebrew/util.spec.js b/test/manager/homebrew/util.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4f4fea0b7fb1471c0e5d49222cb0ed96ce8e5dab --- /dev/null +++ b/test/manager/homebrew/util.spec.js @@ -0,0 +1,11 @@ +const { skip } = require('../../../lib/manager/homebrew/util'); + +describe('lib/manager/homebrew/util', () => { + describe('skip()', () => { + it('handles out of bounds case', () => { + const content = 'some content'; + const idx = content.length * 2; + expect(skip(idx, content, c => c === '!')).toBe(idx); + }); + }); +}); diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap index 31348bd03aa5ca48a8da802c6279438d195e5761..c801d4004405dae5cac8cfd72dfaebd62f241e59 100644 --- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap @@ -44,6 +44,9 @@ Object { "gradle-wrapper": Array [ Object {}, ], + "homebrew": Array [ + Object {}, + ], "kubernetes": Array [ Object {}, ], diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index d6aa77408b4f4197c50f27cecfa78b8d318c9c5a..dc00ef3cf6ecf90687bf412628f5cd12412c9410 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -303,6 +303,8 @@ By default, Renovate will "slugify" the groupName to determine the branch name. And then the branchName would be `renovate/eslint` instead. +## homebrew + ## hostRules Example for configuring `docker` auth: