mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-31 17:51:49 +00:00
141e136869
* feat(manager/mise): support gitlab backend Maps the mise `gitlab` backend (e.g. `gitlab:gitlab-org/cli`) to the gitlab-releases datasource, mirroring the github backend including version_prefix support. * refactor(manager/mise): use optional instead of Nullish for tool options TOML has no null type, so MiseToolOptions values are only ever a string or absent. .optional() models that exactly; Nullish only adds value for sources that can emit explicit null. * fix(manager/mise): use RegExp.escape in gitlab backend Main switched `createGithubToolConfig` from the `escapeRegExp` util to `RegExp.escape` and dropped the import, which left the newly added `createGitlabToolConfig` referencing an undefined symbol after the merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Michael Kriese <michael.kriese@mend.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
309 lines
8.7 KiB
TypeScript
309 lines
8.7 KiB
TypeScript
import {
|
|
isArray,
|
|
isFunction,
|
|
isNonEmptyObject,
|
|
isNonEmptyString,
|
|
isObject,
|
|
isString,
|
|
} from '@sindresorhus/is';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import { readLocalFile } from '../../../util/fs/index.ts';
|
|
import { coerceObject } from '../../../util/object.ts';
|
|
import { regEx } from '../../../util/regex.ts';
|
|
import type { StaticTooling } from '../asdf/types.ts';
|
|
import type { PackageDependency, PackageFileContent } from '../types.ts';
|
|
import {
|
|
createAquaToolConfig,
|
|
createCargoToolConfig,
|
|
createDotnetToolConfig,
|
|
createGemToolConfig,
|
|
createGithubToolConfig,
|
|
createGitlabToolConfig,
|
|
createGoToolConfig,
|
|
createNpmToolConfig,
|
|
createPipxToolConfig,
|
|
createSpmToolConfig,
|
|
createUbiToolConfig,
|
|
} from './backends.ts';
|
|
import { getLockFileName, getLockedVersion } from './lockfile.ts';
|
|
import type { MiseTool, MiseToolOptions } from './schema.ts';
|
|
import { MiseLockFile } from './schema.ts';
|
|
import type { BackendToolingConfig, ToolingDefinition } from './types.ts';
|
|
import {
|
|
asdfTooling,
|
|
getOrderedMiseRegistryBackends,
|
|
miseTooling,
|
|
} from './upgradeable-tooling.ts';
|
|
import { parseTomlFile } from './utils.ts';
|
|
|
|
// Tool names can have options in the tool name
|
|
// e.g. ubi:tamasfe/taplo[matching=full,exe=taplo]
|
|
const optionInToolNameRegex = regEx(/^(?<name>.+?)(?:\[(?<options>.+)\])?$/);
|
|
|
|
/**
|
|
* Extracts mise tool dependencies from a mise configuration file.
|
|
* Supports various backends (core, asdf, aqua, cargo, etc.) and
|
|
* extracts locked versions when a corresponding lock file exists.
|
|
*/
|
|
export async function extractPackageFile(
|
|
content: string,
|
|
packageFile: string,
|
|
): Promise<PackageFileContent | null> {
|
|
logger.trace(`mise.extractPackageFile(${packageFile})`);
|
|
|
|
const misefile = parseTomlFile(content, packageFile);
|
|
if (!misefile) {
|
|
return null;
|
|
}
|
|
|
|
const deps: PackageDependency[] = [];
|
|
|
|
for (const [name, toolData] of Object.entries(misefile.tools)) {
|
|
deps.push(extractToolEntry(name, toolData, 'tools'));
|
|
}
|
|
|
|
for (const [taskName, taskData] of Object.entries(misefile.tasks)) {
|
|
for (const [name, toolData] of Object.entries(
|
|
coerceObject(taskData.tools),
|
|
)) {
|
|
deps.push(extractToolEntry(name, toolData, `task-${taskName}-tools`));
|
|
}
|
|
}
|
|
|
|
if (!deps.length) {
|
|
return null;
|
|
}
|
|
|
|
const result: PackageFileContent = { deps };
|
|
|
|
const lockFileName = getLockFileName(packageFile);
|
|
const lockFileContent = await readLocalFile(lockFileName, 'utf8');
|
|
|
|
if (lockFileContent) {
|
|
const lockFileParsed = MiseLockFile.safeParse(lockFileContent);
|
|
if (lockFileParsed.success) {
|
|
result.lockFiles = [lockFileName];
|
|
for (const dep of deps) {
|
|
const lockedVersion = getLockedVersion(
|
|
lockFileParsed.data,
|
|
dep.depName!,
|
|
);
|
|
if (lockedVersion) {
|
|
dep.lockedVersion = lockedVersion;
|
|
}
|
|
}
|
|
} else {
|
|
logger.debug(
|
|
{ lockFileName, err: lockFileParsed.error },
|
|
'Failed to parse mise lock file',
|
|
);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function parseVersion(toolData: MiseTool): string | null {
|
|
if (isNonEmptyString(toolData)) {
|
|
// Handle the string case
|
|
// e.g. 'erlang = "23.3"'
|
|
return toolData;
|
|
}
|
|
if (isArray(toolData, isString)) {
|
|
// Handle the array case
|
|
// e.g. 'erlang = ["23.3", "24.0"]'
|
|
return toolData.length ? toolData[0] : null; // Get the first version in the array
|
|
}
|
|
if (isObject(toolData) && isNonEmptyString(toolData.version)) {
|
|
// Handle the object case with a string version
|
|
// e.g. 'python = { version = "3.11.2" }'
|
|
return toolData.version;
|
|
}
|
|
return null; // Return null if no version is found
|
|
}
|
|
|
|
function parseOptions(
|
|
optionsInName: string,
|
|
toolOptions: MiseToolOptions,
|
|
): MiseToolOptions {
|
|
const options = isNonEmptyString(optionsInName)
|
|
? Object.fromEntries(
|
|
optionsInName.split(',').map((option) => option.split('=', 2)),
|
|
)
|
|
: {};
|
|
// Options in toolOptions will override options in the tool name
|
|
return {
|
|
...options,
|
|
...toolOptions,
|
|
};
|
|
}
|
|
|
|
function getToolConfig(
|
|
backend: string,
|
|
toolName: string,
|
|
version: string,
|
|
toolOptions: MiseToolOptions,
|
|
): StaticTooling | BackendToolingConfig | null {
|
|
switch (backend) {
|
|
case '': {
|
|
// If the tool name does not specify a backend, it should be a short name or an alias defined by users
|
|
const staticResult = getRegistryToolConfig(toolName, version);
|
|
if (staticResult) {
|
|
return staticResult;
|
|
}
|
|
|
|
// Otherwise, see if we have any known short tool names that are in the `mise-registry.json` data file
|
|
const backends = getOrderedMiseRegistryBackends(toolName);
|
|
|
|
// prioritise the github backend as the best source for data
|
|
if (backends.github) {
|
|
const result = getToolConfig(
|
|
'github',
|
|
backends.github,
|
|
version,
|
|
toolOptions,
|
|
);
|
|
// v8 ignore else -- TODO: add test #40625
|
|
if (result !== null) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
for (const [backendType, backendName] of Object.entries(backends)) {
|
|
const result = getToolConfig(
|
|
backendType,
|
|
backendName,
|
|
version,
|
|
toolOptions,
|
|
);
|
|
// v8 ignore else -- TODO: add test #40625
|
|
if (result !== null) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
// We can specify core, asdf, vfox, aqua backends for tools in the default registry
|
|
// e.g. 'core:rust', 'asdf:rust', 'vfox:clang', 'aqua:act'
|
|
case 'core':
|
|
return getConfigFromTooling(miseTooling, toolName, version);
|
|
case 'asdf':
|
|
return getConfigFromTooling(asdfTooling, toolName, version);
|
|
case 'vfox':
|
|
return getRegistryToolConfig(toolName, version);
|
|
case 'aqua':
|
|
return (
|
|
getRegistryToolConfig(toolName, version) ??
|
|
createAquaToolConfig(toolName, version)
|
|
);
|
|
case 'cargo':
|
|
return createCargoToolConfig(toolName, version);
|
|
case 'dotnet':
|
|
return createDotnetToolConfig(toolName);
|
|
case 'gem':
|
|
return createGemToolConfig(toolName);
|
|
case 'github':
|
|
return createGithubToolConfig(toolName, version, toolOptions);
|
|
case 'gitlab':
|
|
return createGitlabToolConfig(toolName, version, toolOptions);
|
|
case 'go':
|
|
return createGoToolConfig(toolName);
|
|
case 'npm':
|
|
return createNpmToolConfig(toolName);
|
|
case 'pipx':
|
|
return createPipxToolConfig(toolName);
|
|
case 'spm':
|
|
return createSpmToolConfig(toolName);
|
|
case 'ubi':
|
|
return createUbiToolConfig(toolName, version, toolOptions);
|
|
default:
|
|
// Unsupported backend
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the tooling config for a short name defined in the default registry
|
|
* @link https://mise.jdx.dev/registry.html
|
|
*/
|
|
function getRegistryToolConfig(
|
|
short: string,
|
|
version: string,
|
|
): StaticTooling | null {
|
|
// Try to get the config from miseTooling first, then asdfTooling
|
|
return (
|
|
getConfigFromTooling(miseTooling, short, version) ??
|
|
getConfigFromTooling(asdfTooling, short, version)
|
|
);
|
|
}
|
|
|
|
function getConfigFromTooling(
|
|
toolingSource: Record<string, ToolingDefinition>,
|
|
name: string,
|
|
version: string,
|
|
): StaticTooling | null {
|
|
const toolDefinition = toolingSource[name];
|
|
if (!toolDefinition) {
|
|
return null;
|
|
} // Return null if no toolDefinition is found
|
|
|
|
return (
|
|
(isFunction(toolDefinition.config)
|
|
? toolDefinition.config(version)
|
|
: toolDefinition.config) ?? null
|
|
); // Ensure null is returned instead of undefined
|
|
}
|
|
|
|
function extractToolEntry(
|
|
name: string,
|
|
toolData: MiseTool,
|
|
depType: string,
|
|
): PackageDependency {
|
|
const version = parseVersion(toolData);
|
|
const { name: depName, options: optionsInName } = optionInToolNameRegex.exec(
|
|
name.trim(),
|
|
)!.groups!;
|
|
const delimiterIndex = depName.indexOf(':');
|
|
const backend = depName.substring(0, delimiterIndex);
|
|
const toolName = depName.substring(delimiterIndex + 1);
|
|
const options = parseOptions(
|
|
optionsInName,
|
|
isNonEmptyObject(toolData) ? toolData : {},
|
|
);
|
|
const toolConfig =
|
|
version === null
|
|
? null
|
|
: getToolConfig(backend, toolName, version, options);
|
|
return createDependency(depName, version, toolConfig, depType);
|
|
}
|
|
|
|
function createDependency(
|
|
name: string,
|
|
version: string | null,
|
|
config: StaticTooling | BackendToolingConfig | null,
|
|
depType: string,
|
|
): PackageDependency {
|
|
if (version === null) {
|
|
return {
|
|
depName: name,
|
|
depType,
|
|
skipReason: 'unspecified-version',
|
|
};
|
|
}
|
|
if (config === null) {
|
|
return {
|
|
depName: name,
|
|
depType,
|
|
skipReason: 'unsupported-datasource',
|
|
};
|
|
}
|
|
|
|
return {
|
|
depName: name,
|
|
depType,
|
|
currentValue: version,
|
|
// Spread the config last to override other properties
|
|
...config,
|
|
};
|
|
}
|