This commit is contained in:
Risu 2025-01-04 00:54:14 +01:00 committed by GitHub
commit 6ef2b89a56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1018 additions and 41 deletions

View file

@ -0,0 +1,263 @@
import {
createAquaToolConfig,
createCargoToolConfig,
createDotnetToolConfig,
createGemToolConfig,
createGoToolConfig,
createNpmToolConfig,
createPipxToolConfig,
createSpmToolConfig,
createUbiToolConfig,
} from './backends';
describe('modules/manager/mise/backends', () => {
describe('createAquaToolConfig()', () => {
it('should create a tooling config', () => {
expect(
createAquaToolConfig('BurntSushi/ripgrep', '14.1.1'),
).toStrictEqual({
packageName: 'BurntSushi/ripgrep',
datasource: 'github-tags',
currentValue: '14.1.1',
extractVersion: '^v?(?<version>.+)',
});
});
it('should trim the leading v from version', () => {
expect(
createAquaToolConfig('BurntSushi/ripgrep', 'v14.1.1'),
).toStrictEqual({
packageName: 'BurntSushi/ripgrep',
datasource: 'github-tags',
currentValue: '14.1.1',
extractVersion: '^v?(?<version>.+)',
});
});
});
describe('createCargoToolConfig()', () => {
it('should create a tooling config for crate', () => {
expect(createCargoToolConfig('eza', '')).toStrictEqual({
packageName: 'eza',
datasource: 'crate',
});
});
it('should create a tooling config for git tag', () => {
expect(
createCargoToolConfig('https://github.com/username/demo', 'tag:v0.1.0'),
).toStrictEqual({
packageName: 'https://github.com/username/demo',
currentValue: 'v0.1.0',
datasource: 'git-tags',
});
});
it('should provide skipReason for git branch', () => {
expect(
createCargoToolConfig(
'https://github.com/username/demo',
'branch:main',
),
).toStrictEqual({
packageName: 'https://github.com/username/demo',
skipReason: 'unsupported-version',
});
});
it('should create a tooling config for git rev', () => {
expect(
createCargoToolConfig('https://github.com/username/demo', 'rev:abcdef'),
).toStrictEqual({
packageName: 'https://github.com/username/demo',
currentValue: 'abcdef',
datasource: 'git-refs',
});
});
it('should provide skipReason for invalid version', () => {
expect(
createCargoToolConfig('https://github.com/username/demo', 'v0.1.0'),
).toStrictEqual({
packageName: 'https://github.com/username/demo',
skipReason: 'invalid-version',
});
});
});
describe('createDotnetToolConfig()', () => {
it('should create a tooling config', () => {
expect(createDotnetToolConfig('GitVersion.Tool')).toStrictEqual({
packageName: 'GitVersion.Tool',
datasource: 'nuget',
});
});
});
describe('createGemToolConfig()', () => {
it('should create a tooling config', () => {
expect(createGemToolConfig('rubocop')).toStrictEqual({
packageName: 'rubocop',
datasource: 'rubygems',
});
});
});
describe('createGoToolConfig()', () => {
it('should create a tooling config', () => {
expect(createGoToolConfig('github.com/DarthSim/hivemind')).toStrictEqual({
packageName: 'github.com/DarthSim/hivemind',
datasource: 'go',
});
});
});
describe('createNpmToolConfig()', () => {
it('should create a tooling config', () => {
expect(createNpmToolConfig('prettier')).toStrictEqual({
packageName: 'prettier',
datasource: 'npm',
});
});
});
describe('createPipxToolConfig()', () => {
it('should create a tooling config for pypi package', () => {
expect(createPipxToolConfig('yamllint')).toStrictEqual({
packageName: 'yamllint',
datasource: 'pypi',
});
});
it('should create a tooling config for github shorthand', () => {
expect(createPipxToolConfig('psf/black')).toStrictEqual({
packageName: 'psf/black',
datasource: 'github-tags',
});
});
it('should create a tooling config for github url', () => {
expect(
createPipxToolConfig('git+https://github.com/psf/black.git'),
).toStrictEqual({
packageName: 'psf/black',
datasource: 'github-tags',
});
});
it('should create a tooling config for git url', () => {
expect(
createPipxToolConfig('git+https://gitlab.com/user/repo.git'),
).toStrictEqual({
packageName: 'https://gitlab.com/user/repo',
datasource: 'git-refs',
});
});
it('provides skipReason for zip file url', () => {
expect(
createPipxToolConfig('https://github.com/psf/black/archive/18.9b0.zip'),
).toStrictEqual({
packageName: 'https://github.com/psf/black/archive/18.9b0.zip',
skipReason: 'unsupported-url',
});
});
});
describe('createSpmToolConfig()', () => {
it('should create a tooling config for github shorthand', () => {
expect(createSpmToolConfig('tuist/tuist')).toStrictEqual({
packageName: 'tuist/tuist',
datasource: 'github-releases',
});
});
it('should create a tooling config for github url', () => {
expect(
createSpmToolConfig('https://github.com/tuist/tuist.git'),
).toStrictEqual({
packageName: 'tuist/tuist',
datasource: 'github-releases',
});
});
it('provides skipReason for other url', () => {
expect(
createSpmToolConfig('https://gitlab.com/user/repo.git'),
).toStrictEqual({
packageName: 'https://gitlab.com/user/repo.git',
skipReason: 'unsupported-url',
});
});
});
describe('createUbiToolConfig()', () => {
it('should create a tooling config with empty options', () => {
expect(createUbiToolConfig('nekto/act', '0.2.70', {})).toStrictEqual({
packageName: 'nekto/act',
datasource: 'github-releases',
currentValue: '0.2.70',
extractVersion: '^v?(?<version>.+)',
});
});
it('should trim the leading v from version', () => {
expect(createUbiToolConfig('cli/cli', 'v2.64.0', {})).toStrictEqual({
packageName: 'cli/cli',
datasource: 'github-releases',
currentValue: '2.64.0',
extractVersion: '^v?(?<version>.+)',
});
});
it('should ignore options unless tag_regex is provided', () => {
expect(
createUbiToolConfig('cli/cli', '2.64.0', { exe: 'gh' } as any),
).toStrictEqual({
packageName: 'cli/cli',
datasource: 'github-releases',
currentValue: '2.64.0',
extractVersion: '^v?(?<version>.+)',
});
});
it('should set extractVersion if tag_regex is provided', () => {
expect(
createUbiToolConfig('cargo-bins/cargo-binstall', '1.10.17', {
tag_regex: '^\\d+\\.\\d+\\.',
}),
).toStrictEqual({
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
currentValue: '1.10.17',
extractVersion: '^v?(?<version>\\d+\\.\\d+\\.)',
});
});
it('should trim the leading ^v from tag_regex', () => {
expect(
createUbiToolConfig('cargo-bins/cargo-binstall', '1.10.17', {
tag_regex: '^v\\d+\\.\\d+\\.',
}),
).toStrictEqual({
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
currentValue: '1.10.17',
extractVersion: '^v?(?<version>\\d+\\.\\d+\\.)',
});
});
it('should trim the leading ^v? from tag_regex', () => {
expect(
createUbiToolConfig('cargo-bins/cargo-binstall', '1.10.17', {
tag_regex: '^v?\\d+\\.\\d+\\.',
}),
).toStrictEqual({
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
currentValue: '1.10.17',
extractVersion: '^v?(?<version>\\d+\\.\\d+\\.)',
});
});
});
});

View file

@ -0,0 +1,223 @@
import is from '@sindresorhus/is';
import { regEx } from '../../../util/regex';
import { CrateDatasource } from '../../datasource/crate';
import { GitRefsDatasource } from '../../datasource/git-refs';
import { GitTagsDatasource } from '../../datasource/git-tags';
import { GithubReleasesDatasource } from '../../datasource/github-releases';
import { GithubTagsDatasource } from '../../datasource/github-tags';
import { GoDatasource } from '../../datasource/go';
import { NpmDatasource } from '../../datasource/npm';
import { NugetDatasource } from '../../datasource/nuget';
import { PypiDatasource } from '../../datasource/pypi';
import { normalizePythonDepName } from '../../datasource/pypi/common';
import { RubygemsDatasource } from '../../datasource/rubygems';
import type { PackageDependency } from '../types';
import type { MiseToolOptionsSchema } from './schema';
export type BackendToolingConfig = Omit<PackageDependency, 'depName'> &
Required<
| Pick<PackageDependency, 'packageName' | 'datasource'>
| Pick<PackageDependency, 'packageName' | 'skipReason'>
>;
/**
* Create a tooling config for aqua backend
* @link https://mise.jdx.dev/dev-tools/backends/aqua.html
*/
export function createAquaToolConfig(
name: string,
version: string,
): BackendToolingConfig {
// mise supports http aqua package type but we cannot determine it from the tool name
// An error will be thrown afterwards if the package type is http
// ref: https://github.com/jdx/mise/blob/d1b9749d8f3e13ef705c1ea471d96c5935b79136/src/aqua/aqua_registry.rs#L39-L45
return {
packageName: name,
datasource: GithubTagsDatasource.id,
// Trim the leading 'v' from both the current and extracted version
currentValue: version.replace(/^v/, ''),
extractVersion: '^v?(?<version>.+)',
};
}
const cargoGitVersionRegex = regEx(/^(?<type>tag|branch|rev):(?<version>.+)$/);
/**
* Create a tooling config for cargo backend
* @link https://mise.jdx.dev/dev-tools/backends/cargo.html
*/
export function createCargoToolConfig(
name: string,
version: string,
): BackendToolingConfig {
// Avoid narrowing the type of name to never
if (!(is.urlString as (value: unknown) => boolean)(name)) {
return {
packageName: name,
datasource: CrateDatasource.id,
};
}
// tag: branch: or rev: is required for git repository url
// e.g. branch:main, tag:0.1.0, rev:abcdef
const matchGroups = cargoGitVersionRegex.exec(version)?.groups;
if (is.undefined(matchGroups)) {
return {
packageName: name,
skipReason: 'invalid-version',
};
}
const { type, version: gitVersion } = matchGroups;
switch (type as 'tag' | 'branch' | 'rev') {
case 'tag':
return {
packageName: name,
datasource: GitTagsDatasource.id,
currentValue: gitVersion,
};
case 'branch':
return {
packageName: name,
skipReason: 'unsupported-version',
};
case 'rev':
return {
packageName: name,
datasource: GitRefsDatasource.id,
currentValue: gitVersion,
};
}
}
/**
* Create a tooling config for dotnet backend
* @link https://mise.jdx.dev/dev-tools/backends/dotnet.html
*/
export function createDotnetToolConfig(name: string): BackendToolingConfig {
return {
packageName: name,
datasource: NugetDatasource.id,
};
}
/**
* Create a tooling config for gem backend
* @link https://mise.jdx.dev/dev-tools/backends/gem.html
*/
export function createGemToolConfig(name: string): BackendToolingConfig {
return {
packageName: name,
datasource: RubygemsDatasource.id,
};
}
/**
* Create a tooling config for go backend
* @link https://mise.jdx.dev/dev-tools/backends/go.html
*/
export function createGoToolConfig(name: string): BackendToolingConfig {
return {
packageName: name,
datasource: GoDatasource.id,
};
}
/**
* Create a tooling config for npm backend
* @link https://mise.jdx.dev/dev-tools/backends/npm.html
*/
export function createNpmToolConfig(name: string): BackendToolingConfig {
return {
packageName: name,
datasource: NpmDatasource.id,
};
}
const pipxGitHubRegex = regEx(/^git\+https:\/\/github\.com\/(?<repo>.+)\.git$/);
/**
* Create a tooling config for pipx backend
* @link https://mise.jdx.dev/dev-tools/backends/pipx.html
*/
export function createPipxToolConfig(name: string): BackendToolingConfig {
const isGitSyntax = name.startsWith('git+');
// Does not support zip file url
// Avoid type narrowing to prevent type error
if (!isGitSyntax && (is.urlString as (value: unknown) => boolean)(name)) {
return {
packageName: name,
skipReason: 'unsupported-url',
};
}
if (isGitSyntax || name.includes('/')) {
let repoName: string | undefined;
if (isGitSyntax) {
repoName = pipxGitHubRegex.exec(name)?.groups?.repo;
// If the url is not a github repo, treat the version as a git ref
if (is.undefined(repoName)) {
return {
packageName: name.replace(/^git\+/g, '').replaceAll(/\.git$/g, ''),
datasource: GitRefsDatasource.id,
};
}
} else {
repoName = name;
}
return {
packageName: repoName,
datasource: GithubTagsDatasource.id,
};
}
return {
packageName: normalizePythonDepName(name),
datasource: PypiDatasource.id,
};
}
const spmGitHubRegex = regEx(/^https:\/\/github.com\/(?<repo>.+).git$/);
/**
* Create a tooling config for spm backend
* @link https://mise.jdx.dev/dev-tools/backends/spm.html
*/
export function createSpmToolConfig(name: string): BackendToolingConfig {
let repoName: string | undefined;
// Avoid type narrowing to prevent type error
if ((is.urlString as (value: unknown) => boolean)(name)) {
repoName = spmGitHubRegex.exec(name)?.groups?.repo;
// spm backend only supports github repos
if (!repoName) {
return {
packageName: name,
skipReason: 'unsupported-url',
};
}
}
return {
packageName: repoName ?? name,
datasource: GithubReleasesDatasource.id,
};
}
/**
* Create a tooling config for ubi backend
* @link https://mise.jdx.dev/dev-tools/backends/ubi.html
*/
export function createUbiToolConfig(
name: string,
version: string,
toolOptions: MiseToolOptionsSchema,
): BackendToolingConfig {
return {
packageName: name,
datasource: GithubReleasesDatasource.id,
// Trim the leading 'v' from both the current and extracted version
currentValue: version.replace(/^v/, ''),
// Filter versions by tag_regex if it is specified
// ref: https://mise.jdx.dev/dev-tools/backends/ubi.html#ubi-uses-weird-versions
extractVersion: `^v?(?<version>${
is.string(toolOptions.tag_regex)
? toolOptions.tag_regex.replace(/^\^?v?\??/, '')
: '.+'
})`,
};
}

View file

@ -4,7 +4,7 @@ import { extractPackageFile } from '.';
jest.mock('../../../util/fs');
const miseFilename = '.mise.toml';
const miseFilename = 'mise.toml';
const mise1toml = Fixtures.get('Mise.1.toml');
@ -103,10 +103,303 @@ describe('modules/manager/mise/extract', () => {
});
});
it('extracts tools in the default registry with backends', () => {
const content = codeBlock`
[tools]
"core:node" = "16"
"asdf:rust" = "1.82.0"
"vfox:scala" = "3.5.2"
"aqua:act" = "0.2.70"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'core:node',
currentValue: '16',
packageName: 'nodejs',
datasource: 'node-version',
},
{
depName: 'asdf:rust',
currentValue: '1.82.0',
packageName: 'rust-lang/rust',
datasource: 'github-tags',
},
{
depName: 'vfox:scala',
currentValue: '3.5.2',
packageName: 'lampepfl/dotty',
datasource: 'github-tags',
},
{
depName: 'aqua:act',
currentValue: '0.2.70',
packageName: 'nektos/act',
datasource: 'github-releases',
},
],
});
});
it('extracts aqua backend tool', () => {
const content = codeBlock`
[tools]
"aqua:BurntSushi/ripgrep" = "14.1.0"
"aqua:cli/cli" = "v2.64.0"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'aqua:BurntSushi/ripgrep',
currentValue: '14.1.0',
packageName: 'BurntSushi/ripgrep',
datasource: 'github-tags',
extractVersion: '^v?(?<version>.+)',
},
{
depName: 'aqua:cli/cli',
currentValue: '2.64.0',
packageName: 'cli/cli',
datasource: 'github-tags',
extractVersion: '^v?(?<version>.+)',
},
],
});
});
it('extracts cargo backend tools', () => {
const content = codeBlock`
[tools]
"cargo:eza" = "0.18.21"
"cargo:https://github.com/username/demo1" = "tag:v0.1.0"
"cargo:https://github.com/username/demo2" = "branch:main"
"cargo:https://github.com/username/demo3" = "rev:abcdef"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'cargo:eza',
currentValue: '0.18.21',
packageName: 'eza',
datasource: 'crate',
},
{
depName: 'cargo:https://github.com/username/demo1',
currentValue: 'v0.1.0',
packageName: 'https://github.com/username/demo1',
datasource: 'git-tags',
},
{
depName: 'cargo:https://github.com/username/demo2',
packageName: 'https://github.com/username/demo2',
skipReason: 'unsupported-version',
},
{
depName: 'cargo:https://github.com/username/demo3',
currentValue: 'abcdef',
packageName: 'https://github.com/username/demo3',
datasource: 'git-refs',
},
],
});
});
it('extracts dotnet backend tool', () => {
const content = codeBlock`
[tools]
"dotnet:GitVersion.Tool" = "5.12.0"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'dotnet:GitVersion.Tool',
currentValue: '5.12.0',
packageName: 'GitVersion.Tool',
datasource: 'nuget',
},
],
});
});
it('extracts gem backend tool', () => {
const content = codeBlock`
[tools]
"gem:rubocop" = "1.69.2"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'gem:rubocop',
currentValue: '1.69.2',
packageName: 'rubocop',
datasource: 'rubygems',
},
],
});
});
it('extracts go backend tool', () => {
const content = codeBlock`
[tools]
"go:github.com/DarthSim/hivemind" = "1.0.6"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'go:github.com/DarthSim/hivemind',
currentValue: '1.0.6',
packageName: 'github.com/DarthSim/hivemind',
datasource: 'go',
},
],
});
});
it('extracts npm backend tool', () => {
const content = codeBlock`
[tools]
"npm:prettier" = "3.3.2"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'npm:prettier',
currentValue: '3.3.2',
packageName: 'prettier',
datasource: 'npm',
},
],
});
});
it('extracts pipx backend tools', () => {
const content = codeBlock`
[tools]
"pipx:yamllint" = "1.35.0"
"pipx:psf/black" = "24.4.1"
"pipx:git+https://github.com/psf/black.git" = "24.4.1"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'pipx:yamllint',
currentValue: '1.35.0',
packageName: 'yamllint',
datasource: 'pypi',
},
{
depName: 'pipx:psf/black',
currentValue: '24.4.1',
packageName: 'psf/black',
datasource: 'github-tags',
},
{
depName: 'pipx:git+https://github.com/psf/black.git',
currentValue: '24.4.1',
packageName: 'psf/black',
datasource: 'github-tags',
},
],
});
});
it('extracts spm backend tools', () => {
const content = codeBlock`
[tools]
"spm:tuist/tuist" = "4.15.0"
"spm:https://github.com/tuist/tuist.git" = "4.13.0"
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'spm:tuist/tuist',
currentValue: '4.15.0',
packageName: 'tuist/tuist',
datasource: 'github-releases',
},
{
depName: 'spm:https://github.com/tuist/tuist.git',
currentValue: '4.13.0',
packageName: 'tuist/tuist',
datasource: 'github-releases',
},
],
});
});
it('extracts ubi backend tools', () => {
const content = codeBlock`
[tools]
"ubi:nekto/act" = "v0.2.70"
"ubi:cli/cli" = { exe = "gh", version = "1.14.0" }
"ubi:cli/cli[exe=gh]" = "1.14.0"
"ubi:cargo-bins/cargo-binstall" = { tag_regex = "^\\\\d+\\\\.\\\\d+\\\\.", version = "1.0.0" }
"ubi:cargo-bins/cargo-binstall[tag_regex=^\\\\d+\\\\.]" = "1.0.0"
'ubi:cargo-bins/cargo-binstall[tag_regex=^\\d+\\.\\d+\\.]' = { tag_regex = '^\\d+\\.', version = "1.0.0" }
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
deps: [
{
depName: 'ubi:nekto/act',
currentValue: '0.2.70',
packageName: 'nekto/act',
datasource: 'github-releases',
extractVersion: '^v?(?<version>.+)',
},
{
depName: 'ubi:cli/cli',
currentValue: '1.14.0',
packageName: 'cli/cli',
datasource: 'github-releases',
extractVersion: '^v?(?<version>.+)',
},
{
depName: 'ubi:cli/cli',
currentValue: '1.14.0',
packageName: 'cli/cli',
datasource: 'github-releases',
extractVersion: '^v?(?<version>.+)',
},
{
depName: 'ubi:cargo-bins/cargo-binstall',
currentValue: '1.0.0',
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
extractVersion: '^v?(?<version>\\d+\\.\\d+\\.)',
},
{
depName: 'ubi:cargo-bins/cargo-binstall',
currentValue: '1.0.0',
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
extractVersion: '^v?(?<version>\\d+\\.)',
},
{
depName: 'ubi:cargo-bins/cargo-binstall',
currentValue: '1.0.0',
packageName: 'cargo-bins/cargo-binstall',
datasource: 'github-releases',
extractVersion: '^v?(?<version>\\d+\\.)',
},
],
});
});
it('provides skipReason for lines with unsupported tooling', () => {
const content = codeBlock`
[tools]
fake-tool = '1.0.0'
'fake:tool' = '1.0.0'
`;
const result = extractPackageFile(content, miseFilename);
expect(result).toMatchObject({
@ -115,6 +408,10 @@ describe('modules/manager/mise/extract', () => {
depName: 'fake-tool',
skipReason: 'unsupported-datasource',
},
{
depName: 'fake:tool',
skipReason: 'unsupported-datasource',
},
],
});
});
@ -172,7 +469,7 @@ describe('modules/manager/mise/extract', () => {
});
});
it('complete .mise.toml example', () => {
it('complete mise.toml example', () => {
const result = extractPackageFile(mise1toml, miseFilename);
expect(result).toMatchObject({
deps: [

View file

@ -1,12 +1,29 @@
import is from '@sindresorhus/is';
import { logger } from '../../../logger';
import { regEx } from '../../../util/regex';
import type { ToolingConfig } from '../asdf/upgradeable-tooling';
import type { PackageDependency, PackageFileContent } from '../types';
import type { MiseToolSchema } from './schema';
import type { BackendToolingConfig } from './backends';
import {
createAquaToolConfig,
createCargoToolConfig,
createDotnetToolConfig,
createGemToolConfig,
createGoToolConfig,
createNpmToolConfig,
createPipxToolConfig,
createSpmToolConfig,
createUbiToolConfig,
} from './backends';
import type { MiseToolOptionsSchema, MiseToolSchema } from './schema';
import type { ToolingDefinition } from './upgradeable-tooling';
import { asdfTooling, miseTooling } from './upgradeable-tooling';
import { parseTomlFile } from './utils';
// Tool names can have options in the tool name
// e.g. ubi:tamasfe/taplo[matching=full,exe=taplo]
const optionInToolNameRegex = regEx(/^(?<name>.+?)(?:\[(?<options>.+)\])?$/);
export function extractPackageFile(
content: string,
packageFile: string,
@ -24,8 +41,21 @@ export function extractPackageFile(
if (tools) {
for (const [name, toolData] of Object.entries(tools)) {
const version = parseVersion(toolData);
const depName = name.trim();
const toolConfig = getToolConfig(depName, version);
// Parse the tool options in the tool name
const { name: depName, options: optionsInName } =
optionInToolNameRegex.exec(name.trim())?.groups ?? {
name: name.trim(),
};
const delimiterIndex = name.indexOf(':');
const backend = depName.substring(0, delimiterIndex);
const toolName = depName.substring(delimiterIndex + 1);
const options = parseOptions(
optionsInName,
is.nonEmptyObject(toolData) ? toolData : {},
);
const toolConfig = is.null_(version)
? null
: getToolConfig(backend, toolName, version, options);
const dep = createDependency(depName, version, toolConfig);
deps.push(dep);
}
@ -53,18 +83,79 @@ function parseVersion(toolData: MiseToolSchema): string | null {
return null; // Return null if no version is found
}
function getToolConfig(
name: string,
version: string | null,
): ToolingConfig | null {
if (version === null) {
return null; // Early return if version is null
}
function parseOptions(
optionsInName: string,
toolOptions: MiseToolOptionsSchema,
): MiseToolOptionsSchema {
const options = is.nonEmptyString(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: MiseToolOptionsSchema,
): ToolingConfig | 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
return getRegistryToolConfig(toolName, version);
// 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 '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,
): ToolingConfig | null {
// Try to get the config from miseTooling first, then asdfTooling
return (
getConfigFromTooling(miseTooling, name, version) ??
getConfigFromTooling(asdfTooling, name, version)
getConfigFromTooling(miseTooling, short, version) ??
getConfigFromTooling(asdfTooling, short, version)
);
}
@ -79,26 +170,34 @@ function getConfigFromTooling(
} // Return null if no toolDefinition is found
return (
(typeof toolDefinition.config === 'function'
(is.function_(toolDefinition.config)
? toolDefinition.config(version)
: toolDefinition.config) ?? null
); // Ensure null is returned instead of undefined
);
}
function createDependency(
name: string,
version: string | null,
config: ToolingConfig | null,
config: ToolingConfig | BackendToolingConfig | null,
): PackageDependency {
if (version === null) {
return { depName: name, skipReason: 'unspecified-version' };
if (is.null_(version)) {
return {
depName: name,
skipReason: 'unspecified-version',
};
}
if (config === null) {
return { depName: name, skipReason: 'unsupported-datasource' };
if (is.null_(config)) {
return {
depName: name,
skipReason: 'unsupported-datasource',
};
}
return {
depName: name,
currentValue: version,
// Spread the config last to override other properties
...config,
};
}

View file

@ -1,3 +1,17 @@
import { deduplicateArray } from '../../../util/array';
import { CrateDatasource } from '../../datasource/crate';
import { GitRefsDatasource } from '../../datasource/git-refs';
import { GitTagsDatasource } from '../../datasource/git-tags';
import { GithubReleasesDatasource } from '../../datasource/github-releases';
import { GithubTagsDatasource } from '../../datasource/github-tags';
import { GoDatasource } from '../../datasource/go';
import { JavaVersionDatasource } from '../../datasource/java-version';
import { NodeVersionDatasource } from '../../datasource/node-version';
import { NpmDatasource } from '../../datasource/npm';
import { NugetDatasource } from '../../datasource/nuget';
import { PypiDatasource } from '../../datasource/pypi';
import { RubyVersionDatasource } from '../../datasource/ruby-version';
import { RubygemsDatasource } from '../../datasource/rubygems';
import { supportedDatasources as asdfSupportedDatasources } from '../asdf';
export { extractPackageFile } from './extract';
@ -9,5 +23,29 @@ export const defaultConfig = {
fileMatch: ['(^|/)\\.?mise\\.toml$', '(^|/)\\.?mise/config\\.toml$'],
};
// Re-use the asdf datasources, as mise and asdf support the same plugins.
export const supportedDatasources = asdfSupportedDatasources;
const backendDatasources = {
core: [
GithubReleasesDatasource.id,
GithubTagsDatasource.id,
JavaVersionDatasource.id,
NodeVersionDatasource.id,
RubyVersionDatasource.id,
],
// Re-use the asdf datasources, as mise and asdf support the same plugins.
asdf: asdfSupportedDatasources,
aqua: [GithubTagsDatasource.id],
cargo: [CrateDatasource.id, GitTagsDatasource.id, GitRefsDatasource.id],
dotnet: [NugetDatasource.id],
gem: [RubygemsDatasource.id],
go: [GoDatasource.id],
npm: [NpmDatasource.id],
pipx: [PypiDatasource.id, GithubTagsDatasource.id, GitRefsDatasource.id],
spm: [GithubReleasesDatasource.id],
ubi: [GithubReleasesDatasource.id],
// not supported
vfox: [],
};
export const supportedDatasources = deduplicateArray(
Object.values(backendDatasources).flat(),
);

View file

@ -1,9 +1,4 @@
Renovate can update the [mise](https://mise.jdx.dev/configuration.html#mise-toml) `.mise.toml` file.
Renovate's `mise` manager can version these tools:
<!-- Autogenerate in https://github.com/renovatebot/renovate -->
<!-- Autogenerate end -->
Renovate can update the [mise](https://mise.jdx.dev/configuration.html#mise-toml) `mise.toml` file.
### Renovate only updates primary versions
@ -30,16 +25,11 @@ To maintain consistency and reliability, Renovate opts to only manage the _first
This follows the same workflow that Renovate's `asdf` manager uses.
### Plugin/tool support
### Short names support
Renovate uses:
Renovate uses [mise registry](https://mise.jdx.dev/registry.html) to understand tools short names.
- [mise's plugins](https://github.com/jdx/mise/tree/main/src/plugins/core)
- [asdf's plugins](https://mise.jdx.dev/registry.html)
to understand and manage tool versioning.
Support for new tools/plugins needs to be _manually_ added to Renovate's logic.
Support for new tool short names needs to be _manually_ added to Renovate's logic.
#### Adding new tool support
@ -48,8 +38,67 @@ There are 2 ways to integrate versioning for a new tool:
- Renovate's `mise` manager: ensure upstream `mise` supports the tool, then add support to the `mise` manager in Renovate
- Renovate's `asdf` manager: improve the `asdf` manager in Renovate, which automatically extends support to `mise`
If `mise` adds support for more tools via its own [core plugins](https://mise.jdx.dev/plugins.html#core-plugins), you can create a PR to extend Renovate's `mise` manager to add support for the new tooling.
If `mise` adds support for more tools via its own [core tools](https://mise.jdx.dev/core-tools.html), you can create a PR to extend Renovate's `mise` manager to add support for the new core tools.
You may be able to add support for new tooling upstream in the core plugins - create an issue and see if the community agrees whether it belongs there, or if it would be better as an `asdf-` plugin.
If you are wanting to add support for an other tools' short names to `mise`, you can create a PR to extend Renovate's `asdf` manager, which indirectly helps Renovate's `mise` manager as well.
If you are wanting to add support for an existing `asdf-x` plugin to `mise`, you can create a PR to extend Renovate's `asdf` manager, which indirectly helps Renovate's `mise` manager as well.
Note that some tools in the registry are not using the `asdf` backend. We are currently not supporting those tool short names.
TODO: Change the registry lookup.
### Backends support
Renovate's `mise` manager supports the following [backends](https://mise.jdx.dev/dev-tools/backends/):
- [`core`](https://mise.jdx.dev/core-tools.html)
- [`asdf`](https://mise.jdx.dev/dev-tools/backends/asdf.html)
- [`aqua`](https://mise.jdx.dev/dev-tools/backends/aqua.html)
- [`cargo`](https://mise.jdx.dev/dev-tools/backends/cargo.html)
- [`go`](https://mise.jdx.dev/dev-tools/backends/go.html)
- [`npm`](https://mise.jdx.dev/dev-tools/backends/npm.html)
- [`pipx`](https://mise.jdx.dev/dev-tools/backends/pipx.html)
- [`spm`](https://mise.jdx.dev/dev-tools/backends/spm.html)
- [`ubi`](https://mise.jdx.dev/dev-tools/backends/ubi.html)
- [`vfox`](https://mise.jdx.dev/dev-tools/backends/vfox.html)
#### Limitations
Renovate's `mise` manager does not support the following tool syntax:
- `asdf` and `vfox` plugins
e.g. `asdf:asdf:mise-plugins/asdf-yarn` or `vfox:vfox:version-fox/vfox-elixir`
Short names with backends like `asdf:yarn` or `vfox:elixir` are supported if the short names are supported.
- `aqua` packages with `http` [package type](https://aquaproj.github.io/docs/reference/registry-config/#package-types).
However if the short name using `aqua` backend is supported by Renovate, it will be updated.
e.g. [`aqua:helm/helm`](https://github.com/aquaproj/aqua-registry/blob/main/pkgs/helm/helm/registry.yaml) is not supported, but `helm` or `aqua:helm` is supported.
- `aqua` packages with [`version_filter`](https://aquaproj.github.io/docs/reference/registry-config/version-prefix).
We don't read the aqua registry itself, so we can't support this feature.
If some packages using `version_filter` like [`aqua:biomejs/biome`](https://github.com/aquaproj/aqua-registry/blob/main/pkgs/biomejs/biome/registry.yaml) are not updated or updated incorrectly, set `extractVersion` in the Renovate config manually like below.
```json
{
"packageRules": [
{
"depNames": ["aqua:biomejs/biome"],
"extractVersion": "cli/(?<version>.+)"
}
]
}
```
- `ubi` backend tools with [`tag_regex`](https://mise.jdx.dev/dev-tools/backends/ubi.html#ubi-uses-weird-versions) option.
The `tag_regex` option is used as `extractVersion`, but the regex engines are not the same between mise and Renovate.
If the version is not updated or updated incorrectly, override `extractVersion` manually in the renovate config.
- Versions with `v` prefix.
mise automatically strips the `v` prefix from versions, but Renovate does not.
If the version is not updated or updated incorrectly, set `extractVersion` to `v(?<version>.+)` in the Renovate config.
### Supported default registry tool short names
Renovate's `mise` manager can only version these tool short names:
<!-- Autogenerate in https://github.com/renovatebot/renovate -->
<!-- Autogenerate end -->

View file

@ -1,9 +1,17 @@
import { z } from 'zod';
import { Toml } from '../../../util/schema-utils';
const MiseToolOptionsSchema = z.object({
// ubi backend only
tag_regex: z.string().optional(),
});
export type MiseToolOptionsSchema = z.infer<typeof MiseToolOptionsSchema>;
const MiseToolSchema = z.union([
z.string(),
z.object({ version: z.string().optional() }),
MiseToolOptionsSchema.extend({
version: z.string().optional(),
}),
z.array(z.string()),
]);
export type MiseToolSchema = z.infer<typeof MiseToolSchema>;