diff --git a/lib/modules/manager/bazel/extract.spec.ts b/lib/modules/manager/bazel/extract.spec.ts index 79a34a789d1cb1e4cfb8ab6e547627428fc9ac7f..85e8d2d50ac08dce5c3c3ea7ee107bb34b099880 100644 --- a/lib/modules/manager/bazel/extract.spec.ts +++ b/lib/modules/manager/bazel/extract.spec.ts @@ -1,7 +1,8 @@ import { Fixtures } from '../../../../test/fixtures'; -import { extractPackageFile as extract } from '.'; +import { extractPackageFile as _extractPackageFile } from '.'; -const extractPackageFile = (content: string) => extract(content, 'WORKSPACE'); +const extractPackageFile = (content: string) => + _extractPackageFile(content, 'WORKSPACE'); describe('modules/manager/bazel/extract', () => { describe('extractPackageFile()', () => { diff --git a/lib/modules/manager/bazel/parser.spec.ts b/lib/modules/manager/bazel/parser.spec.ts index 4bbf8bd6dddd085c0dd8754c8cfdb38e523aa564..79d12c72126be0ebee77b79bb43be92f9b97f235 100644 --- a/lib/modules/manager/bazel/parser.spec.ts +++ b/lib/modules/manager/bazel/parser.spec.ts @@ -1,4 +1,4 @@ -import { parse } from './parser'; +import { extract, parse } from './parser'; describe('modules/manager/bazel/parser', () => { it('parses rules input', () => { @@ -44,6 +44,10 @@ describe('modules/manager/bazel/parser', () => { }, ], }); + expect(extract(res!)).toMatchObject([ + { rule: 'go_repository', name: 'foo' }, + { rule: 'go_repository', name: 'bar', deps: ['baz', 'qux'] }, + ]); }); it('parses multiple archives', () => { @@ -100,6 +104,25 @@ describe('modules/manager/bazel/parser', () => { }, ], }); + expect(extract(res!)).toMatchObject([ + { + rule: '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', + }, + { + rule: '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', + ], + }, + ]); }); it('parses http_archive', () => { @@ -129,6 +152,15 @@ describe('modules/manager/bazel/parser', () => { }, ], }); + expect(extract(res!)).toMatchObject([ + { + rule: 'http_archive', + name: 'rules_nodejs', + sha256: + '5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064', + url: 'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz', + }, + ]); }); it('parses http_archive with prefixes and multiple urls', () => { @@ -174,5 +206,19 @@ describe('modules/manager/bazel/parser', () => { }, ], }); + expect(extract(res!)).toMatchObject([ + { + name: 'bazel_toolchains', + rule: 'http_archive', + 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', + ], + }, + ]); }); }); diff --git a/lib/modules/manager/bazel/parser.ts b/lib/modules/manager/bazel/parser.ts index 70f5090145b364be9e3fb47862c079e5e5a04151..75718503e4f7156f4ef714bfba6bd1b04ff68fa1 100644 --- a/lib/modules/manager/bazel/parser.ts +++ b/lib/modules/manager/bazel/parser.ts @@ -2,7 +2,13 @@ import { lang, lexer, parser, query as q } from 'good-enough-parser'; import hasha from 'hasha'; import * as memCache from '../../../util/cache/memory'; import { supportedRulesRegex } from './rules/index'; -import type { ArrayFragment, NestedFragment, RecordFragment } from './types'; +import type { + ArrayFragment, + Fragment, + FragmentData, + NestedFragment, + RecordFragment, +} from './types'; interface Ctx { readonly source: string; @@ -232,3 +238,20 @@ export function parse(input: string): ArrayFragment | null { memCache.set(cacheKey, result); return result; } + +export function extract(fragment: Fragment): FragmentData { + if (fragment.type === 'string') { + return fragment.value; + } + + if (fragment.type === 'record') { + const { children } = fragment; + const result: Record<string, FragmentData> = {}; + for (const [key, value] of Object.entries(children)) { + result[key] = extract(value); + } + return result; + } + + return fragment.children.map(extract); +} diff --git a/lib/modules/manager/bazel/rules/docker.ts b/lib/modules/manager/bazel/rules/docker.ts index 797c8c013562f5fbbec5c96edf55e596bc2d3e93..233487c5e8091156f19a7ceedcb5fbee3ae7e229 100644 --- a/lib/modules/manager/bazel/rules/docker.ts +++ b/lib/modules/manager/bazel/rules/docker.ts @@ -1,38 +1,28 @@ -import is from '@sindresorhus/is'; +import { z } from 'zod'; import { DockerDatasource } from '../../../datasource/docker'; import { id as dockerVersioning } from '../../../versioning/docker'; import type { PackageDependency } from '../../types'; -import type { Target } from '../types'; -export function dockerDependency({ - rule: depType, - name: depName, - tag: currentValue, - digest: currentDigest, - repository: packageName, - registry, -}: Target): PackageDependency | null { - let dep: PackageDependency | null = null; +export const dockerRules = ['container_pull'] as const; - if ( - depType === 'container_pull' && - is.string(depName) && - is.string(currentValue) && - is.string(currentDigest) && - is.string(packageName) && - is.string(registry) - ) { - dep = { +export const DockerTarget = z + .object({ + rule: z.enum(dockerRules), + name: z.string(), + tag: z.string(), + digest: z.string(), + repository: z.string(), + registry: z.string(), + }) + .transform( + ({ rule, name, repository, tag, digest, registry }): PackageDependency => ({ datasource: DockerDatasource.id, versioning: dockerVersioning, - depType, - depName, - packageName, - currentValue, - currentDigest, + depType: rule, + depName: name, + packageName: repository, + currentValue: tag, + currentDigest: digest, registryUrls: [registry], - }; - } - - return dep; -} + }) + ); diff --git a/lib/modules/manager/bazel/rules/git.ts b/lib/modules/manager/bazel/rules/git.ts index c5ee9affb5476dd1634351cdf5a65d68dd8cd477..bdf52e6f5b91be9f8ad835ec1e87d460a0d29620 100644 --- a/lib/modules/manager/bazel/rules/git.ts +++ b/lib/modules/manager/bazel/rules/git.ts @@ -1,50 +1,51 @@ -import is from '@sindresorhus/is'; import parseGithubUrl from 'github-url-from-git'; +import { z } from 'zod'; +import { regEx } from '../../../../util/regex'; import { GithubReleasesDatasource } from '../../../datasource/github-releases'; import type { PackageDependency } from '../../types'; -import type { Target } from '../types'; - -export function gitDependency({ - rule: depType, - name: depName, - tag: currentValue, - commit: currentDigest, - remote, -}: Target): PackageDependency | null { - let dep: PackageDependency | null = null; - - if ( - depType === 'git_repository' && - is.string(depName) && - (is.string(currentValue) || is.string(currentDigest)) && - is.string(remote) - ) { - dep = { - datasource: GithubReleasesDatasource.id, - depType, - depName, + +const githubUrlRegex = regEx( + /^https:\/\/github\.com\/(?<packageName>[^/]+\/[^/]+)/ +); + +function githubPackageName(input: string): string | undefined { + return parseGithubUrl(input)?.match(githubUrlRegex)?.groups?.packageName; +} + +export const gitRules = ['git_repository'] as const; + +export const GitTarget = z + .object({ + rule: z.enum(gitRules), + name: z.string(), + tag: z.string().optional(), + commit: z.string().optional(), + remote: z.string(), + }) + .refine(({ tag, commit }) => !!tag || !!commit) + .transform(({ rule, name, tag, commit, remote }): PackageDependency => { + const dep: PackageDependency = { + depType: rule, + depName: name, }; - if (is.string(currentValue)) { - dep.currentValue = currentValue; + if (tag) { + dep.currentValue = tag; } - if (is.string(currentDigest)) { - dep.currentDigest = currentDigest; + if (commit) { + dep.currentDigest = commit; } - // TODO: Check if we really need to use parse here or if it should always be a plain https url (#9605) - const packageName = parseGithubUrl(remote)?.substring( - 'https://github.com/'.length - ); + const githubPackage = githubPackageName(remote); + if (githubPackage) { + dep.datasource = GithubReleasesDatasource.id; + dep.packageName = githubPackage; + } - // istanbul ignore else - if (packageName) { - dep.packageName = packageName; - } else { - dep.skipReason = 'unsupported-remote'; + if (!dep.datasource) { + dep.skipReason = 'unsupported-datasource'; } - } - return dep; -} + return dep; + }); diff --git a/lib/modules/manager/bazel/rules/go.ts b/lib/modules/manager/bazel/rules/go.ts index d82036fe04d4119f1fe42d276687bc724dcde41a..ff6f4398003b7a14a50cf4468fae91a86c6f255a 100644 --- a/lib/modules/manager/bazel/rules/go.ts +++ b/lib/modules/manager/bazel/rules/go.ts @@ -1,54 +1,51 @@ -import is from '@sindresorhus/is'; +import { z } from 'zod'; import { regEx } from '../../../../util/regex'; import { GoDatasource } from '../../../datasource/go'; import type { PackageDependency } from '../../types'; -import type { Target } from '../types'; -export function goDependency({ - rule: depType, - name: depName, - tag: currentValue, - commit: currentDigest, - importpath: packageName, - remote, -}: Target): PackageDependency | null { - let dep: PackageDependency | null = null; +export const goRules = ['go_repository'] as const; - if ( - depType === 'go_repository' && - is.string(depName) && - (is.string(currentValue) || is.string(currentDigest)) && - is.string(packageName) - ) { - dep = { - datasource: GoDatasource.id, - depType, - depName, - packageName, - }; +export const GoTarget = z + .object({ + rule: z.enum(goRules), + name: z.string(), + tag: z.string().optional(), + commit: z.string().optional(), + importpath: z.string(), + remote: z.string().optional(), + }) + .refine(({ tag, commit }) => !!tag || !!commit) + .transform( + ({ rule, name, tag, commit, importpath, remote }): PackageDependency => { + const dep: PackageDependency = { + datasource: GoDatasource.id, + depType: rule, + depName: name, + packageName: importpath, + }; - if (is.string(currentValue)) { - dep.currentValue = currentValue; - } + if (tag) { + dep.currentValue = tag; + } - if (is.string(currentDigest)) { - dep.currentValue = 'v0.0.0'; - dep.currentDigest = currentDigest; - dep.currentDigestShort = currentDigest.substring(0, 7); - dep.digestOneAndOnly = true; - } + if (commit) { + dep.currentValue = 'v0.0.0'; + dep.currentDigest = commit; + dep.currentDigestShort = commit.substring(0, 7); + dep.digestOneAndOnly = true; + } - if (is.string(remote)) { - const remoteMatch = regEx( - /https:\/\/github\.com(?:.*\/)(([a-zA-Z]+)([-])?([a-zA-Z]+))/ - ).exec(remote); - if (remoteMatch && remoteMatch[0].length === remote.length) { - dep.packageName = remote.replace('https://', ''); - } else { - dep.skipReason = 'unsupported-remote'; + if (remote) { + const remoteMatch = regEx( + /https:\/\/github\.com(?:.*\/)(([a-zA-Z]+)([-])?([a-zA-Z]+))/ + ).exec(remote); + if (remoteMatch && remoteMatch[0].length === remote.length) { + dep.packageName = remote.replace('https://', ''); + } else { + dep.skipReason = 'unsupported-remote'; + } } - } - } - return dep; -} + return dep; + } + ); diff --git a/lib/modules/manager/bazel/rules/http.ts b/lib/modules/manager/bazel/rules/http.ts index 10dbcfbe03aa9f11d65aecc622c645257dc79293..4fc3e4bc9b14d61e4e3376b8e9d3620b5218edff 100644 --- a/lib/modules/manager/bazel/rules/http.ts +++ b/lib/modules/manager/bazel/rules/http.ts @@ -1,10 +1,11 @@ import is from '@sindresorhus/is'; +import { z } from 'zod'; import { regEx } from '../../../../util/regex'; import { parseUrl } from '../../../../util/url'; import { GithubReleasesDatasource } from '../../../datasource/github-releases'; import { GithubTagsDatasource } from '../../../datasource/github-tags'; import type { PackageDependency } from '../../types'; -import type { Target, UrlParsedResult } from '../types'; +import type { UrlParsedResult } from '../types'; export function parseArchiveUrl( urlString: string | undefined | null @@ -50,47 +51,35 @@ export function parseArchiveUrl( return null; } -export function httpDependency({ - rule: depType, - name: depName, - url, - urls, - sha256, -}: Target): PackageDependency | null { - let dep: PackageDependency | null = null; +export const httpRules = ['http_archive', 'http_file'] as const; - if ( - (depType === 'http_archive' || depType === 'http_file') && - is.string(depName) && - is.string(sha256) - ) { - let parsedUrl: UrlParsedResult | null = null; - if (is.string(url)) { - parsedUrl = parseArchiveUrl(url); - } else if (is.array(urls, is.string)) { - for (const u of urls) { - parsedUrl = parseArchiveUrl(u); - if (parsedUrl) { - break; - } - } +export const HttpTarget = z + .object({ + rule: z.enum(httpRules), + name: z.string(), + url: z.string().optional(), + urls: z.array(z.string()).optional(), + sha256: z.string(), + }) + .refine(({ url, urls }) => !!url || !!urls) + .transform(({ rule, name, url, urls = [] }): PackageDependency | null => { + const parsedUrl = [url, ...urls].map(parseArchiveUrl).find(is.truthy); + if (!parsedUrl) { + return null; } - if (parsedUrl) { - dep = { - datasource: parsedUrl.datasource, - depType, - depName, - packageName: parsedUrl.repo, - }; + const dep: PackageDependency = { + datasource: parsedUrl.datasource, + depType: rule, + depName: name, + packageName: parsedUrl.repo, + }; - if (regEx(/^[a-f0-9]{40}$/i).test(parsedUrl.currentValue)) { - dep.currentDigest = parsedUrl.currentValue; - } else { - dep.currentValue = parsedUrl.currentValue; - } + if (regEx(/^[a-f0-9]{40}$/i).test(parsedUrl.currentValue)) { + dep.currentDigest = parsedUrl.currentValue; + } else { + dep.currentValue = parsedUrl.currentValue; } - } - return dep; -} + return dep; + }); diff --git a/lib/modules/manager/bazel/rules/index.spec.ts b/lib/modules/manager/bazel/rules/index.spec.ts index f0b268641db0831d9687b02bafe3f043135b6169..4a01c579767cc36a61aef28bec0233a3f3e9ae43 100644 --- a/lib/modules/manager/bazel/rules/index.spec.ts +++ b/lib/modules/manager/bazel/rules/index.spec.ts @@ -1,11 +1,8 @@ -import { dockerDependency } from './docker'; -import { gitDependency } from './git'; -import { goDependency } from './go'; -import { httpDependency, parseArchiveUrl } from './http'; -import { extractDepFromFragment } from '.'; +import { parseArchiveUrl } from './http'; +import { extractDepFromFragmentData } from '.'; describe('modules/manager/bazel/rules/index', () => { - test('parseUrl', () => { + it('parses archiveUrl', () => { expect(parseArchiveUrl('')).toBeNull(); expect(parseArchiveUrl(null)).toBeNull(); expect(parseArchiveUrl(null)).toBeNull(); @@ -46,325 +43,284 @@ describe('modules/manager/bazel/rules/index', () => { }); }); - test('gitDependency', () => { - expect(gitDependency({ rule: 'foo_bar', name: 'foo_bar' })).toBeNull(); - - expect( - gitDependency({ rule: 'git_repository', name: 'foo_bar' }) - ).toBeNull(); - - expect( - gitDependency({ rule: 'git_repository', name: 'foo_bar', tag: '1.2.3' }) - ).toBeNull(); - - expect( - gitDependency({ - rule: 'git_repository', - name: 'foo_bar', - tag: '1.2.3', - remote: 'https://github.com/foo/bar', - }) - ).toEqual({ - datasource: 'github-releases', - depType: 'git_repository', - depName: 'foo_bar', - packageName: 'foo/bar', - currentValue: '1.2.3', - }); - - expect( - gitDependency({ - rule: 'git_repository', - name: 'foo_bar', - commit: 'abcdef0123abcdef0123abcdef0123abcdef0123', - remote: 'https://github.com/foo/bar', - }) - ).toEqual({ - datasource: 'github-releases', - depType: 'git_repository', - depName: 'foo_bar', - packageName: 'foo/bar', - currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - }); - }); - - test('goDependency', () => { - expect(goDependency({ rule: 'foo_bar', name: 'foo_bar' })).toBeNull(); - - expect(goDependency({ rule: 'go_repository', name: 'foo_bar' })).toBeNull(); + describe('git', () => { + it('extracts git dependencies', () => { + expect( + extractDepFromFragmentData({ rule: 'foo_bar', name: 'foo_bar' }) + ).toBeNull(); - expect( - goDependency({ rule: 'go_repository', name: 'foo_bar', tag: '1.2.3' }) - ).toBeNull(); + expect( + extractDepFromFragmentData({ rule: 'git_repository', name: 'foo_bar' }) + ).toBeNull(); - expect( - goDependency({ - rule: 'go_repository', - name: 'foo_bar', - tag: '1.2.3', - importpath: 'foo/bar/baz', - }) - ).toEqual({ - datasource: 'go', - depType: 'go_repository', - depName: 'foo_bar', - packageName: 'foo/bar/baz', - currentValue: '1.2.3', - }); + expect( + extractDepFromFragmentData({ + rule: 'git_repository', + name: 'foo_bar', + tag: '1.2.3', + }) + ).toBeNull(); - expect( - goDependency({ - rule: 'go_repository', - name: 'foo_bar', - commit: 'abcdef0123abcdef0123abcdef0123abcdef0123', - importpath: 'foo/bar/baz', - }) - ).toEqual({ - datasource: 'go', - depType: 'go_repository', - depName: 'foo_bar', - packageName: 'foo/bar/baz', - currentValue: 'v0.0.0', - currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - currentDigestShort: 'abcdef0', - digestOneAndOnly: true, - }); + expect( + extractDepFromFragmentData({ + rule: 'git_repository', + name: 'foo_bar', + tag: '1.2.3', + remote: 'https://github.com/foo/bar', + }) + ).toEqual({ + datasource: 'github-releases', + depType: 'git_repository', + depName: 'foo_bar', + packageName: 'foo/bar', + currentValue: '1.2.3', + }); - expect( - goDependency({ - rule: 'go_repository', - name: 'foo_bar', - tag: '1.2.3', - importpath: 'foo/bar/baz', - remote: 'https://github.com/foo/bar', - }) - ).toEqual({ - datasource: 'go', - depType: 'go_repository', - depName: 'foo_bar', - packageName: 'github.com/foo/bar', - currentValue: '1.2.3', - }); + expect( + extractDepFromFragmentData({ + rule: 'git_repository', + name: 'foo_bar', + commit: 'abcdef0123abcdef0123abcdef0123abcdef0123', + remote: 'https://github.com/foo/bar', + }) + ).toEqual({ + datasource: 'github-releases', + depType: 'git_repository', + depName: 'foo_bar', + packageName: 'foo/bar', + currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + }); - expect( - goDependency({ - rule: 'go_repository', - name: 'foo_bar', - tag: '1.2.3', - importpath: 'foo/bar/baz', - remote: 'https://example.com/foo/bar', - }) - ).toEqual({ - datasource: 'go', - depType: 'go_repository', - depName: 'foo_bar', - packageName: 'foo/bar/baz', - currentValue: '1.2.3', - skipReason: 'unsupported-remote', + expect( + extractDepFromFragmentData({ + rule: 'git_repository', + name: 'foo_bar', + tag: '1.2.3', + remote: 'https://gitlab.com/foo/bar', + }) + ).toMatchObject({ + currentValue: '1.2.3', + depName: 'foo_bar', + depType: 'git_repository', + skipReason: 'unsupported-datasource', + }); }); }); - test('httpDependency', () => { - expect(httpDependency({ rule: 'foo_bar', name: 'foo_bar' })).toBeNull(); - - expect( - httpDependency({ rule: 'http_archive', name: 'foo_bar' }) - ).toBeNull(); + describe('go', () => { + it('extracts go dependencies', () => { + expect( + extractDepFromFragmentData({ rule: 'foo_bar', name: 'foo_bar' }) + ).toBeNull(); - expect( - httpDependency({ - rule: 'http_archive', - name: 'foo_bar', - sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', - }) - ).toBeNull(); + expect( + extractDepFromFragmentData({ rule: 'go_repository', name: 'foo_bar' }) + ).toBeNull(); - expect( - httpDependency({ - rule: 'http_archive', - name: 'foo_bar', - sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', - url: 'https://github.com/foo/bar/archive/abcdef0123abcdef0123abcdef0123abcdef0123.tar.gz', - }) - ).toEqual({ - currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - datasource: 'github-tags', - depName: 'foo_bar', - depType: 'http_archive', - packageName: 'foo/bar', - }); + expect( + extractDepFromFragmentData({ + rule: 'go_repository', + name: 'foo_bar', + tag: '1.2.3', + }) + ).toBeNull(); - expect( - httpDependency({ - rule: 'http_archive', - name: 'foo_bar', - sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', - urls: [ - 'https://example.com/foo/bar', - 'https://github.com/foo/bar/archive/abcdef0123abcdef0123abcdef0123abcdef0123.tar.gz', - ], - }) - ).toEqual({ - currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - datasource: 'github-tags', - depName: 'foo_bar', - depType: 'http_archive', - packageName: 'foo/bar', - }); + expect( + extractDepFromFragmentData({ + rule: 'go_repository', + name: 'foo_bar', + tag: '1.2.3', + importpath: 'foo/bar/baz', + }) + ).toEqual({ + datasource: 'go', + depType: 'go_repository', + depName: 'foo_bar', + packageName: 'foo/bar/baz', + currentValue: '1.2.3', + }); - expect( - httpDependency({ - rule: 'http_archive', - name: 'foo_bar', - sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', - url: 'https://github.com/foo/bar/releases/download/1.2.3/foobar-1.2.3.tar.gz', - }) - ).toEqual({ - currentValue: '1.2.3', - datasource: 'github-releases', - depName: 'foo_bar', - depType: 'http_archive', - packageName: 'foo/bar', - }); + expect( + extractDepFromFragmentData({ + rule: 'go_repository', + name: 'foo_bar', + commit: 'abcdef0123abcdef0123abcdef0123abcdef0123', + importpath: 'foo/bar/baz', + }) + ).toEqual({ + datasource: 'go', + depType: 'go_repository', + depName: 'foo_bar', + packageName: 'foo/bar/baz', + currentValue: 'v0.0.0', + currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + currentDigestShort: 'abcdef0', + digestOneAndOnly: true, + }); - expect( - httpDependency({ - rule: 'http_archive', - name: 'foo_bar', - sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', - urls: [ - 'https://example.com/foo/bar', - 'https://github.com/foo/bar/releases/download/1.2.3/foobar-1.2.3.tar.gz', - ], - }) - ).toEqual({ - currentValue: '1.2.3', - datasource: 'github-releases', - depName: 'foo_bar', - depType: 'http_archive', - packageName: 'foo/bar', - }); + expect( + extractDepFromFragmentData({ + rule: 'go_repository', + name: 'foo_bar', + tag: '1.2.3', + importpath: 'foo/bar/baz', + remote: 'https://github.com/foo/bar', + }) + ).toEqual({ + datasource: 'go', + depType: 'go_repository', + depName: 'foo_bar', + packageName: 'github.com/foo/bar', + currentValue: '1.2.3', + }); - expect( - httpDependency({ - rule: 'http_archive', - name: 'aspect_rules_js', - sha256: - 'db9f446752fe4100320cf8487e8fd476b9af0adf6b99b601bcfd70b289bb0598', - urls: [ - 'https://github.com/aspect-build/rules_js/archive/refs/tags/v1.1.2.tar.gz', - ], - }) - ).toEqual({ - currentValue: 'v1.1.2', - datasource: 'github-tags', - depName: 'aspect_rules_js', - depType: 'http_archive', - packageName: 'aspect-build/rules_js', + expect( + extractDepFromFragmentData({ + rule: 'go_repository', + name: 'foo_bar', + tag: '1.2.3', + importpath: 'foo/bar/baz', + remote: 'https://example.com/foo/bar', + }) + ).toEqual({ + datasource: 'go', + depType: 'go_repository', + depName: 'foo_bar', + packageName: 'foo/bar/baz', + currentValue: '1.2.3', + skipReason: 'unsupported-remote', + }); }); }); - test('dockerDependency', () => { - expect(dockerDependency({ rule: 'foo_bar', name: 'foo_bar' })).toBeNull(); + describe('http', () => { + it('extracts http dependencies', () => { + expect( + extractDepFromFragmentData({ rule: 'foo_bar', name: 'foo_bar' }) + ).toBeNull(); - expect( - dockerDependency({ - rule: 'container_pull', - name: 'foo_bar', - tag: '1.2.3', - digest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - repository: 'example.com/foo/bar', - registry: 'https://example.com', - }) - ).toEqual({ - currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', - currentValue: '1.2.3', - datasource: 'docker', - depName: 'foo_bar', - depType: 'container_pull', - packageName: 'example.com/foo/bar', - registryUrls: ['https://example.com'], - versioning: 'docker', - }); - }); + expect( + extractDepFromFragmentData({ rule: 'http_archive', name: 'foo_bar' }) + ).toBeNull(); - describe('extractDepFromFragment', () => { - it('returns null for unknown rule type', () => { expect( - extractDepFromFragment({ - type: 'record', - value: '', - offset: 0, - children: { - rule: { type: 'string', value: 'foo', offset: 0 }, - name: { type: 'string', value: 'bar', offset: 0 }, - }, + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'foo_bar', + sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', }) ).toBeNull(); - }); - it('extracts from git_repository', () => { expect( - extractDepFromFragment({ - type: 'record', - value: '', - offset: 0, - children: { - rule: { type: 'string', value: 'git_repository', offset: 0 }, - name: { type: 'string', value: 'foo_bar', offset: 0 }, - tag: { type: 'string', value: '1.2.3', offset: 0 }, - remote: { - type: 'string', - value: 'https://github.com/foo/bar', - offset: 0, - }, - }, + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'foo_bar', + sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', + url: 'https://github.com/foo/bar/archive/abcdef0123abcdef0123abcdef0123abcdef0123.tar.gz', }) ).toEqual({ - datasource: 'github-releases', - depType: 'git_repository', + currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + datasource: 'github-tags', depName: 'foo_bar', + depType: 'http_archive', + packageName: 'foo/bar', + }); + + expect( + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'foo_bar', + sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', + urls: [ + 'https://example.com/foo/bar', + 'https://github.com/foo/bar/archive/abcdef0123abcdef0123abcdef0123abcdef0123.tar.gz', + ], + }) + ).toEqual({ + currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + datasource: 'github-tags', + depName: 'foo_bar', + depType: 'http_archive', packageName: 'foo/bar', + }); + + expect( + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'foo_bar', + sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', + url: 'https://github.com/foo/bar/releases/download/1.2.3/foobar-1.2.3.tar.gz', + }) + ).toEqual({ currentValue: '1.2.3', + datasource: 'github-releases', + depName: 'foo_bar', + depType: 'http_archive', + packageName: 'foo/bar', }); - }); - it('extracts from http_archive', () => { expect( - extractDepFromFragment({ - type: 'record', - value: '', - offset: 0, - children: { - rule: { type: 'string', value: 'http_archive', offset: 0 }, - name: { type: 'string', value: 'rules_nodejs', offset: 0 }, - sha256: { - type: 'string', - value: - '5aef09ed3279aa01d5c928e3beb248f9ad32dde6aafe6373a8c994c3ce643064', - offset: 0, - }, - urls: { - type: 'array', - value: '', - offset: 0, - children: [ - { - type: 'string', - offset: 0, - value: - 'https://github.com/bazelbuild/rules_nodejs/releases/download/5.5.3/rules_nodejs-core-5.5.3.tar.gz', - }, - ], - }, - }, + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'foo_bar', + sha256: 'abcdef0123abcdef0123abcdef0123abcdef0123', + urls: [ + 'https://example.com/foo/bar', + 'https://github.com/foo/bar/releases/download/1.2.3/foobar-1.2.3.tar.gz', + ], }) ).toEqual({ + currentValue: '1.2.3', datasource: 'github-releases', + depName: 'foo_bar', depType: 'http_archive', - depName: 'rules_nodejs', - packageName: 'bazelbuild/rules_nodejs', - currentValue: '5.5.3', + packageName: 'foo/bar', + }); + + expect( + extractDepFromFragmentData({ + rule: 'http_archive', + name: 'aspect_rules_js', + sha256: + 'db9f446752fe4100320cf8487e8fd476b9af0adf6b99b601bcfd70b289bb0598', + urls: [ + 'https://github.com/aspect-build/rules_js/archive/refs/tags/v1.1.2.tar.gz', + ], + }) + ).toEqual({ + currentValue: 'v1.1.2', + datasource: 'github-tags', + depName: 'aspect_rules_js', + depType: 'http_archive', + packageName: 'aspect-build/rules_js', + }); + }); + }); + + describe('docker', () => { + it('extracts docker dependencies', () => { + expect( + extractDepFromFragmentData({ rule: 'foo_bar', name: 'foo_bar' }) + ).toBeNull(); + + expect( + extractDepFromFragmentData({ + rule: 'container_pull', + name: 'foo_bar', + tag: '1.2.3', + digest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + repository: 'example.com/foo/bar', + registry: 'https://example.com', + }) + ).toEqual({ + currentDigest: 'abcdef0123abcdef0123abcdef0123abcdef0123', + currentValue: '1.2.3', + datasource: 'docker', + depName: 'foo_bar', + depType: 'container_pull', + packageName: 'example.com/foo/bar', + registryUrls: ['https://example.com'], + versioning: 'docker', }); }); }); diff --git a/lib/modules/manager/bazel/rules/index.ts b/lib/modules/manager/bazel/rules/index.ts index ec9c7966541da2580e97b5fe06db3fd342371e3e..8296c4514d2fa673d22762ebe873b165ed2220e9 100644 --- a/lib/modules/manager/bazel/rules/index.ts +++ b/lib/modules/manager/bazel/rules/index.ts @@ -1,74 +1,35 @@ -import is from '@sindresorhus/is'; -import { logger } from '../../../../logger'; +import { z } from 'zod'; import { regEx } from '../../../../util/regex'; import type { PackageDependency } from '../../types'; -import type { - Fragment, - StringFragment, - Target, - TargetAttribute, -} from '../types'; -import { dockerDependency } from './docker'; -import { gitDependency } from './git'; -import { goDependency } from './go'; -import { httpDependency } from './http'; - -type DependencyExtractor = (_: Target) => PackageDependency | null; -type DependencyExtractorRegistry = Record<string, DependencyExtractor>; - -const dependencyExtractorRegistry: DependencyExtractorRegistry = { - git_repository: gitDependency, - go_repository: goDependency, - http_archive: httpDependency, - http_file: httpDependency, - container_pull: dockerDependency, -}; - -const supportedRules = Object.keys(dependencyExtractorRegistry); +import { extract } from '../parser'; +import type { Fragment, FragmentData, Target } from '../types'; +import { DockerTarget, dockerRules } from './docker'; +import { GitTarget, gitRules } from './git'; +import { GoTarget, goRules } from './go'; +import { HttpTarget, httpRules } from './http'; + +const Target = z.union([DockerTarget, GitTarget, GoTarget, HttpTarget]); + +/** + * Gather all rule names supported by Renovate in order to speed up parsing + * by filtering out other syntactically correct rules we don't support yet. + */ +const supportedRules = [...dockerRules, ...gitRules, ...goRules, ...httpRules]; export const supportedRulesRegex = regEx(`^${supportedRules.join('|')}$`); -function isTarget(x: Record<string, TargetAttribute>): x is Target { - return is.string(x.name) && is.string(x.rule); -} - -export function coerceFragmentToTarget(fragment: Fragment): Target | null { - if (fragment.type === 'record') { - const { children } = fragment; - const target: Record<string, TargetAttribute> = {}; - for (const [key, value] of Object.entries(children)) { - if (value.type === 'array') { - const values = value.children - .filter((x): x is StringFragment => x.type === 'string') - .map((x) => x.value); - target[key] = values; - } else if (value.type === 'string') { - target[key] = value.value; - } - } - - if (isTarget(target)) { - return target; - } +export function extractDepFromFragmentData( + fragmentData: FragmentData +): PackageDependency | null { + const res = Target.safeParse(fragmentData); + if (!res.success) { + return null; } - - return null; + return res.data; } export function extractDepFromFragment( fragment: Fragment ): PackageDependency | null { - const target = coerceFragmentToTarget(fragment); - if (!target) { - return null; - } - - const dependencyExtractor = dependencyExtractorRegistry[target.rule]; - if (!dependencyExtractor) { - logger.debug( - `Bazel dependency extractor function not found for ${target.rule}` - ); - return null; - } - - return dependencyExtractor(target); + const fragmentData = extract(fragment); + return extractDepFromFragmentData(fragmentData); } diff --git a/lib/modules/manager/bazel/types.ts b/lib/modules/manager/bazel/types.ts index 10db409ac852c1bbc93751651b4e12b07a62dd57..951abee9c4ba6aa5e430b281619492538ef86ec2 100644 --- a/lib/modules/manager/bazel/types.ts +++ b/lib/modules/manager/bazel/types.ts @@ -36,3 +36,8 @@ export interface StringFragment extends FragmentBase { export type NestedFragment = ArrayFragment | RecordFragment; export type Fragment = NestedFragment | StringFragment; + +export type FragmentData = + | string + | FragmentData[] + | { [k: string]: FragmentData };