mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-26 15:31:53 +00:00
a2b96944f9
* feat(mise): add lock file support - Add lockfile.ts with getLockFileName() for deriving lock file paths - Add artifacts.ts with updateArtifacts() and updateLockedDependency() - Add MiseLockFile Zod schema for parsing lock files - Update extract.ts to be async and extract lockedVersion from lock files - Export supportsLockFileMaintenance, lockFileNames, updateArtifacts, updateLockedDependency * refactor(mise): remove redundant comments Remove comments that simply restate what the code already clearly communicates. Keep valuable comments that explain non-obvious behavior (e.g., why temporary errors are rethrown) and move helpful examples into JSDoc comments. * fix(mise): improve lock file version lookup and add tests - Fix lock file key lookup for non-registry tools (aqua:, ubi:, etc.) Registry tools use short names (node), non-registry use full names (aqua:cli/cli). New getLockedVersion() tries both patterns. - Add injection prevention test for shell quoting - Add update-failed test for error handling - Add comprehensive tests for getLockedVersion() - Add null check for depName in updateLockedDependency - Add JSDoc to updateArtifacts and extractPackageFile - Add lock file support documentation to README - Remove code duplication by consolidating into lockfile.ts * fix(mise): pass --local flag and MISE_ENV for env/local config files When updating mise.local.toml, `mise lock` updates mise.lock, not mise.local.lock. Add --local flag to target the correct lock file. For env-specific configs (mise.test.toml), set MISE_ENV=test so mise reads and locks the correct configuration. Extracted getConfigType() from getLockFileName() to share the parsing logic between lock file name derivation and command flag generation. Builds on #42099 by altendky. * test(mise): cover tool absent from lock file * refactor(mise): remove dead branches in extractPackageFile tools is required by MiseFile schema so if(tools) is always true. depName is always set by createDependency so if(dep.depName) is always true. Add test for tool absent from lock file to cover the lockedVersion assignment branch. * fix(mise): address review feedback on lock file support * fix(mise): remove unsupported tool constraint * fix(mise): add containerbase tool definition * fix(mise): add typed exec constraint support * fix(config): restore requireConfig typing * test(mise): await registry extraction cases * Update lib/modules/manager/mise/artifacts.ts Co-authored-by: Michael Kriese <michael.kriese@gmx.de> * fix: revert unrelated types cleanup * fix: drop missing VersioningName import * fix(mise): use npm versioning string after import removal * test(mise): set binarySource=global in artifacts spec GlobalConfig.get now returns defaults; without explicit binarySource the test fell into containerbase dynamic install and tried to resolve the mise version via the github-releases datasource, causing missing HTTP mock errors. Force binarySource=global so the spec only exercises the mocked exec path. --------- Co-authored-by: Kyle Altendorf <sda@fstab.net> Co-authored-by: Michael Kriese <michael.kriese@gmx.de> Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
391 lines
9.6 KiB
TypeScript
391 lines
9.6 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 type { VersioningApi } from '../../modules/versioning/types.ts';
|
|
import { getEnv } from '../env.ts';
|
|
import type { Opt, ToolConfig, ToolConstraint, ToolName } from './types.ts';
|
|
|
|
export const allToolConfig: Record<ToolName, ToolConfig> = {
|
|
bazelisk: {
|
|
datasource: 'github-releases',
|
|
packageName: 'bazelbuild/bazelisk',
|
|
versioning: 'semver',
|
|
},
|
|
bun: {
|
|
datasource: 'github-releases',
|
|
packageName: 'oven-sh/bun',
|
|
extractVersion: '^bun-v(?<version>.*)$',
|
|
versioning: 'npm',
|
|
},
|
|
bundler: {
|
|
datasource: 'rubygems',
|
|
packageName: 'bundler',
|
|
versioning: 'ruby',
|
|
},
|
|
cocoapods: {
|
|
datasource: 'rubygems',
|
|
packageName: 'cocoapods',
|
|
versioning: 'ruby',
|
|
},
|
|
composer: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/composer-prebuild',
|
|
versioning: 'composer',
|
|
},
|
|
conan: {
|
|
datasource: 'pypi',
|
|
packageName: 'conan',
|
|
versioning: 'pep440',
|
|
},
|
|
copier: {
|
|
datasource: 'pypi',
|
|
packageName: 'copier',
|
|
versioning: 'pep440',
|
|
},
|
|
corepack: {
|
|
datasource: 'npm',
|
|
packageName: 'corepack',
|
|
versioning: 'npm',
|
|
},
|
|
deno: {
|
|
datasource: 'github-releases',
|
|
packageName: 'denoland/deno',
|
|
versioning: 'deno',
|
|
},
|
|
devbox: {
|
|
datasource: 'github-releases',
|
|
packageName: 'jetify-com/devbox',
|
|
versioning: 'semver',
|
|
},
|
|
dotnet: {
|
|
datasource: 'dotnet-version',
|
|
packageName: 'dotnet-sdk',
|
|
versioning: 'semver',
|
|
},
|
|
erlang: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/erlang-prebuild',
|
|
versioning: 'semver-coerced',
|
|
},
|
|
elixir: {
|
|
datasource: 'github-releases',
|
|
packageName: 'elixir-lang/elixir',
|
|
versioning: 'semver',
|
|
},
|
|
flux: {
|
|
datasource: 'github-releases',
|
|
packageName: 'fluxcd/flux2',
|
|
versioning: 'semver',
|
|
},
|
|
gleam: {
|
|
datasource: 'github-releases',
|
|
packageName: 'gleam-lang/gleam',
|
|
versioning: 'semver',
|
|
},
|
|
golang: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/golang-prebuild',
|
|
versioning: 'npm',
|
|
},
|
|
gradle: {
|
|
datasource: 'gradle-version',
|
|
packageName: 'gradle',
|
|
versioning: 'gradle',
|
|
},
|
|
hashin: {
|
|
datasource: 'pypi',
|
|
packageName: 'hashin',
|
|
versioning: 'pep440',
|
|
},
|
|
helm: {
|
|
datasource: 'github-releases',
|
|
packageName: 'helm/helm',
|
|
versioning: 'semver',
|
|
},
|
|
helmfile: {
|
|
datasource: 'github-releases',
|
|
packageName: 'helmfile/helmfile',
|
|
versioning: 'semver',
|
|
},
|
|
java: {
|
|
datasource: 'java-version',
|
|
packageName: 'java?system=true',
|
|
versioning: 'npm',
|
|
},
|
|
/* not used in Renovate */
|
|
'java-maven': {
|
|
datasource: 'java-version',
|
|
packageName: 'java?system=true',
|
|
versioning: 'maven',
|
|
},
|
|
jb: {
|
|
datasource: 'github-releases',
|
|
packageName: 'jsonnet-bundler/jsonnet-bundler',
|
|
versioning: 'semver',
|
|
},
|
|
kustomize: {
|
|
datasource: 'github-releases',
|
|
packageName: 'kubernetes-sigs/kustomize',
|
|
extractVersion: '^kustomize/v(?<version>.*)$',
|
|
versioning: 'semver',
|
|
},
|
|
maven: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/maven-prebuild',
|
|
versioning: 'maven',
|
|
},
|
|
mise: {
|
|
datasource: 'github-releases',
|
|
packageName: 'jdx/mise',
|
|
versioning: 'npm',
|
|
},
|
|
nix: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/nix-prebuild',
|
|
versioning: 'semver',
|
|
},
|
|
node: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/node-prebuild',
|
|
versioning: 'node',
|
|
},
|
|
npm: {
|
|
datasource: 'npm',
|
|
packageName: 'npm',
|
|
versioning: 'npm',
|
|
},
|
|
pdm: {
|
|
datasource: 'github-releases',
|
|
packageName: 'pdm-project/pdm',
|
|
versioning: 'semver',
|
|
},
|
|
php: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/php-prebuild',
|
|
versioning: 'composer',
|
|
},
|
|
'pip-tools': {
|
|
datasource: 'pypi',
|
|
packageName: 'pip-tools',
|
|
versioning: 'pep440',
|
|
},
|
|
pipenv: {
|
|
datasource: 'pypi',
|
|
packageName: 'pipenv',
|
|
versioning: 'pep440',
|
|
},
|
|
pnpm: {
|
|
datasource: 'npm',
|
|
packageName: 'pnpm',
|
|
versioning: 'npm',
|
|
},
|
|
pixi: {
|
|
datasource: 'github-releases',
|
|
packageName: 'prefix-dev/pixi',
|
|
versioning: 'conda',
|
|
extractVersion: '^v(?<version>.*)$',
|
|
},
|
|
poetry: {
|
|
datasource: 'pypi',
|
|
packageName: 'poetry',
|
|
versioning: 'pep440',
|
|
},
|
|
python: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/python-prebuild',
|
|
versioning: 'python',
|
|
},
|
|
ruby: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/ruby-prebuild',
|
|
versioning: 'ruby',
|
|
},
|
|
rust: {
|
|
datasource: 'docker',
|
|
packageName: 'rust',
|
|
versioning: 'semver',
|
|
},
|
|
uv: {
|
|
datasource: 'pypi',
|
|
packageName: 'uv',
|
|
versioning: 'pep440',
|
|
},
|
|
yarn: {
|
|
datasource: 'npm',
|
|
packageName: 'yarn',
|
|
versioning: 'npm',
|
|
},
|
|
'yarn-slim': {
|
|
datasource: 'npm',
|
|
packageName: 'yarn',
|
|
versioning: 'npm',
|
|
},
|
|
dart: {
|
|
datasource: 'dart-version',
|
|
packageName: 'dart',
|
|
versioning: 'npm',
|
|
},
|
|
flutter: {
|
|
datasource: 'github-releases',
|
|
packageName: 'containerbase/flutter-prebuild',
|
|
versioning: 'npm',
|
|
},
|
|
vendir: {
|
|
datasource: 'github-releases',
|
|
packageName: 'carvel-dev/vendir',
|
|
versioning: 'semver',
|
|
},
|
|
} as const;
|
|
|
|
let _getPkgReleases: Promise<
|
|
typeof import('../../modules/datasource/index.ts')
|
|
> | null = null;
|
|
|
|
async function getPkgReleases(
|
|
toolConfig: ToolConfig,
|
|
): Promise<ReleaseResult | null> {
|
|
_getPkgReleases ??= import('../../modules/datasource/index.ts');
|
|
const { getPkgReleases } = await _getPkgReleases;
|
|
return getPkgReleases(toolConfig);
|
|
}
|
|
|
|
export function getToolConfig(toolName: ToolName): ToolConfig {
|
|
return allToolConfig[toolName];
|
|
}
|
|
|
|
export function supportsDynamicInstall(toolName: ToolName): 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: 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 { get: getVersioning } =
|
|
await import('../../modules/versioning/index.ts');
|
|
const versioning = getVersioning(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;
|
|
}
|