mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 06:51:11 +00:00
2f71422620
* feat(exec): register bazelisk as containerbase tool * test(bazel-module): add failing tests for updateArtifacts * feat(bazel-module): implement updateArtifacts for lockfile support * feat(bazel-module): export updateArtifacts from manager index * fix(bazel-module): address code review feedback - Use logger.warn instead of debug for artifact errors - Add utf8 encoding to readLocalFile calls - Use getSiblingFileName for lockfile path derivation - Add empty updatedDeps guard - Strengthen test assertions * docs(bazel-module): document lockfile support * Update lib/modules/manager/bazel-module/index.ts Co-authored-by: Michael Kriese <michael.kriese@gmx.de> * Apply suggestions from code review Co-authored-by: Michael Kriese <michael.kriese@gmx.de> * fix(bazel-module): address review feedback - Remove unnecessary afterEach in tests - Use partial<StatusResult> for all StatusResult mocks - Add lockfile maintenance support - Export supportsLockFileMaintenance flag * fix(bazel-module): handle untracked lock files and clarify docs Check status.not_added alongside status.modified for lock file changes, matching the pattern used by other managers (composer, gomod, etc.). Clarify that Bazelisk (not Renovate) reads .bazelversion. Add test for lockfile maintenance with no changes. * feat(bazelisk): implement updateArtifacts for MODULE.bazel.lock When the Bazel version in .bazelversion changes, run `bazel mod deps` to regenerate MODULE.bazel.lock and return the updated lockfile as an artifact. * feat(bazelisk): add lockfile maintenance support * docs(bazelisk): add lockfile support section linking to bazel-module docs * refactor(bazel-module): extract shared lockfile update helper * refactor(bazel-module): delegate lockfile update to shared helper Replace inline lockfile update logic in artifacts.ts with a call to the shared updateBazelLockfile() helper, and update tests to mock the helper. * refactor(bazelisk): delegate lockfile update to shared helper Replace inline lockfile update logic in bazelisk/artifacts.ts with a call to the shared updateBazelLockfile() helper from bazel-module/lockfile.ts. * fix(bazelisk): use index.md for cross-manager docs link The docs pipeline renames readme.md to index.md, so cross-manager links must reference index.md to pass the docs link checker. * feat(bazel-module,bazelisk): support bazelisk version constraint Pass config.constraints.bazelisk through to toolConstraints so users can pin the bazelisk version if the newest release is broken. * fix(bazel-module): rename lockFile to fileName in ArtifactError Upstream renamed lockFile to fileName in ArtifactError type (#41631). --------- Co-authored-by: Michael Kriese <michael.kriese@gmx.de>
386 lines
10 KiB
TypeScript
386 lines
10 KiB
TypeScript
import { isString } from '@sindresorhus/is';
|
|
import { quote } from 'shlex';
|
|
import { GlobalConfig } from '../../config/global.ts';
|
|
import { logger } from '../../logger/index.ts';
|
|
import type { ReleaseResult } from '../../modules/datasource/index.ts';
|
|
import { id as composerVersioningId } from '../../modules/versioning/composer/index.ts';
|
|
import { id as condaVersioningId } from '../../modules/versioning/conda/index.ts';
|
|
import { id as gradleVersioningId } from '../../modules/versioning/gradle/index.ts';
|
|
import * as allVersioning from '../../modules/versioning/index.ts';
|
|
import { id as mavenVersioningId } from '../../modules/versioning/maven/index.ts';
|
|
import { id as nodeVersioningId } from '../../modules/versioning/node/index.ts';
|
|
import { id as npmVersioningId } from '../../modules/versioning/npm/index.ts';
|
|
import { id as pep440VersioningId } from '../../modules/versioning/pep440/index.ts';
|
|
import { id as pythonVersioningId } from '../../modules/versioning/python/index.ts';
|
|
import { id as rubyVersioningId } from '../../modules/versioning/ruby/index.ts';
|
|
import { id as semverVersioningId } from '../../modules/versioning/semver/index.ts';
|
|
import { id as semverCoercedVersioningId } from '../../modules/versioning/semver-coerced/index.ts';
|
|
import { getEnv } from '../env.ts';
|
|
import type { Opt, ToolConfig, ToolConstraint } from './types.ts';
|
|
|
|
const allToolConfig: Record<string, ToolConfig> = {
|
|
bazelisk: {
|
|
datasource: 'github-releases',
|
|
packageName: 'bazelbuild/bazelisk',
|
|
versioning: semverVersioningId,
|
|
},
|
|
bun: {
|
|
datasource: 'github-releases',
|
|
packageName: 'oven-sh/bun',
|
|
extractVersion: '^bun-v(?<version>.*)$',
|
|
versioning: npmVersioningId,
|
|
},
|
|
bundler: {
|
|
datasource: 'rubygems',
|
|
packageName: 'bundler',
|
|
versioning: rubyVersioningId,
|
|
},
|
|
cocoapods: {
|
|
datasource: 'rubygems',
|
|
packageName: 'cocoapods',
|
|
versioning: rubyVersioningId,
|
|
},
|
|
composer: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/composer-prebuild',
|
|
versioning: composerVersioningId,
|
|
},
|
|
conan: {
|
|
datasource: 'pypi',
|
|
packageName: 'conan',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
copier: {
|
|
datasource: 'pypi',
|
|
packageName: 'copier',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
corepack: {
|
|
datasource: 'npm',
|
|
packageName: 'corepack',
|
|
versioning: npmVersioningId,
|
|
},
|
|
devbox: {
|
|
datasource: 'github-releases',
|
|
packageName: 'jetify-com/devbox',
|
|
versioning: semverVersioningId,
|
|
},
|
|
dotnet: {
|
|
datasource: 'dotnet-version',
|
|
packageName: 'dotnet-sdk',
|
|
versioning: semverVersioningId,
|
|
},
|
|
erlang: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/erlang-prebuild',
|
|
versioning: semverCoercedVersioningId,
|
|
},
|
|
elixir: {
|
|
datasource: 'github-releases',
|
|
packageName: 'elixir-lang/elixir',
|
|
versioning: semverVersioningId,
|
|
},
|
|
flux: {
|
|
datasource: 'github-releases',
|
|
packageName: 'fluxcd/flux2',
|
|
versioning: semverVersioningId,
|
|
},
|
|
gleam: {
|
|
datasource: 'github-releases',
|
|
packageName: 'gleam-lang/gleam',
|
|
versioning: semverVersioningId,
|
|
},
|
|
golang: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/golang-prebuild',
|
|
versioning: npmVersioningId,
|
|
},
|
|
gradle: {
|
|
datasource: 'gradle-version',
|
|
packageName: 'gradle',
|
|
versioning: gradleVersioningId,
|
|
},
|
|
hashin: {
|
|
datasource: 'pypi',
|
|
packageName: 'hashin',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
helm: {
|
|
datasource: 'github-releases',
|
|
packageName: 'helm/helm',
|
|
versioning: semverVersioningId,
|
|
},
|
|
helmfile: {
|
|
datasource: 'github-releases',
|
|
packageName: 'helmfile/helmfile',
|
|
versioning: semverVersioningId,
|
|
},
|
|
java: {
|
|
datasource: 'java-version',
|
|
packageName: 'java?system=true',
|
|
versioning: npmVersioningId,
|
|
},
|
|
/* not used in Renovate */
|
|
'java-maven': {
|
|
datasource: 'java-version',
|
|
packageName: 'java?system=true',
|
|
versioning: mavenVersioningId,
|
|
},
|
|
jb: {
|
|
datasource: 'github-releases',
|
|
packageName: 'jsonnet-bundler/jsonnet-bundler',
|
|
versioning: semverVersioningId,
|
|
},
|
|
kustomize: {
|
|
datasource: 'github-releases',
|
|
packageName: 'kubernetes-sigs/kustomize',
|
|
extractVersion: '^kustomize/v(?<version>.*)$',
|
|
versioning: semverVersioningId,
|
|
},
|
|
maven: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/maven-prebuild',
|
|
versioning: mavenVersioningId,
|
|
},
|
|
nix: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/nix-prebuild',
|
|
versioning: semverVersioningId,
|
|
},
|
|
node: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/node-prebuild',
|
|
versioning: nodeVersioningId,
|
|
},
|
|
npm: {
|
|
datasource: 'npm',
|
|
packageName: 'npm',
|
|
versioning: npmVersioningId,
|
|
},
|
|
pdm: {
|
|
datasource: 'github-releases',
|
|
packageName: 'pdm-project/pdm',
|
|
versioning: semverVersioningId,
|
|
},
|
|
php: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/php-prebuild',
|
|
versioning: composerVersioningId,
|
|
},
|
|
'pip-tools': {
|
|
datasource: 'pypi',
|
|
packageName: 'pip-tools',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
pipenv: {
|
|
datasource: 'pypi',
|
|
packageName: 'pipenv',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
pnpm: {
|
|
datasource: 'npm',
|
|
packageName: 'pnpm',
|
|
versioning: npmVersioningId,
|
|
},
|
|
pixi: {
|
|
datasource: 'github-releases',
|
|
packageName: 'prefix-dev/pixi',
|
|
versioning: condaVersioningId,
|
|
extractVersion: '^v(?<version>.*)$',
|
|
},
|
|
poetry: {
|
|
datasource: 'pypi',
|
|
packageName: 'poetry',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
python: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/python-prebuild',
|
|
versioning: pythonVersioningId,
|
|
},
|
|
ruby: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/ruby-prebuild',
|
|
versioning: rubyVersioningId,
|
|
},
|
|
rust: {
|
|
datasource: 'docker',
|
|
packageName: 'rust',
|
|
versioning: semverVersioningId,
|
|
},
|
|
uv: {
|
|
datasource: 'pypi',
|
|
packageName: 'uv',
|
|
versioning: pep440VersioningId,
|
|
},
|
|
yarn: {
|
|
datasource: 'npm',
|
|
packageName: 'yarn',
|
|
versioning: npmVersioningId,
|
|
},
|
|
'yarn-slim': {
|
|
datasource: 'npm',
|
|
packageName: 'yarn',
|
|
versioning: npmVersioningId,
|
|
},
|
|
dart: {
|
|
datasource: 'dart-version',
|
|
packageName: 'dart',
|
|
versioning: npmVersioningId,
|
|
},
|
|
flutter: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/flutter-prebuild',
|
|
versioning: npmVersioningId,
|
|
},
|
|
vendir: {
|
|
datasource: 'github-releases',
|
|
packageName: 'carvel-dev/vendir',
|
|
versioning: semverVersioningId,
|
|
},
|
|
};
|
|
|
|
let _getPkgReleases: Promise<
|
|
typeof import('../../modules/datasource/index.js')
|
|
> | null = null;
|
|
|
|
async function getPkgReleases(
|
|
toolConfig: ToolConfig,
|
|
): Promise<ReleaseResult | null> {
|
|
_getPkgReleases ??= import('../../modules/datasource/index.js');
|
|
const { getPkgReleases } = await _getPkgReleases;
|
|
return getPkgReleases(toolConfig);
|
|
}
|
|
|
|
export function supportsDynamicInstall(toolName: string): boolean {
|
|
return !!allToolConfig[toolName];
|
|
}
|
|
|
|
export function isContainerbase(): boolean {
|
|
return !!getEnv().CONTAINERBASE;
|
|
}
|
|
|
|
export function isDynamicInstall(
|
|
toolConstraints?: Opt<ToolConstraint[]>,
|
|
): boolean {
|
|
if (GlobalConfig.get('binarySource') !== 'install') {
|
|
return false;
|
|
}
|
|
if (!isContainerbase()) {
|
|
logger.debug('Falling back to binarySource=global');
|
|
return false;
|
|
}
|
|
return (
|
|
!toolConstraints ||
|
|
toolConstraints.every((toolConstraint) =>
|
|
supportsDynamicInstall(toolConstraint.toolName),
|
|
)
|
|
);
|
|
}
|
|
|
|
function isStable(
|
|
version: string,
|
|
versioningApi: allVersioning.VersioningApi,
|
|
latest?: string,
|
|
): boolean {
|
|
if (!versioningApi.isStable(version)) {
|
|
return false;
|
|
}
|
|
if (isString(latest)) {
|
|
if (versioningApi.isGreaterThan(version, latest)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export async function resolveConstraint(
|
|
toolConstraint: ToolConstraint,
|
|
): Promise<string> {
|
|
const { toolName } = toolConstraint;
|
|
const toolConfig = allToolConfig[toolName];
|
|
if (!toolConfig) {
|
|
throw new Error(`Invalid tool to install: ${toolName}`);
|
|
}
|
|
|
|
const versioning = allVersioning.get(toolConfig.versioning);
|
|
let constraint = toolConstraint.constraint;
|
|
if (constraint) {
|
|
if (versioning.isValid(constraint)) {
|
|
if (versioning.isSingleVersion(constraint)) {
|
|
return constraint.replace(/^=+/, '').trim();
|
|
}
|
|
} else {
|
|
logger.warn(
|
|
{ toolName, constraint, versioning: toolConfig.versioning },
|
|
'Invalid tool constraint',
|
|
);
|
|
constraint = undefined;
|
|
}
|
|
}
|
|
|
|
const pkgReleases = await getPkgReleases(toolConfig);
|
|
const releases = pkgReleases?.releases ?? [];
|
|
|
|
if (!releases?.length) {
|
|
logger.warn({ toolConfig }, 'No tool releases found.');
|
|
throw new Error('No tool releases found.');
|
|
}
|
|
|
|
const matchingReleases = releases.filter(
|
|
(r) => !constraint || versioning.matches(r.version, constraint),
|
|
);
|
|
|
|
const stableMatchingVersion = matchingReleases
|
|
.filter((r) => isStable(r.version, versioning, pkgReleases?.tags?.latest))
|
|
.pop()?.version;
|
|
if (stableMatchingVersion) {
|
|
logger.debug(
|
|
{ toolName, constraint, resolvedVersion: stableMatchingVersion },
|
|
'Resolved stable matching version',
|
|
);
|
|
return stableMatchingVersion;
|
|
}
|
|
|
|
const unstableMatchingVersion = matchingReleases.pop()?.version;
|
|
if (unstableMatchingVersion) {
|
|
logger.debug(
|
|
{ toolName, constraint, resolvedVersion: unstableMatchingVersion },
|
|
'Resolved unstable matching version',
|
|
);
|
|
return unstableMatchingVersion;
|
|
}
|
|
|
|
const stableVersion = releases
|
|
.filter((r) => isStable(r.version, versioning, pkgReleases?.tags?.latest))
|
|
.pop()?.version;
|
|
if (stableVersion) {
|
|
logger.warn(
|
|
{ toolName, constraint, stableVersion },
|
|
'No matching tool versions found for constraint - using latest stable version',
|
|
);
|
|
}
|
|
|
|
const highestVersion = releases.pop()!.version;
|
|
logger.warn(
|
|
{ toolName, constraint, highestVersion },
|
|
'No matching or stable tool versions found - using an unstable version',
|
|
);
|
|
return highestVersion;
|
|
}
|
|
|
|
export async function generateInstallCommands(
|
|
toolConstraints: Opt<ToolConstraint[]>,
|
|
): Promise<string[]> {
|
|
const installCommands: string[] = [];
|
|
if (toolConstraints?.length) {
|
|
for (const toolConstraint of toolConstraints) {
|
|
const toolVersion = await resolveConstraint(toolConstraint);
|
|
const { toolName } = toolConstraint;
|
|
const installCommand = `install-tool ${toolName} ${quote(toolVersion)}`;
|
|
installCommands.push(installCommand);
|
|
}
|
|
}
|
|
return installCommands;
|
|
}
|