mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-26 15:31:53 +00:00
1ea8602aaa
* test(manager): cover twelve else-if branches The trailing `else if` of a chain leaves v8 counting an implicit else that no coverage-ignore hint can suppress. Fourteen of those were marked as structurally unreachable, but only two of them actually are -- each of these twelve has an input that takes it: - `bazel`: an archive url whose file name is only a suffix, which strips to an empty value, so neither value nor digest is set - `cargo`: a `[package]` section with no `version` at all - `composer`: an inline `package` repository, which the discriminated union does carry, contrary to the comment that claimed otherwise - `deps-edn`: an `:mvn/repos` entry whose spec is a plain string - `gomod`: an update where only the `go` directive changed, leaving no additional dependencies to count - `helmfile`: a release with an empty chart name, which is neither a local path nor an oci ref and yields an empty repo name - `nuget`: a lock file that is gone after the restore - `swift`: a comma where `from` expects a colon, and where `from:` expects a version -- both are labels the state machine searches for and then ignores - `terraform`: a module source too short to be a registry module - `tflint-plugin`: a quoted attribute that is neither version nor source - `travis`: a matrix `node_js` that is a number The stale comments go with them. Three more in `swift` were already wrong before this change: the branches they described were covered. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * refactor(manager): collapse two exhaustive else-if chains `glasskube` and `gradle` were the only two of the fourteen trailing `else if` branches that really are unreachable, and both are provable from a type rather than from a reading of the surrounding code: - `glasskube` parses resources with a schema that admits exactly `Package`, `ClusterPackage` and `PackageRepository`, and filters anything else out - `gradle`'s `ContentDescriptorSpec.mode` is `'include' | 'exclude'` Turning the final `else if` into a plain `else` drops the phantom branch v8 was counting. With these, the manager tree reaches 100% branch coverage. The glasskube check is inverted so that the single-literal discriminant narrows for the compiler. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * test(manager/travis): inline the numeric node_js fixture The three-line fixture went into `__fixtures__` only because every other fixture in this spec is loaded through `Fixtures.get`. A reader of the test should be able to see the input without opening a second file, so write it inline with `codeBlock`. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> --------- Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
167 lines
4.4 KiB
TypeScript
167 lines
4.4 KiB
TypeScript
import { isString, isTruthy } from '@sindresorhus/is';
|
|
import { z } from 'zod/v4';
|
|
import { regEx } from '../../../../util/regex.ts';
|
|
import { parseUrl } from '../../../../util/url.ts';
|
|
import { GithubReleasesDatasource } from '../../../datasource/github-releases/index.ts';
|
|
import { GithubTagsDatasource } from '../../../datasource/github-tags/index.ts';
|
|
import { GitlabReleasesDatasource } from '../../../datasource/gitlab-releases/index.ts';
|
|
import { GitlabTagsDatasource } from '../../../datasource/gitlab-tags/index.ts';
|
|
import type { PackageDependency } from '../../types.ts';
|
|
|
|
// Source: https://bazel.build/rules/lib/repo/http
|
|
const archives = [
|
|
'.zip',
|
|
'.tar',
|
|
'.jar',
|
|
'.war',
|
|
'.aar',
|
|
'.ar',
|
|
'.deb',
|
|
|
|
'.gz',
|
|
'.tar.gz',
|
|
'.tgz',
|
|
|
|
'.bz2',
|
|
'.tar.bz2',
|
|
'.tbz2',
|
|
|
|
'.xz',
|
|
'.tar.xz',
|
|
'.txz',
|
|
|
|
'.zst',
|
|
'.tar.zst',
|
|
'.tzst',
|
|
];
|
|
|
|
const archiveSuffixRegex = regEx(
|
|
`(?:${archives.map((archive) => RegExp.escape(archive)).join('|')})$`,
|
|
);
|
|
|
|
function stripArchiveSuffix(value: string): string {
|
|
return value.replace(archiveSuffixRegex, '');
|
|
}
|
|
|
|
function isHash(value: unknown): value is string {
|
|
return isString(value) && regEx(/[0-9a-z]{40}/i).test(value);
|
|
}
|
|
|
|
export function parseGithubPath(
|
|
pathname: string,
|
|
): Partial<PackageDependency> | null {
|
|
const [p0, p1, p2, p3, p4, p5] = pathname.split('/').slice(1);
|
|
const packageName = `${p0}/${p1}`;
|
|
let datasource = '';
|
|
let value: string | null = null;
|
|
if (p2 === 'releases' && p3 === 'download') {
|
|
datasource = GithubReleasesDatasource.id;
|
|
value = p4;
|
|
} else if (p2 === 'archive' && p3 === 'refs' && p4 === 'tags') {
|
|
// https://github.com/foo/bar/archive/refs/tags/v1.2.3.tar.gz
|
|
datasource = GithubTagsDatasource.id;
|
|
value = p5;
|
|
} else if (p2 === 'archive') {
|
|
// https://github.com/foo/bar/archive/1.2.3.tar.gz
|
|
datasource = GithubTagsDatasource.id;
|
|
value = p3;
|
|
}
|
|
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
value = stripArchiveSuffix(value);
|
|
return isHash(value)
|
|
? { datasource, packageName, currentDigest: value }
|
|
: { datasource, packageName, currentValue: value };
|
|
}
|
|
|
|
function parseGitlabPath(pathname: string): Partial<PackageDependency> | null {
|
|
// https://gitlab.com/libeigen/eigen/-/archive/3.3.5/eigen-3.3.5.zip
|
|
// https://gitlab.com/libeigen/eigen/-/archive/90ee821c563fa20db4d64d6991ddca256d5c52f2/eigen-90ee821c563fa20db4d64d6991ddca256d5c52f2.tar.gz
|
|
const [p0, p1, p2, p3, p4] = pathname.split('/').slice(1);
|
|
const packageName = `${p0}/${p1}`;
|
|
if (p2 === '-' && p3 === 'archive' && p4) {
|
|
return isHash(p4)
|
|
? {
|
|
datasource: GitlabTagsDatasource.id,
|
|
packageName,
|
|
currentDigest: p4,
|
|
}
|
|
: {
|
|
datasource: GitlabReleasesDatasource.id,
|
|
packageName,
|
|
currentValue: p4,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function parseArchiveUrl(
|
|
urlString: string | undefined | null,
|
|
): Partial<PackageDependency> | null {
|
|
if (!urlString) {
|
|
return null;
|
|
}
|
|
|
|
const url = parseUrl(urlString);
|
|
|
|
if (url?.host === 'github.com') {
|
|
return parseGithubPath(url.pathname);
|
|
}
|
|
|
|
if (url?.host === 'gitlab.com') {
|
|
return parseGitlabPath(url.pathname);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export const httpRules = [
|
|
'http_archive',
|
|
'_http_archive',
|
|
'http_file',
|
|
'_http_file',
|
|
] as const;
|
|
|
|
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[] => {
|
|
const parsedUrl = [url, ...urls].map(parseArchiveUrl).find(isTruthy);
|
|
if (!parsedUrl) {
|
|
return [];
|
|
}
|
|
|
|
const dep: PackageDependency = {
|
|
datasource: parsedUrl.datasource,
|
|
depType: rule,
|
|
depName: name,
|
|
packageName: parsedUrl.packageName,
|
|
};
|
|
|
|
// We don't want to set both `currentValue` and `currentDigest`.
|
|
//
|
|
// What we want is to provide the first occurrence of `currentValue`,
|
|
// or, if it's not available, `currentDigest`.
|
|
//
|
|
// Auto-replace mechanism will replace this first occurrence,
|
|
// and artifact update function will do the rest.
|
|
//
|
|
// Hence, `if-else-if` is being used here.
|
|
if (parsedUrl.currentValue) {
|
|
dep.currentValue = parsedUrl.currentValue;
|
|
} else if (parsedUrl.currentDigest) {
|
|
dep.currentDigest = parsedUrl.currentDigest;
|
|
}
|
|
|
|
return [dep];
|
|
});
|