diff --git a/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap index 096eb05ff041a03ab1b859dbb4f6a8a369023121..068fe046658f1a018068660c7cb198688a7e7cb6 100644 --- a/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap +++ b/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap @@ -8,11 +8,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "com_github_bitly_go-nsq", "depType": "go_repository", "managerData": { - "def": "go_repository( - name = "com_github_bitly_go-nsq", - importpath = "github.com/bitly/go-nsq", - tag = "v1.0.5" -)", + "idx": 0, }, "packageName": "github.com/bitly/go-nsq", }, @@ -25,11 +21,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depType": "go_repository", "digestOneAndOnly": true, "managerData": { - "def": "go_repository( - name = "com_github_google_uuid", - importpath = "github.com/google/uuid", - commit = "dec09d789f3dba190787f8b4454c7d3c936fed9e" -)", + "idx": 1, }, "packageName": "github.com/google/uuid", }, @@ -39,11 +31,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "com_gopkgin_mgo_v2", "depType": "go_repository", "managerData": { - "def": "go_repository( - name = "com_gopkgin_mgo_v2", - importpath = "gopkg.in/mgo.v2", - tag = "v2" -)", + "idx": 2, }, "packageName": "gopkg.in/mgo.v2", }, @@ -53,11 +41,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "build_bazel_rules_nodejs", "depType": "git_repository", "managerData": { - "def": "git_repository( - name = "build_bazel_rules_nodejs", - remote = "https://github.com/bazelbuild/rules_nodejs.git", - tag = "0.3.1", -)", + "idx": 3, }, "packageName": "bazelbuild/rules_nodejs", }, @@ -67,11 +51,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "build_bazel_rules_typescript", "depType": "git_repository", "managerData": { - "def": "git_repository( - name = "build_bazel_rules_typescript", - remote = "https://github.com/bazelbuild/rules_typescript.git", - tag = "0.6.1", -)", + "idx": 4, }, "packageName": "bazelbuild/rules_typescript", }, @@ -81,12 +61,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "distroless", "depType": "http_archive", "managerData": { - "def": "http_archive( - name="distroless", - sha256="f7a6ecfb8174a1dd4713ea3b21621072996ada7e8f1a69e6ae7581be137c6dd6", - strip_prefix="distroless-446923c3756ceeaa75888f52fcbdd48bb314fbf8", - urls=["https://github.com/GoogleContainerTools/distroless/archive/446923c3756ceeaa75888f52fcbdd48bb314fbf8.tar.gz"] -)", + "idx": 5, }, "packageName": "GoogleContainerTools/distroless", }, @@ -96,15 +71,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "bazel_toolchains", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "bazel_toolchains", - sha256 = "4b1468b254a572dbe134cc1fd7c6eab1618a72acd339749ea343bd8f55c3b7eb", - strip_prefix = "bazel-toolchains-d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz", - "https://github.com/bazelbuild/bazel-toolchains/archive/d665ccfa3e9c90fa789671bf4ef5f7c19c5715c4.tar.gz", - ], -)", + "idx": 6, }, "packageName": "bazelbuild/bazel-toolchains", }, @@ -114,13 +81,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "rules_nodejs", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "rules_nodejs", - sha256 = "5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064", - urls = [ - "https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz" - ], -)", + "idx": 7, }, "packageName": "bazelbuild/rules_nodejs", }, @@ -130,11 +91,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "io_bazel_rules_sass", "depType": "git_repository", "managerData": { - "def": "git_repository( - name = "io_bazel_rules_sass", - remote = "https://github.com/bazelbuild/rules_sass.git", - tag = "0.0.3", -)", + "idx": 8, }, "packageName": "bazelbuild/rules_sass", }, @@ -144,12 +101,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "com_github_bazelbuild_buildtools", "depType": "git_repository", "managerData": { - "def": "git_repository( - name = "com_github_bazelbuild_buildtools", - remote = "https://github.com/bazelbuild/buildtools.git", - # Note, this commit matches the version of buildifier in angular/ngcontainer - commit = "b3b620e8bcff18ed3378cd3f35ebeb7016d71f71", -)", + "idx": 9, }, "packageName": "bazelbuild/buildtools", }, @@ -159,11 +111,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "io_bazel_rules_go", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "io_bazel_rules_go", - url = "https://github.com/bazelbuild/rules_go/releases/download/0.7.1/rules_go-0.7.1.tar.gz", - sha256 = "341d5eacef704415386974bc82a1783a8b7ffbff2ab6ba02375e1ca20d9b031c", -)", + "idx": 10, }, "packageName": "bazelbuild/rules_go", }, @@ -173,15 +121,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "bazel_skylib", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "bazel_skylib", - sha256 = "b5f6abe419da897b7901f90cbab08af958b97a8f3575b0d3dd062ac7ce78541f", - strip_prefix = "bazel-skylib-0.5.0", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/archive/0.5.0.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/archive/0.5.0.tar.gz", - ], -)", + "idx": 11, }, "packageName": "bazelbuild/bazel-skylib", }, @@ -191,12 +131,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "distroless", "depType": "http_archive", "managerData": { - "def": "http_archive( - name="distroless", - sha256="f7a6ecfb8174a1dd4713ea3b21621072996ada7e8f1a69e6ae7581be137c6dd6", - strip_prefix="distroless-446923c3756ceeaa75888f52fcbdd48bb314fbf8", - urls=["https://github.com/GoogleContainerTools/distroless/archive/446923c3756ceeaa75888f52fcbdd48bb314fbf8.tar.gz"] -)", + "idx": 12, }, "packageName": "GoogleContainerTools/distroless", }, @@ -206,12 +141,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "io_bazel_rules_go", "depType": "http_archive", "managerData": { - "def": "maybe( - http_archive, - name = "io_bazel_rules_go", - sha256 = "2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f", - url = "https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip", -)", + "idx": 13, }, "packageName": "bazelbuild/rules_go", }, @@ -221,15 +151,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "bazel_gazelle", "depType": "http_archive", "managerData": { - "def": "maybe( - http_archive, - name = "bazel_gazelle", - sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", - "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", - ], -)", + "idx": 14, }, "packageName": "bazelbuild/bazel-gazelle", }, @@ -242,12 +164,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depType": "go_repository", "digestOneAndOnly": true, "managerData": { - "def": "maybe( - go_repository, - name = "com_github_pkg_errors", - commit = "816c9085562cd7ee03e7f8188a1cfd942858cded", - importpath = "github.com/pkg/errors", -)", + "idx": 15, }, "packageName": "github.com/pkg/errors", }, @@ -258,13 +175,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "py3_image_base", "depType": "container_pull", "managerData": { - "def": "container_pull( - name = "py3_image_base", - digest = "sha256:d5a717649fd93ea5b9c430d7f84e4c37ba219eb53bd73ed1d4a5a98e9edd84a7", - registry = "gcr.io", - repository = "distroless/python3-debian10", - tag = "latest", -)", + "idx": 16, }, "packageName": "distroless/python3-debian10", "registryUrls": [ @@ -278,12 +189,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() extracts multiple ty "depName": "distroless", "depType": "http_file", "managerData": { - "def": "http_file( - name="distroless", - sha256="f7a6ecfb8174a1dd4713ea3b21621072996ada7e8f1a69e6ae7581be137c6dd6", - strip_prefix="distroless-446923c3756ceeaa75888f52fcbdd48bb314fbf8", - urls=["https://github.com/GoogleContainerTools/distroless/archive/446923c3756ceeaa75888f52fcbdd48bb314fbf8.tar.gz"] -)", + "idx": 17, }, "packageName": "GoogleContainerTools/distroless", }, @@ -298,12 +204,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() sequential http_arch "depName": "aspect_rules_js", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "aspect_rules_js", - sha256 = "db9f446752fe4100320cf8487e8fd476b9af0adf6b99b601bcfd70b289bb0598", - strip_prefix = "rules_js-1.1.2", - url = "https://github.com/aspect-build/rules_js/archive/refs/tags/v1.1.2.tar.gz", - )", + "idx": 0, }, "packageName": "aspect-build/rules_js", }, @@ -313,11 +214,7 @@ exports[`modules/manager/bazel/extract extractPackageFile() sequential http_arch "depName": "rules_nodejs", "depType": "http_archive", "managerData": { - "def": "http_archive( - name = "rules_nodejs", - sha256 = "5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064", - urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz"], - )", + "idx": 1, }, "packageName": "bazelbuild/rules_nodejs", }, diff --git a/lib/modules/manager/bazel/common.spec.ts b/lib/modules/manager/bazel/common.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4946216e58282c583f41871f2fd24ac62b90a6c --- /dev/null +++ b/lib/modules/manager/bazel/common.spec.ts @@ -0,0 +1,49 @@ +import { updateCode } from './common'; + +describe('modules/manager/bazel/common', () => { + describe('updateCode', () => { + it('returns input for invalid', () => { + const input = `!@#`; + const output = updateCode(input, [0], 'foobar'); + expect(output).toBe(input); + }); + + it('replaces whole rule', () => { + const input = `git_repository(name = "foo")`; + const output = updateCode(input, [0], 'abcde'); + expect(output).toBe(`abcde`); + }); + + it('replaces rule key', () => { + const input = `git_repository(name = "foo")`; + const output = updateCode(input, [0, 'name'], 'bar'); + expect(output).toBe(`git_repository(name = "bar")`); + }); + + it('returns input on wrong index', () => { + const input = `git_repository(name = "foo")`; + const output = updateCode(input, [1, 'name'], 'bar'); + expect(output).toBe(input); + }); + + it('returns input on wrong key', () => { + const input = `git_repository(name = "foo")`; + const output = updateCode(input, [0, 'foobar'], 'bar'); + expect(output).toBe(input); + }); + + it('replaces array values', () => { + const input = `git_repository(name = "foo", deps = ["bar", "baz", "qux"])`; + const output = updateCode(input, [0, 'deps', 1], 'BAZ'); + expect(output).toBe( + `git_repository(name = "foo", deps = ["bar", "BAZ", "qux"])` + ); + }); + + it('updates using function', () => { + const input = `git_repository(name = "foo")`; + const output = updateCode(input, [0, 'name'], (x) => x.toUpperCase()); + expect(output).toBe(`git_repository(name = "FOO")`); + }); + }); +}); diff --git a/lib/modules/manager/bazel/common.ts b/lib/modules/manager/bazel/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..b13a23121c394d35bfba4f69c1cdb1cb84ce369b --- /dev/null +++ b/lib/modules/manager/bazel/common.ts @@ -0,0 +1,74 @@ +import is from '@sindresorhus/is'; +import { parse } from './parser'; +import type { Fragment, FragmentPath, FragmentUpdater } from './types'; + +export function findCodeFragment( + input: string, + path: FragmentPath +): Fragment | null { + const parsed = parse(input); + if (!parsed) { + return null; + } + + const [ruleIndex, ...restPath] = path; + let fragment: Fragment | undefined = parsed[ruleIndex]; + for (let pathIndex = 0; pathIndex < restPath.length; pathIndex += 1) { + if (!fragment) { + break; + } + + const key = restPath[pathIndex]; + + if (fragment.type === 'array' && is.number(key)) { + fragment = fragment.children[key]; + } + + if (fragment.type === 'record' && is.string(key)) { + fragment = fragment.children[key]; + } + } + + return fragment ?? null; +} + +export function patchCodeAtFragment( + input: string, + fragment: Fragment, + updater: FragmentUpdater +): string { + const { value, offset } = fragment; + const left = input.slice(0, offset); + const right = input.slice(offset + value.length); + return is.string(updater) + ? `${left}${updater}${right}` + : `${left}${updater(value)}${right}`; +} + +export function patchCodeAtFragments( + input: string, + fragments: Fragment[], + updater: FragmentUpdater +): string { + const sortedFragments = fragments.sort( + ({ offset: a }, { offset: b }) => b - a + ); + let result = input; + for (const fragment of sortedFragments) { + result = patchCodeAtFragment(result, fragment, updater); + } + return result; +} + +export function updateCode( + input: string, + path: FragmentPath, + updater: FragmentUpdater +): string { + const fragment = findCodeFragment(input, path); + if (!fragment) { + return input; + } + + return patchCodeAtFragment(input, fragment, updater); +} diff --git a/lib/modules/manager/bazel/extract.ts b/lib/modules/manager/bazel/extract.ts index a273c6cf5b21856abff7cfb44a4ce05a482f08f3..072c632a181298d786904c409adc3d02fed3c2ee 100644 --- a/lib/modules/manager/bazel/extract.ts +++ b/lib/modules/manager/bazel/extract.ts @@ -22,7 +22,7 @@ export function extractPackageFile( continue; } - dep.managerData = { def: fragment.value }; + dep.managerData = { idx }; deps.push(dep); } diff --git a/lib/modules/manager/bazel/types.ts b/lib/modules/manager/bazel/types.ts index 951abee9c4ba6aa5e430b281619492538ef86ec2..56f16a17b53e56aa417d309e4109f925338e4bbc 100644 --- a/lib/modules/manager/bazel/types.ts +++ b/lib/modules/manager/bazel/types.ts @@ -5,7 +5,7 @@ export interface UrlParsedResult { } export interface BazelManagerData { - def: string; + idx: number; } export type TargetAttribute = string | string[]; @@ -41,3 +41,10 @@ export type FragmentData = | string | FragmentData[] | { [k: string]: FragmentData }; + +export type FragmentPath = + | [number] + | [number, string] + | [number, string, number]; + +export type FragmentUpdater = string | ((_: string) => string); diff --git a/lib/modules/manager/bazel/update.spec.ts b/lib/modules/manager/bazel/update.spec.ts index 8b343510e74c2685f49dbdf54b58814c5a0baa65..f336e00445006d2e491b0f667ca139fa85192fd9 100644 --- a/lib/modules/manager/bazel/update.spec.ts +++ b/lib/modules/manager/bazel/update.spec.ts @@ -21,7 +21,7 @@ describe('modules/manager/bazel/update', () => { const upgrade = { depName: 'build_bazel_rules_nodejs', depType: 'git_repository', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue: '0.1.8', newValue: '0.2.0', }; @@ -49,7 +49,7 @@ describe('modules/manager/bazel/update', () => { const upgrade = { depName: 'build_bazel_rules_nodejs', depType: 'git_repository', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue: '0.1.8', newValue: '0.2.0', }; @@ -86,7 +86,7 @@ describe('modules/manager/bazel/update', () => { const upgrade = { depName: 'hasura', depType: 'container_pull', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, currentDigest, @@ -119,7 +119,7 @@ describe('modules/manager/bazel/update', () => { const upgrade = { depName: 'com_github_google_uuid', depType: 'go_repository', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue: 'v0.0.0', currentDigest, newDigest, @@ -127,17 +127,12 @@ describe('modules/manager/bazel/update', () => { updateType: 'major' as UpdateType, }; - const output = input.replace( - `"${currentDigest}"`, - `"${newDigest}", # ${newValue}` - ); + const output = input.replace(`"${currentDigest}"`, `"${newDigest}"`); const res = await updateDependency({ fileContent: input, upgrade }); expect(res).toEqual(output); - expect(res).toContain( - '"aaa09d789f3dba190787f8b4454c7d3c936fe123", # v1.0.3' - ); + expect(res).toContain('"aaa09d789f3dba190787f8b4454c7d3c936fe123"'); }); it('updates commit-based http archive', async () => { @@ -158,7 +153,7 @@ describe('modules/manager/bazel/update', () => { depName: 'distroless', depType: 'http_archive', repo: 'GoogleContainerTools/distroless', - managerData: { def: input }, + managerData: { idx: 0 }, currentDigest, newDigest, }; @@ -204,7 +199,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, }; @@ -248,7 +243,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, }; @@ -293,7 +288,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skyfoo', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, }; @@ -321,7 +316,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue: '0.5.0', newValue: '0.6.2', }; @@ -343,7 +338,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue: '0.5.0', newValue: '0.6.2', }; @@ -372,7 +367,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, }; @@ -426,7 +421,7 @@ describe('modules/manager/bazel/update', () => { depName: 'bazel_skylib', depType: 'http_archive', repo: 'bazelbuild/bazel-skylib', - managerData: { def: input }, + managerData: { idx: 0 }, currentValue, newValue, }; @@ -484,7 +479,7 @@ describe('modules/manager/bazel/update', () => { depName: 'rules_nodejs', depType: 'http_archive', repo: 'bazelbuild/rules_nodejs', - managerData: { def: upgraded_http_archive }, + managerData: { idx: 1 }, currentValue: currentValue1, newValue: newValue1, }; @@ -543,7 +538,7 @@ describe('modules/manager/bazel/update', () => { depName: 'rules_nodejs', depType: 'http_archive', repo: 'bazelbuild/rules_nodejs', - managerData: { def: upgraded_http_archive }, + managerData: { idx: 1 }, currentValue: currentValue1, newValue: newValue1, }; diff --git a/lib/modules/manager/bazel/update.ts b/lib/modules/manager/bazel/update.ts index 9c4b0904809b063a9ac19ca3d9347c5d296fb393..873733a4515e80bb81c74be44075300a76363143 100644 --- a/lib/modules/manager/bazel/update.ts +++ b/lib/modules/manager/bazel/update.ts @@ -1,54 +1,68 @@ -// TODO: types (#7154) -/* eslint-disable @typescript-eslint/restrict-template-expressions */ +import is from '@sindresorhus/is'; import hasha from 'hasha'; import { logger } from '../../../logger'; import * as packageCache from '../../../util/cache/package'; import { Http } from '../../../util/http'; +import { map as pMap } from '../../../util/promises'; import { regEx } from '../../../util/regex'; import type { UpdateDependencyConfig } from '../types'; -import type { BazelManagerData } from './types'; +import { findCodeFragment, patchCodeAtFragments, updateCode } from './common'; +import type { BazelManagerData, RecordFragment, StringFragment } from './types'; const http = new Http('bazel'); -function updateWithNewVersion( - content: string, - currentValue: string, - newValue: string -): string { - // istanbul ignore if - if (currentValue === newValue) { - return content; +function getUrlFragments(rule: RecordFragment): StringFragment[] { + const urls: StringFragment[] = []; + + const urlRecord = rule.children['url']; + if (urlRecord?.type === 'string') { + urls.push(urlRecord); + } + + const urlsRecord = rule.children['urls']; + if (urlsRecord?.type === 'array') { + for (const urlRecord of urlsRecord.children) { + if (urlRecord.type === 'string') { + urls.push(urlRecord); + } + } } - const replaceFrom = currentValue.replace(regEx(/^v/), ''); - const replaceTo = newValue.replace(regEx(/^v/), ''); - let newContent = content; - do { - newContent = newContent.replace(replaceFrom, replaceTo); - } while (newContent.includes(replaceFrom)); - return newContent; + + return urls; } -function extractUrl(flattened: string): string[] | null { - const urlMatch = regEx(/url="(.*?)"/).exec(flattened); - if (!urlMatch) { - logger.debug('Cannot locate urls in new definition'); - return null; +const urlMassages = { + 'bazel-skylib.': 'bazel_skylib-', + '/bazel-gazelle/releases/download/0': '/bazel-gazelle/releases/download/v0', + '/bazel-gazelle-0': '/bazel-gazelle-v0', + '/rules_go/releases/download/0': '/rules_go/releases/download/v0', + '/rules_go-0': '/rules_go-v0', +}; + +function massageUrl(url: string): string { + let result = url; + for (const [from, to] of Object.entries(urlMassages)) { + result = result.replace(from, to); } - return [urlMatch[1]]; + return result; +} + +function replaceAll(input: string, from: string, to: string): string { + return input.split(from).join(to); } -function extractUrls(content: string): string[] | null { - const flattened = content.replace(regEx(/\n/g), '').replace(regEx(/\s/g), ''); - const urlsMatch = regEx(/urls?=\[.*?\]/).exec(flattened); - if (!urlsMatch) { - return extractUrl(flattened); +function replaceValues( + content: string, + from: string | null | undefined, + to: string | null | undefined +): string { + // istanbul ignore if + if (!from || !to || from === to) { + return content; } - const urls = urlsMatch[0] - .replace(regEx(/urls?=\[/), '') - .replace(regEx(/,?\]$/), '') - .split(',') - .map((url) => url.replace(regEx(/"/g), '')); - return urls; + const massagedFrom = from.replace(regEx(/^v/), ''); + const massagedTo = to.replace(regEx(/^v/), ''); + return replaceAll(content, massagedFrom, massagedTo); } async function getHashFromUrl(url: string): Promise<string | null> { @@ -75,22 +89,20 @@ async function getHashFromUrl(url: string): Promise<string | null> { async function getHashFromUrls(urls: string[]): Promise<string | null> { const hashes = ( - await Promise.all(urls.map((url) => getHashFromUrl(url))) - ).filter(Boolean); - const distinctHashes = [...new Set(hashes)]; - if (!distinctHashes.length) { - logger.debug({ hashes, urls }, 'Could not calculate hash for URLs'); + await pMap(urls, (url) => getHashFromUrl(massageUrl(url))) + ).filter(is.truthy); + if (!hashes.length) { + logger.debug({ urls }, 'Could not calculate hash for URLs'); return null; } + + const distinctHashes = new Set(hashes); // istanbul ignore if - if (distinctHashes.length > 1) { + if (distinctHashes.size > 1) { logger.warn({ urls }, 'Found multiple hashes for single def'); } - return distinctHashes[0]; -} -function setNewHash(content: string, hash: string): string { - return content.replace(regEx(/(sha256\s*=\s*)"[^"]+"/), `$1"${hash}"`); + return hashes[0]; } export async function updateDependency({ @@ -98,83 +110,77 @@ export async function updateDependency({ upgrade, }: UpdateDependencyConfig<BazelManagerData>): Promise<string | null> { try { - logger.debug( - `bazel.updateDependency(): ${upgrade.newValue ?? upgrade.newDigest}` - ); - let newDef: string | undefined; - if (upgrade.depType === 'container_pull' && upgrade.managerData?.def) { - newDef = upgrade.managerData.def - .replace(regEx(/(tag\s*=\s*)"[^"]+"/), `$1"${upgrade.newValue}"`) - .replace(regEx(/(digest\s*=\s*)"[^"]+"/), `$1"${upgrade.newDigest}"`); + const { newValue, newDigest } = upgrade; + logger.debug({ newValue, newDigest }, `bazel.updateDependency()`); + const idx = upgrade.managerData!.idx; + + if (upgrade.depType === 'container_pull') { + let result = fileContent; + + if (newValue) { + result = updateCode(result, [idx, 'tag'], newValue); + } + + if (newDigest) { + result = updateCode(result, [idx, 'digest'], newDigest); + } + + return result; } + if ( - (upgrade.depType === 'git_repository' || - upgrade.depType === 'go_repository') && - upgrade.managerData?.def + upgrade.depType === 'git_repository' || + upgrade.depType === 'go_repository' ) { - newDef = upgrade.managerData.def - .replace(regEx(/(tag\s*=\s*)"[^"]+"/), `$1"${upgrade.newValue}"`) - .replace(regEx(/(commit\s*=\s*)"[^"]+"/), `$1"${upgrade.newDigest}"`); - if (upgrade.currentDigest && upgrade.updateType !== 'digest') { - newDef = newDef.replace( - regEx(/(commit\s*=\s*)"[^"]+".*?\n/), - `$1"${upgrade.newDigest}", # ${upgrade.newValue}\n` - ); + let result = fileContent; + + if (newValue) { + result = updateCode(result, [idx, 'tag'], newValue); } - } else if ( - (upgrade.depType === 'http_archive' || upgrade.depType === 'http_file') && - upgrade.managerData?.def && - (upgrade.currentValue || upgrade.currentDigest) && - (upgrade.newValue ?? upgrade.newDigest) - ) { - newDef = updateWithNewVersion( - upgrade.managerData.def, - (upgrade.currentValue ?? upgrade.currentDigest)!, - (upgrade.newValue ?? upgrade.newDigest)! - ); - const massages = { - 'bazel-skylib.': 'bazel_skylib-', - '/bazel-gazelle/releases/download/0': - '/bazel-gazelle/releases/download/v0', - '/bazel-gazelle-0': '/bazel-gazelle-v0', - '/rules_go/releases/download/0': '/rules_go/releases/download/v0', - '/rules_go-0': '/rules_go-v0', - }; - for (const [from, to] of Object.entries(massages)) { - newDef = newDef.replace(from, to); + + if (newDigest) { + result = updateCode(result, [idx, 'commit'], newDigest); + } + + return result; + } + + if (upgrade.depType === 'http_file' || upgrade.depType === 'http_archive') { + const rule = findCodeFragment(fileContent, [idx]); + // istanbul ignore if + if (rule?.type !== 'record') { + return null; } - const urls = extractUrls(newDef); - if (!urls?.length) { - logger.debug({ newDef }, 'urls is empty'); + + const urlFragments = getUrlFragments(rule); + if (!urlFragments?.length) { + logger.debug({ def: rule.value }, 'urls is empty'); return null; } + + const updateValues = (oldUrl: string): string => { + let url = oldUrl; + url = replaceValues(url, upgrade.currentValue, upgrade.newValue); + url = replaceValues(url, upgrade.currentDigest, upgrade.newDigest); + return url; + }; + + const urls = urlFragments.map(({ value }) => updateValues(value)); const hash = await getHashFromUrls(urls); if (!hash) { return null; } - logger.debug({ hash }, 'Calculated hash'); - newDef = setNewHash(newDef, hash); - } - logger.debug({ oldDef: upgrade.managerData?.def, newDef }); - - // istanbul ignore if: needs test - if (!newDef) { - return null; - } - let existingRegExStr = `(?:maybe\\s*\\(\\s*)?${upgrade.depType}(?:\\(|,)[^\\)]+name\\s*=\\s*"${upgrade.depName}"(.*\\n)+?\\s*\\)`; - if (newDef.endsWith('\n')) { - existingRegExStr += '\n'; + let result = fileContent; + result = patchCodeAtFragments(result, urlFragments, updateValues); + result = updateCode(result, [idx, 'strip_prefix'], updateValues); + result = updateCode(result, [idx, 'sha256'], hash); + return result; } - const existingDef = regEx(existingRegExStr); - // istanbul ignore if - if (!existingDef.test(fileContent)) { - logger.debug('Cannot match existing string'); - return null; - } - return fileContent.replace(existingDef, newDef); } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'Error setting new bazel WORKSPACE version'); - return null; } + + // istanbul ignore next + return null; }