From 4a304b8e6db9bfc32049b86717c549eaeb8bc8b9 Mon Sep 17 00:00:00 2001
From: James Brookes <jbrookes@confluent.io>
Date: Mon, 8 Jul 2024 10:27:31 -0400
Subject: [PATCH] feat(manager): add mise package manager (#29950)

---
 docs/usage/node.md                            |   1 +
 lib/modules/manager/api.ts                    |   2 +
 .../manager/mise/__fixtures__/Mise.1.toml     |  19 +
 lib/modules/manager/mise/extract.spec.ts      | 354 ++++++++++++++++++
 lib/modules/manager/mise/extract.ts           | 107 ++++++
 lib/modules/manager/mise/index.ts             |  12 +
 lib/modules/manager/mise/readme.md            |  55 +++
 lib/modules/manager/mise/schema.ts            |  16 +
 .../manager/mise/upgradeable-tooling.ts       | 148 ++++++++
 lib/modules/manager/mise/utils.spec.ts        |  41 ++
 lib/modules/manager/mise/utils.ts             |  15 +
 tools/docs/index.ts                           |   5 +
 tools/docs/manager-mise-supported-plugins.ts  |  29 ++
 13 files changed, 804 insertions(+)
 create mode 100644 lib/modules/manager/mise/__fixtures__/Mise.1.toml
 create mode 100644 lib/modules/manager/mise/extract.spec.ts
 create mode 100644 lib/modules/manager/mise/extract.ts
 create mode 100644 lib/modules/manager/mise/index.ts
 create mode 100644 lib/modules/manager/mise/readme.md
 create mode 100644 lib/modules/manager/mise/schema.ts
 create mode 100644 lib/modules/manager/mise/upgradeable-tooling.ts
 create mode 100644 lib/modules/manager/mise/utils.spec.ts
 create mode 100644 lib/modules/manager/mise/utils.ts
 create mode 100644 tools/docs/manager-mise-supported-plugins.ts

diff --git a/docs/usage/node.md b/docs/usage/node.md
index 5543401526..4fcca5e694 100644
--- a/docs/usage/node.md
+++ b/docs/usage/node.md
@@ -21,6 +21,7 @@ Renovate can manage the Node.js version in the following files:
 - The [`.nvmrc`](https://github.com/creationix/nvm#nvmrc) file for the [Node Version Manager](https://github.com/creationix/nvm)
 - The [`.node-version`](https://github.com/nodenv/nodenv#choosing-the-node-version) file for the [nodenv](https://github.com/nodenv/nodenv) environment manager
 - The [`.tool-versions`](https://asdf-vm.com/manage/configuration.html#tool-versions) file for the [asdf](https://github.com/asdf-vm/asdf) version manager
+- The [`.mise.toml`](https://mise.jdx.dev/configuration.html#mise-toml) file for the [mise](https://github.com/jdx/mise) version manager
 - The [`node_js`](https://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Specifying-Node.js-versions) field in [`.travis.yml`](https://docs.travis-ci.com/user/customizing-the-build/)
 
 ## Configuring which version of npm Renovate uses
diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 9616fd86cb..726479f4d0 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -56,6 +56,7 @@ import * as maven from './maven';
 import * as mavenWrapper from './maven-wrapper';
 import * as meteor from './meteor';
 import * as mint from './mint';
+import * as mise from './mise';
 import * as mix from './mix';
 import * as nix from './nix';
 import * as nodenv from './nodenv';
@@ -153,6 +154,7 @@ api.set('maven', maven);
 api.set('maven-wrapper', mavenWrapper);
 api.set('meteor', meteor);
 api.set('mint', mint);
+api.set('mise', mise);
 api.set('mix', mix);
 api.set('nix', nix);
 api.set('nodenv', nodenv);
diff --git a/lib/modules/manager/mise/__fixtures__/Mise.1.toml b/lib/modules/manager/mise/__fixtures__/Mise.1.toml
new file mode 100644
index 0000000000..667f2d6a9e
--- /dev/null
+++ b/lib/modules/manager/mise/__fixtures__/Mise.1.toml
@@ -0,0 +1,19 @@
+[env]
+# supports arbitrary env vars so mise can be used like direnv/dotenv
+NODE_ENV = 'production'
+
+[tools]
+# specify single or multiple versions
+java = '21.0.2'
+erlang = ['23.3', '24.0']
+
+# supports everything you can do with .tool-versions currently
+node = ['16', 'prefix:20', 'ref:master', 'path:~/.nodes/14']
+
+[plugins]
+# specify a custom repo URL
+# note this will only be used if the plugin does not already exist
+python = 'https://github.com/asdf-community/asdf-python'
+
+[alias.node] # project-local aliases
+my_custom_node = '20'
diff --git a/lib/modules/manager/mise/extract.spec.ts b/lib/modules/manager/mise/extract.spec.ts
new file mode 100644
index 0000000000..67e2edea53
--- /dev/null
+++ b/lib/modules/manager/mise/extract.spec.ts
@@ -0,0 +1,354 @@
+import { codeBlock } from 'common-tags';
+import { Fixtures } from '../../../../test/fixtures';
+import { extractPackageFile } from '.';
+
+jest.mock('../../../util/fs');
+
+const miseFilename = '.mise.toml';
+
+const mise1toml = Fixtures.get('Mise.1.toml');
+
+describe('modules/manager/mise/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('returns null for empty', () => {
+      expect(extractPackageFile('', miseFilename)).toBeNull();
+    });
+
+    it('returns null for invalid TOML', () => {
+      expect(extractPackageFile('foo', miseFilename)).toBeNull();
+    });
+
+    it('returns null for empty tools section', () => {
+      const content = codeBlock`
+      [tools]
+    `;
+      expect(extractPackageFile(content, miseFilename)).toBeNull();
+    });
+
+    it('extracts tools - mise core plugins', () => {
+      const content = codeBlock`
+      [tools]
+      erlang = '23.3'
+      node = '16'
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'erlang',
+            currentValue: '23.3',
+            datasource: 'github-tags',
+          },
+          {
+            depName: 'node',
+            currentValue: '16',
+            datasource: 'node-version',
+          },
+        ],
+      });
+    });
+
+    it('extracts tools - asdf plugins', () => {
+      const content = codeBlock`
+      [tools]
+      terraform = '1.8.0'
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'terraform',
+            currentValue: '1.8.0',
+          },
+        ],
+      });
+    });
+
+    it('extracts tools with multiple versions', () => {
+      const content = codeBlock`
+      [tools]
+      erlang = ['23.3', '24.0']
+      node = ['16', 'prefix:20', 'ref:master', 'path:~/.nodes/14']
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'erlang',
+            currentValue: '23.3',
+            datasource: 'github-tags',
+          },
+          {
+            depName: 'node',
+            currentValue: '16',
+            datasource: 'node-version',
+          },
+        ],
+      });
+    });
+
+    it('extracts tools with plugin options', () => {
+      const content = codeBlock`
+      [tools]
+      python = {version='3.11', virtualenv='.venv'}
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'python',
+            currentValue: '3.11',
+          },
+        ],
+      });
+    });
+
+    it('provides skipReason for lines with unsupported tooling', () => {
+      const content = codeBlock`
+      [tools]
+      fake-tool = '1.0.0'
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'fake-tool',
+            skipReason: 'unsupported-datasource',
+          },
+        ],
+      });
+    });
+
+    it('provides skipReason for missing version - empty string', () => {
+      const content = codeBlock`
+      [tools]
+      python = ''
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'python',
+            skipReason: 'unspecified-version',
+          },
+        ],
+      });
+    });
+
+    it('provides skipReason for missing version - missing version in object', () => {
+      const content = codeBlock`
+      [tools]
+      python = {virtualenv='.venv'}
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'python',
+            skipReason: 'unspecified-version',
+          },
+        ],
+      });
+    });
+
+    it('provides skipReason for missing version - empty array', () => {
+      const content = codeBlock`
+      [tools]
+      java = '21.0.2'
+      erlang = []
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+          },
+          {
+            depName: 'erlang',
+            skipReason: 'unspecified-version',
+          },
+        ],
+      });
+    });
+
+    it('complete .mise.toml example', () => {
+      const result = extractPackageFile(mise1toml, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+          {
+            depName: 'erlang',
+            currentValue: '23.3',
+            datasource: 'github-tags',
+          },
+          {
+            depName: 'node',
+            currentValue: '16',
+            datasource: 'node-version',
+          },
+        ],
+      });
+    });
+
+    it('complete example with skip', () => {
+      const content = codeBlock`
+      [tools]
+      java = '21.0.2'
+      erlang = ['23.3', '24.0']
+      terraform = {version='1.8.0'}
+      fake-tool = '1.6.2'
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+          {
+            depName: 'erlang',
+            currentValue: '23.3',
+            datasource: 'github-tags',
+          },
+          {
+            depName: 'terraform',
+            currentValue: '1.8.0',
+          },
+          {
+            depName: 'fake-tool',
+            skipReason: 'unsupported-datasource',
+          },
+        ],
+      });
+    });
+
+    it('core java plugin function', () => {
+      const content = codeBlock`
+      [tools]
+      java = "21.0.2"
+    `;
+      const result = extractPackageFile(content, miseFilename);
+      expect(result).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content2 = codeBlock`
+      [tools]
+      java = "openjdk-21.0.2"
+    `;
+      const result2 = extractPackageFile(content2, miseFilename);
+      expect(result2).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content3 = codeBlock`
+      [tools]
+      java = "temurin-21.0.2"
+    `;
+      const result3 = extractPackageFile(content3, miseFilename);
+      expect(result3).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content4 = codeBlock`
+      [tools]
+      java = "zulu-21.0.2"
+    `;
+      const result4 = extractPackageFile(content4, miseFilename);
+      expect(result4).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content5 = codeBlock`
+      [tools]
+      java = "corretto-21.0.2"
+    `;
+      const result5 = extractPackageFile(content5, miseFilename);
+      expect(result5).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content6 = codeBlock`
+      [tools]
+      java = "oracle-graalvm-21.0.2"
+    `;
+      const result6 = extractPackageFile(content6, miseFilename);
+      expect(result6).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      const content7 = codeBlock`
+      [tools]
+      java = "adoptopenjdk-21.0.2"
+    `;
+      const result7 = extractPackageFile(content7, miseFilename);
+      expect(result7).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '21.0.2',
+            datasource: 'java-version',
+          },
+        ],
+      });
+
+      // Test that fallback to asdf Plugin works
+      const content8 = codeBlock`
+      [tools]
+      java = "adoptopenjdk-jre-16.0.0+36"
+    `;
+      const result8 = extractPackageFile(content8, miseFilename);
+      expect(result8).toMatchObject({
+        deps: [
+          {
+            depName: 'java',
+            currentValue: '16.0.0+36',
+            datasource: 'java-version',
+          },
+        ],
+      });
+    });
+  });
+});
diff --git a/lib/modules/manager/mise/extract.ts b/lib/modules/manager/mise/extract.ts
new file mode 100644
index 0000000000..3828795d84
--- /dev/null
+++ b/lib/modules/manager/mise/extract.ts
@@ -0,0 +1,107 @@
+import is from '@sindresorhus/is';
+import { logger } from '../../../logger';
+import type { ToolingConfig } from '../asdf/upgradeable-tooling';
+import type { PackageDependency, PackageFileContent } from '../types';
+import type { MiseToolSchema } from './schema';
+import {
+  ToolingDefinition,
+  asdfTooling,
+  miseTooling,
+} from './upgradeable-tooling';
+import { parseTomlFile } from './utils';
+
+export function extractPackageFile(
+  content: string,
+  packageFile: string,
+): PackageFileContent | null {
+  logger.trace(`mise.extractPackageFile(${packageFile})`);
+
+  const misefile = parseTomlFile(content, packageFile);
+  if (!misefile) {
+    return null;
+  }
+
+  const deps: PackageDependency[] = [];
+  const tools = misefile.tools;
+
+  if (tools) {
+    for (const [name, toolData] of Object.entries(tools)) {
+      const version = parseVersion(toolData);
+      const depName = name.trim();
+      const toolConfig = getToolConfig(depName, version);
+      const dep = createDependency(depName, version, toolConfig);
+      deps.push(dep);
+    }
+  }
+
+  return deps.length ? { deps } : null;
+}
+
+function parseVersion(toolData: MiseToolSchema): string | null {
+  if (is.nonEmptyString(toolData)) {
+    // Handle the string case
+    // e.g. 'erlang = "23.3"'
+    return toolData;
+  }
+  if (is.array(toolData, is.string)) {
+    // 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 (is.nonEmptyString(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 getToolConfig(
+  name: string,
+  version: string | null,
+): ToolingConfig | null {
+  if (version === null) {
+    return null; // Early return if version is null
+  }
+
+  // Try to get the config from miseTooling first, then asdfTooling
+  return (
+    getConfigFromTooling(miseTooling, name, version) ??
+    getConfigFromTooling(asdfTooling, name, version)
+  );
+}
+
+function getConfigFromTooling(
+  toolingSource: Record<string, ToolingDefinition>,
+  name: string,
+  version: string,
+): ToolingConfig | null {
+  const toolDefinition = toolingSource[name];
+  if (!toolDefinition) {
+    return null;
+  } // Return null if no toolDefinition is found
+
+  return (
+    (typeof toolDefinition.config === 'function'
+      ? toolDefinition.config(version)
+      : toolDefinition.config) ?? null
+  ); // Ensure null is returned instead of undefined
+}
+
+function createDependency(
+  name: string,
+  version: string | null,
+  config: ToolingConfig | null,
+): PackageDependency {
+  if (version === null) {
+    return { depName: name, skipReason: 'unspecified-version' };
+  }
+  if (config === null) {
+    return { depName: name, skipReason: 'unsupported-datasource' };
+  }
+  return {
+    depName: name,
+    currentValue: version,
+    ...config,
+  };
+}
diff --git a/lib/modules/manager/mise/index.ts b/lib/modules/manager/mise/index.ts
new file mode 100644
index 0000000000..d90ebcad5b
--- /dev/null
+++ b/lib/modules/manager/mise/index.ts
@@ -0,0 +1,12 @@
+import { supportedDatasources as asdfSupportedDatasources } from '../asdf';
+
+export { extractPackageFile } from './extract';
+
+export const displayName = 'mise';
+
+export const defaultConfig = {
+  fileMatch: ['(^|/)\\.mise\\.toml$'],
+};
+
+// Re-use the asdf datasources, as mise and asdf support the same plugins.
+export const supportedDatasources = asdfSupportedDatasources;
diff --git a/lib/modules/manager/mise/readme.md b/lib/modules/manager/mise/readme.md
new file mode 100644
index 0000000000..11c125b854
--- /dev/null
+++ b/lib/modules/manager/mise/readme.md
@@ -0,0 +1,55 @@
+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 only updates primary versions
+
+Renovate's `mise` manager is designed to automatically update the _first_ (primary) version listed for each tool in the `mise.toml` file.
+
+Secondary or fallback versions require manual updates.
+
+#### Example
+
+Given a `.mise.toml` entry like:
+
+```toml
+[tools]
+erlang = ["23.3", "22.0"]
+```
+
+Renovate will update `"23.3"` (the primary version) but will not touch `"22.0"` (the fallback version).
+
+#### Why can Renovate only update primary versions?
+
+To maintain consistency and reliability, Renovate opts to only manage the _first_ listed version.
+
+- Fallback versions can often be older versions of a tool that are known to work and are there as a backup.
+
+This follows the same workflow that Renovate's `asdf` manager uses.
+
+### Plugin/tool support
+
+Renovate uses:
+
+- [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.
+
+#### Adding new tool support
+
+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.
+
+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 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.
diff --git a/lib/modules/manager/mise/schema.ts b/lib/modules/manager/mise/schema.ts
new file mode 100644
index 0000000000..b4614ebff9
--- /dev/null
+++ b/lib/modules/manager/mise/schema.ts
@@ -0,0 +1,16 @@
+import { z } from 'zod';
+import { Toml } from '../../../util/schema-utils';
+
+const MiseToolSchema = z.union([
+  z.string(),
+  z.object({ version: z.string().optional() }),
+  z.array(z.string()),
+]);
+export type MiseToolSchema = z.infer<typeof MiseToolSchema>;
+
+export const MiseFileSchema = z.object({
+  tools: z.record(MiseToolSchema),
+});
+export type MiseFileSchema = z.infer<typeof MiseFileSchema>;
+
+export const MiseFileSchemaToml = Toml.pipe(MiseFileSchema);
diff --git a/lib/modules/manager/mise/upgradeable-tooling.ts b/lib/modules/manager/mise/upgradeable-tooling.ts
new file mode 100644
index 0000000000..f13a4c6736
--- /dev/null
+++ b/lib/modules/manager/mise/upgradeable-tooling.ts
@@ -0,0 +1,148 @@
+import { GithubReleasesDatasource } from '../../datasource/github-releases';
+import { GithubTagsDatasource } from '../../datasource/github-tags';
+import { JavaVersionDatasource } from '../../datasource/java-version';
+import { NodeVersionDatasource } from '../../datasource/node-version';
+import { RubyVersionDatasource } from '../../datasource/ruby-version';
+import * as regexVersioning from '../../versioning/regex';
+import * as semverVersioning from '../../versioning/semver';
+import { ToolingConfig, upgradeableTooling } from '../asdf/upgradeable-tooling';
+
+export interface ToolingDefinition {
+  config: ToolingConfig;
+  misePluginUrl?: string;
+}
+
+export const asdfTooling = upgradeableTooling;
+
+export const miseTooling: Record<string, ToolingDefinition> = {
+  bun: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/bun.html',
+    config: {
+      packageName: 'oven-sh/bun',
+      datasource: GithubReleasesDatasource.id,
+      extractVersion: '^bun-v(?<version>\\S+)',
+    },
+  },
+  deno: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/deno.html',
+    config: {
+      packageName: 'denoland/deno',
+      datasource: GithubReleasesDatasource.id,
+      extractVersion: '^v(?<version>\\S+)',
+    },
+  },
+  erlang: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/erlang.html',
+    config: {
+      packageName: 'erlang/otp',
+      datasource: GithubTagsDatasource.id,
+      extractVersion: '^OTP-(?<version>\\S+)',
+      versioning: `${regexVersioning.id}:^(?<major>\\d+?)\\.(?<minor>\\d+?)(\\.(?<patch>\\d+))?$`,
+    },
+  },
+  go: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/go.html',
+    config: {
+      packageName: 'golang/go',
+      datasource: GithubTagsDatasource.id,
+      extractVersion: '^go(?<version>\\S+)',
+    },
+  },
+  java: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/java.html',
+    config: (version) => {
+      // no prefix is shorthand for openjdk
+      const versionMatch = version.match(/^(\d\S+)/)?.[1];
+      if (versionMatch) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: versionMatch,
+        };
+      }
+      const openJdkMatches = version.match(
+        /^openjdk-(?<version>\d\S+)/,
+      )?.groups;
+      if (openJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: openJdkMatches.version,
+        };
+      }
+      const adoptOpenJdkMatches = version.match(
+        /^adoptopenjdk-(?<version>\d\S+)/,
+      )?.groups;
+      if (adoptOpenJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: adoptOpenJdkMatches.version,
+        };
+      }
+      const temurinJdkMatches = version.match(
+        /^temurin-(?<version>\d\S+)/,
+      )?.groups;
+      if (temurinJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: temurinJdkMatches.version,
+        };
+      }
+      const correttoJdkMatches = version.match(
+        /^corretto-(?<version>\d\S+)/,
+      )?.groups;
+      if (correttoJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: correttoJdkMatches.version,
+        };
+      }
+      const zuluJdkMatches = version.match(/^zulu-(?<version>\d\S+)/)?.groups;
+      if (zuluJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: zuluJdkMatches.version,
+        };
+      }
+      const oracleGraalvmJdkMatches = version.match(
+        /^oracle-graalvm-(?<version>\d\S+)/,
+      )?.groups;
+      if (oracleGraalvmJdkMatches) {
+        return {
+          datasource: JavaVersionDatasource.id,
+          packageName: 'java-jdk',
+          currentValue: oracleGraalvmJdkMatches.version,
+        };
+      }
+
+      return undefined;
+    },
+  },
+  node: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/node.html',
+    config: {
+      packageName: 'nodejs',
+      datasource: NodeVersionDatasource.id,
+    },
+  },
+  python: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/python.html',
+    config: {
+      packageName: 'python/cpython',
+      datasource: GithubTagsDatasource.id,
+      extractVersion: '^v(?<version>\\S+)',
+    },
+  },
+  ruby: {
+    misePluginUrl: 'https://mise.jdx.dev/lang/ruby.html',
+    config: {
+      packageName: 'ruby-version',
+      datasource: RubyVersionDatasource.id,
+      versioning: semverVersioning.id,
+    },
+  },
+};
diff --git a/lib/modules/manager/mise/utils.spec.ts b/lib/modules/manager/mise/utils.spec.ts
new file mode 100644
index 0000000000..38c1594f7b
--- /dev/null
+++ b/lib/modules/manager/mise/utils.spec.ts
@@ -0,0 +1,41 @@
+import { codeBlock } from 'common-tags';
+import { parseTomlFile } from './utils';
+
+const miseFilename = '.mise.toml';
+
+describe('modules/manager/mise/utils', () => {
+  describe('parseTomlFile', () => {
+    it('load and parse successfully', () => {
+      const fileContent = codeBlock`
+        [tools]
+        erlang = '23.3'
+        node = '16'
+      `;
+      const actual = parseTomlFile(fileContent, miseFilename);
+      expect(actual).toMatchObject({
+        tools: {
+          erlang: '23.3',
+          node: '16',
+        },
+      });
+    });
+
+    it('invalid toml', () => {
+      const invalidToml = codeBlock`
+      clearly: "invalid" "toml"
+    `;
+      const actual = parseTomlFile(invalidToml, miseFilename);
+      expect(actual).toBeNull();
+    });
+
+    it('invalid schema', () => {
+      const content = codeBlock`
+      [invalid]
+      erlang = '23.3'
+      node = '16'
+    `;
+      const actual = parseTomlFile(content, miseFilename);
+      expect(actual).toBeNull();
+    });
+  });
+});
diff --git a/lib/modules/manager/mise/utils.ts b/lib/modules/manager/mise/utils.ts
new file mode 100644
index 0000000000..8ca8a4c63d
--- /dev/null
+++ b/lib/modules/manager/mise/utils.ts
@@ -0,0 +1,15 @@
+import { logger } from '../../../logger';
+import { MiseFileSchema, MiseFileSchemaToml } from './schema';
+
+export function parseTomlFile(
+  content: string,
+  packageFile: string,
+): MiseFileSchema | null {
+  const res = MiseFileSchemaToml.safeParse(content);
+  if (res.success) {
+    return res.data;
+  } else {
+    logger.debug({ err: res.error, packageFile }, 'Error parsing Mise file.');
+    return null;
+  }
+}
diff --git a/tools/docs/index.ts b/tools/docs/index.ts
index 737138b108..b105c7ed2a 100644
--- a/tools/docs/index.ts
+++ b/tools/docs/index.ts
@@ -7,6 +7,7 @@ import { generateDatasources } from './datasources';
 import { getOpenGitHubItems } from './github-query-items';
 import { generateManagers } from './manager';
 import { generateManagerAsdfSupportedPlugins } from './manager-asdf-supported-plugins';
+import { generateManagerMiseSupportedPlugins } from './manager-mise-supported-plugins';
 import { generatePlatforms } from './platforms';
 import { generatePresets } from './presets';
 import { generateSchema } from './schema';
@@ -46,6 +47,10 @@ export async function generateDocs(): Promise<void> {
     logger.info('* managers/asdf/supported-plugins');
     await generateManagerAsdfSupportedPlugins(dist);
 
+    // managers/mise supported plugins
+    logger.info('* managers/mise/supported-plugins');
+    await generateManagerMiseSupportedPlugins(dist);
+
     // presets
     logger.info('* presets');
     await generatePresets(dist);
diff --git a/tools/docs/manager-mise-supported-plugins.ts b/tools/docs/manager-mise-supported-plugins.ts
new file mode 100644
index 0000000000..f2291f9ab3
--- /dev/null
+++ b/tools/docs/manager-mise-supported-plugins.ts
@@ -0,0 +1,29 @@
+import {
+  asdfTooling,
+  miseTooling,
+} from '../../lib/modules/manager/mise/upgradeable-tooling';
+import { readFile, updateFile } from '../utils';
+import { replaceContent } from './utils';
+
+function generateMiseTooling(): string {
+  return Object.entries(miseTooling)
+    .map(([name, { misePluginUrl }]) => `- [${name} (mise)](${misePluginUrl})`)
+    .join('\n');
+}
+
+function generateAsdfTooling(): string {
+  return Object.entries(asdfTooling)
+    .map(([name, { asdfPluginUrl }]) => `- [${name} (asdf)](${asdfPluginUrl})`)
+    .join('\n');
+}
+
+export async function generateManagerMiseSupportedPlugins(
+  dist: string,
+): Promise<void> {
+  const indexFileName = `${dist}/modules/manager/mise/index.md`;
+  let indexContent = await readFile(indexFileName);
+  // Combine the output of both mise and asdf tooling generation
+  const combinedTooling = `${generateMiseTooling()}\n${generateAsdfTooling()}`;
+  indexContent = replaceContent(indexContent, combinedTooling);
+  await updateFile(indexFileName, indexContent);
+}
-- 
GitLab