diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 2132e67ad4..d7844dde0a 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -15,7 +15,7 @@ The full list of changes for this release can be found in https://github.com/dev
Release with new features and bugfixes:
-* https://github.com/devonfw/IDEasy/issues/797[#797]: Fix VSCode install on macOS
+* https://github.com/devonfw/IDEasy/issues/797[#797]: Fix VSCode install on macOS
* https://github.com/devonfw/IDEasy/issues/1956[#1956]: Merging MAVEN_ARGS buggy
* https://github.com/devonfw/IDEasy/issues/1978[#1978]: Enhanced the quality status documentation
* https://github.com/devonfw/IDEasy/issues/1946[#1946]: Add Spyder Python IDE
@@ -37,6 +37,7 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/1457[#1457]: Improve CLI error messages on invalid args or commandlets not available in current context
* https://github.com/devonfw/IDEasy/issues/2030[#2030]: Fix Plugin install error in Intellij
* https://github.com/devonfw/IDEasy/issues/2032[#2032]: commandlet env bash leads to broken output
+* https://github.com/devonfw/IDEasy/issues/2026[#2026]: Create UvRepository and UvBasedCommandlet
The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/45?closed=1[milestone 2026.06.001].
diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
index 19c53c7975..0ba7bb1b09 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java
@@ -8,8 +8,6 @@
import java.util.Map;
import java.util.NoSuchElementException;
-import com.devonfw.tools.ide.tool.inso.Inso;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,16 +35,18 @@
import com.devonfw.tools.ide.tool.gradle.Gradle;
import com.devonfw.tools.ide.tool.gui.Gui;
import com.devonfw.tools.ide.tool.helm.Helm;
+import com.devonfw.tools.ide.tool.inso.Inso;
import com.devonfw.tools.ide.tool.intellij.Intellij;
import com.devonfw.tools.ide.tool.jasypt.Jasypt;
import com.devonfw.tools.ide.tool.java.Java;
import com.devonfw.tools.ide.tool.jmc.Jmc;
+import com.devonfw.tools.ide.tool.just.Just;
import com.devonfw.tools.ide.tool.kotlinc.Kotlinc;
import com.devonfw.tools.ide.tool.kotlinc.KotlincNative;
import com.devonfw.tools.ide.tool.kubectl.KubeCtl;
import com.devonfw.tools.ide.tool.lazydocker.LazyDocker;
-import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.msvc.Msvc;
+import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.nest.Nest;
import com.devonfw.tools.ide.tool.ng.Ng;
import com.devonfw.tools.ide.tool.node.Node;
@@ -172,6 +172,7 @@ public CommandletManagerImpl(IdeContext context) {
add(new Nest(context));
add(new Cdk(context));
add(new Claude(context));
+ add(new Just(context));
}
/**
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
index d7a8f92a8e..8d379d90c6 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
@@ -77,6 +77,7 @@
import com.devonfw.tools.ide.tool.pip.PipRepository;
import com.devonfw.tools.ide.tool.repository.DefaultToolRepository;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
+import com.devonfw.tools.ide.tool.uv.UvRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.util.DateTimeUtil;
import com.devonfw.tools.ide.util.PrivacyUtil;
@@ -155,6 +156,8 @@ public abstract class AbstractIdeContext implements IdeContext, IdeLogArgFormatt
private PipRepository pipRepository;
+ private UvRepository uvRepository;
+
private DirectoryMerger workspaceMerger;
protected UrlMetadata urlMetadata;
@@ -295,6 +298,13 @@ protected PipRepository createPipRepository() {
return new PipRepository(this);
}
+ /**
+ * @return a new {@link UvRepository}
+ */
+ protected UvRepository createUvRepository() {
+ return new UvRepository(this);
+ }
+
private Path findIdeRoot(Path ideHomePath) {
Path ideRootPath = null;
@@ -514,6 +524,14 @@ public PipRepository getPipRepository() {
return this.pipRepository;
}
+ @Override
+ public UvRepository getUvRepository() {
+ if (this.uvRepository == null) {
+ this.uvRepository = createUvRepository();
+ }
+ return this.uvRepository;
+ }
+
@Override
public CustomToolRepository getCustomToolRepository() {
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
index 204a930fb8..e8279c7177 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
@@ -31,6 +31,7 @@
import com.devonfw.tools.ide.tool.npm.NpmRepository;
import com.devonfw.tools.ide.tool.pip.PipRepository;
import com.devonfw.tools.ide.tool.repository.ToolRepository;
+import com.devonfw.tools.ide.tool.uv.UvRepository;
import com.devonfw.tools.ide.url.model.UrlMetadata;
import com.devonfw.tools.ide.variable.IdeVariables;
import com.devonfw.tools.ide.version.IdeVersion;
@@ -372,6 +373,11 @@ default void requireOnline(String purpose, boolean explicitOnlineCheck) {
*/
PipRepository getPipRepository();
+ /**
+ * @return the {@link UvRepository}.
+ */
+ UvRepository getUvRepository();
+
/**
* @return the {@link Path} to the IDE instance directory. You can have as many IDE instances on the same computer as independent tenants for different
* isolated projects.
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/just/Just.java b/cli/src/main/java/com/devonfw/tools/ide/tool/just/Just.java
new file mode 100644
index 0000000000..5791e10e29
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/just/Just.java
@@ -0,0 +1,28 @@
+package com.devonfw.tools.ide.tool.just;
+
+import java.util.Set;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.tool.uv.UvBasedCommandlet;
+
+/**
+ * {@link UvBasedCommandlet} for just.
+ */
+public class Just extends UvBasedCommandlet {
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link com.devonfw.tools.ide.context.IdeContext}.
+ */
+ public Just(IdeContext context) {
+ super(context, "just", Set.of(Tag.BUILD));
+ }
+
+ @Override
+ public String getPackageName() {
+ return "rust-just";
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java
index 9665353f4d..76478091f3 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/Uv.java
@@ -44,6 +44,7 @@ public void installPython(Path installationPath, VersionIdentifier resolvedVersi
ProcessResult result = runTool(processContext, ProcessMode.DEFAULT_CAPTURE, List.of("venv", "--python", resolvedVersion.toString()));
assert result.isSuccessful();
}
+
@Override
public void setEnvironment(EnvironmentContext environmentContext, ToolInstallation toolInstallation, boolean additionalInstallation) {
@@ -51,5 +52,6 @@ public void setEnvironment(EnvironmentContext environmentContext, ToolInstallati
Path pythonPath = this.context.getSoftwarePath().resolve("python");
environmentContext.withEnvVar("UV_TOOL_DIR", pythonPath.resolve("tools").toString());
environmentContext.withEnvVar("UV_TOOL_BIN_DIR", pythonPath.resolve("bin").toString());
+ environmentContext.withPathEntry(pythonPath.resolve("bin"));
}
}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifact.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifact.java
new file mode 100644
index 0000000000..4a2abd98a0
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifact.java
@@ -0,0 +1,51 @@
+package com.devonfw.tools.ide.tool.uv;
+
+import java.util.Objects;
+
+import com.devonfw.tools.ide.tool.repository.SoftwareArtifact;
+
+/**
+ * Simple type representing a uv tool artifact resolved from PyPI.
+ */
+public final class UvArtifact extends SoftwareArtifact {
+
+ private final String name;
+
+ /**
+ * The constructor.
+ *
+ * @param name the {@link #getName() name}.
+ * @param version the {@link #getVersion() version}.
+ */
+ public UvArtifact(String name, String version) {
+ super(version);
+ this.name = name;
+ }
+
+ /**
+ * @return the package name in PyPi.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ protected String computeKey() {
+ return this.name + "@" + this.version;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (obj instanceof UvArtifact other) {
+ return this.name.equals(other.name) && this.version.equals(other.version);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.name, this.version);
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifactMetadata.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifactMetadata.java
new file mode 100644
index 0000000000..df9dc25c6d
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvArtifactMetadata.java
@@ -0,0 +1,84 @@
+package com.devonfw.tools.ide.tool.uv;
+
+import java.util.Collections;
+import java.util.Set;
+
+import com.devonfw.tools.ide.os.OperatingSystem;
+import com.devonfw.tools.ide.os.SystemArchitecture;
+import com.devonfw.tools.ide.url.model.file.UrlChecksums;
+import com.devonfw.tools.ide.url.model.file.UrlChecksumsEmpty;
+import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+
+/**
+ * {@link UrlDownloadFileMetadata} representing metadata of a uv tool artifact from PyPI.
+ */
+public class UvArtifactMetadata implements UrlDownloadFileMetadata {
+
+ private final UvArtifact uvArtifact;
+ private final String tool;
+ private final String edition;
+ private final VersionIdentifier version;
+
+ /**
+ * The constructor.
+ *
+ * @param uvArtifact the {@link UvArtifact}.
+ * @param tool the tool name.
+ * @param edition the edition.
+ */
+ public UvArtifactMetadata(UvArtifact uvArtifact, String tool, String edition) {
+ this.uvArtifact = uvArtifact;
+ this.version = VersionIdentifier.of(uvArtifact.getVersion());
+ this.tool = tool;
+ this.edition = edition;
+ }
+
+ /**
+ * @return the {@link UvArtifact}
+ */
+ public UvArtifact getUvArtifact() {
+ return this.uvArtifact;
+ }
+
+ @Override
+ public String getTool() {
+ return this.tool;
+ }
+
+ @Override
+ public String getEdition() {
+ return this.edition;
+ }
+
+ @Override
+ public VersionIdentifier getVersion() {
+ return this.version;
+ }
+
+ @Override
+ public Set getUrls() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public OperatingSystem getOs() {
+ return null;
+ }
+
+ @Override
+ public SystemArchitecture getArch() {
+ return null;
+ }
+
+ @Override
+ public UrlChecksums getChecksums() {
+ return UrlChecksumsEmpty.of();
+ }
+
+ @Override
+ public String toString() {
+ return this.uvArtifact.toString();
+ }
+
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvBasedCommandlet.java
new file mode 100644
index 0000000000..a27977bb91
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvBasedCommandlet.java
@@ -0,0 +1,90 @@
+package com.devonfw.tools.ide.tool.uv;
+
+import java.nio.file.Files;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.devonfw.tools.ide.common.Tag;
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.log.IdeLogLevel;
+import com.devonfw.tools.ide.process.ProcessContext;
+import com.devonfw.tools.ide.process.ProcessErrorHandling;
+import com.devonfw.tools.ide.process.ProcessMode;
+import com.devonfw.tools.ide.process.ProcessResult;
+import com.devonfw.tools.ide.tool.PackageManagerBasedLocalToolCommandlet;
+import com.devonfw.tools.ide.tool.PackageManagerRequest;
+import com.devonfw.tools.ide.tool.repository.ToolRepository;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+
+/**
+ * {@link PackageManagerBasedLocalToolCommandlet} for tools that are installed via {@code uv tool install} for PyPI. Concrete tools(e.g. {@code Just}) only need
+ * a constructor and to override {@link #getPackageName()} if the PyPI package name differs from the IDEasy tool name.
+ */
+public abstract class UvBasedCommandlet extends PackageManagerBasedLocalToolCommandlet {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UvBasedCommandlet.class);
+
+ /**
+ * The constructor.
+ *
+ * @param context the {@link IdeContext}.
+ * @param tool the {@link #getName() tool name}.
+ * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
+ */
+ public UvBasedCommandlet(IdeContext context, String tool, Set tags) {
+ super(context, tool, tags);
+ }
+
+ @Override
+ protected Class getPackageManagerClass() {
+ return Uv.class;
+ }
+
+ @Override
+ public ToolRepository getToolRepository() {
+ return this.context.getUvRepository();
+ }
+
+ @Override
+ protected Uv getParentTool() {
+ return this.context.getCommandletManager().getCommandlet(Uv.class);
+ }
+
+ @Override
+ protected String appendVersion(String tool, VersionIdentifier version) {
+ return tool + "@" + version;
+ }
+
+ @Override
+ protected void completeRequestArgs(PackageManagerRequest request) {
+ request.addArg("tool");
+ super.completeRequestArgs(request);
+ }
+
+ @Override
+ protected VersionIdentifier computeInstalledVersion() {
+ if (!Files.isDirectory(this.context.getSoftwarePath().resolve("uv"))) {
+ LOG.trace("Since uv is not installed, the tool {} cannot be installed either.", this.tool);
+ return null;
+ }
+ String packageName = getPackageName();
+ PackageManagerRequest request = new PackageManagerRequest("list", packageName).addArg("tool").addArg("list")
+ .setProcessMode(ProcessMode.DEFAULT_CAPTURE);
+ ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.NONE);
+ request.setProcessContext(pc);
+ ProcessResult result = runPackageManager(request);
+ if (result.isSuccessful()) {
+ String prefix = packageName + "v";
+ for (String line : result.getOut()) {
+ if (line.startsWith(prefix)) {
+ return VersionIdentifier.of(line.substring(prefix.length()).trim());
+ }
+ }
+ }
+ LOG.debug("Could not determine installed version from 'uv tool list' output.");
+ result.log(IdeLogLevel.DEBUG);
+ return null;
+ }
+}
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvRepository.java b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvRepository.java
new file mode 100644
index 0000000000..4a6f4f654d
--- /dev/null
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/uv/UvRepository.java
@@ -0,0 +1,72 @@
+package com.devonfw.tools.ide.tool.uv;
+
+import java.util.List;
+
+import com.devonfw.tools.ide.context.IdeContext;
+import com.devonfw.tools.ide.json.JsonMapping;
+import com.devonfw.tools.ide.tool.ToolCommandlet;
+import com.devonfw.tools.ide.tool.pip.PypiObject;
+import com.devonfw.tools.ide.tool.repository.ArtifactToolRepository;
+import com.devonfw.tools.ide.version.VersionIdentifier;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Implementation of {@link ArtifactToolRepository} for tools installed via {@code uv tool} and resolved from PyPI.
+ */
+public class UvRepository extends ArtifactToolRepository {
+
+ public static final String REGISTRY_URL = "https://pypi.org/pypi/";
+ private static final ObjectMapper MAPPER = JsonMapping.create();
+ public static final String ID = "uv";
+
+ /**
+ * The constructor.
+ *
+ * @param context the owning {@link IdeContext}.
+ */
+ public UvRepository(IdeContext context) {
+ super(context);
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ protected UvArtifact resolveArtifact(String tool, String edition, VersionIdentifier version, ToolCommandlet toolCommandlet) {
+ if (toolCommandlet instanceof UvBasedCommandlet uv) {
+ String name = uv.getPackageName();
+ if (version == null) {
+ version = VersionIdentifier.LATEST;
+ }
+ return new UvArtifact(name, version.toString());
+ }
+ throw new UnsupportedOperationException("Tool '" + tool + "' is not supported by uv repository.");
+ }
+
+ @Override
+ protected List fetchVersions(UvArtifact artifact) {
+ String url = getRegistryUrl() + artifact.getName() + "/json";
+ String json = this.context.getFileAccess().download(url);
+ try {
+ PypiObject pypiObject = MAPPER.readValue(json, PypiObject.class);
+ return pypiObject.releases();
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Failed to process JSON from " + url, e);
+ }
+ }
+
+ /**
+ * @return the registry URL.
+ */
+ private String getRegistryUrl() {
+ return REGISTRY_URL;
+ }
+
+ @Override
+ public UvArtifactMetadata getMetadata(UvArtifact artifact, String tool, String edition) {
+ return new UvArtifactMetadata(artifact, tool, edition);
+ }
+}
diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties
index c197f8cb0b..20387aeb17 100644
--- a/cli/src/main/resources/nls/Help.properties
+++ b/cli/src/main/resources/nls/Help.properties
@@ -73,6 +73,8 @@ cmd.java=Tool commandlet for Java (OpenJDK).
cmd.java.detail=Java lets you develop and deploy Java applications on desktops and servers. Detailed documentation can be found at https://openjdk.org/
cmd.jmc=Tool commandlet for JDK Mission Control (performance analysis).
cmd.jmc.detail=JDK Mission Control is a profiling and monitoring tool for Java applications. Detailed documentation can be found at https://docs.oracle.com/en/java/java-components/jdk-mission-control/
+cmd.just=Tool commandlet for just (command runner).
+cmd.just.detail=just is a handy command runner for saving and running project-specific commands, similar to make. It is installed via uv. Detailed documentation can be found at https://github.com/casey/just
cmd.kotlinc=Tool commandlet for Kotlin (compiler for JRE language).
cmd.kotlinc-native=Tool commandlet for Kotlin-Native (compiler for JRE language).
cmd.kotlinc-native.detail=Kotlin/Native Compiler (kotlinc-native) compiles Kotlin code to native executables. Detailed documentation can be found at https://kotlinlang.org/docs/reference/native-overview.html
diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties
index ffe1bd8459..dbaa18475e 100644
--- a/cli/src/main/resources/nls/Help_de.properties
+++ b/cli/src/main/resources/nls/Help_de.properties
@@ -73,6 +73,8 @@ cmd.java=Werkzeug Kommando für Java (OpenJDK).
cmd.java.detail=Java kann zum Erstellen und Verbreiten von Java Anwendungen für PCs und Server verwendet werden. Detaillierte Dokumentation ist zu finden unter https://openjdk.org/
cmd.jmc=Werkzeug Kommando für JDK Mission Control (Performance Analyse).
cmd.jmc.detail=JDK Mission Control ist ein Profiling- und Überwachungswerkzeug für Java-Anwendungen. Detaillierte Dokumentation ist zu finden unter https://docs.oracle.com/en/java/java-components/jdk-mission-control/
+cmd.just=Tool-Commandlet für just (Command-Runner)
+cmd.just.detail=just ist ein praktischer Command-Runner zum Speichern und Ausführen projektspezifischer Befehl, ähnlich wie make. Es wird über uv installiert. Eine ausführliche Dokumentation findest du unter https://github.com/casey/just
cmd.kotlinc=Werkzeug Kommando für Kotlin (Compiler für JRE Sprache).
cmd.kotlinc-native=Werkzeug Kommando für Kotlin-Native (Compiler für JRE Sprache).
cmd.kotlinc-native.detail=Der Kotlin/Native-Compiler (kotlinc-native) kompiliert Kotlin-Code in native ausführbare Dateien. Detaillierte Dokumentation ist zu finden unter https://kotlinlang.org/docs/reference/native-overview.html
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/just/JustTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/just/JustTest.java
new file mode 100644
index 0000000000..ec2056bb7a
--- /dev/null
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/just/JustTest.java
@@ -0,0 +1,59 @@
+package com.devonfw.tools.ide.tool.just;
+
+import org.junit.jupiter.api.Test;
+
+import com.devonfw.tools.ide.context.AbstractIdeContextTest;
+import com.devonfw.tools.ide.context.IdeTestContext;
+import com.devonfw.tools.ide.os.SystemInfoMock;
+import com.devonfw.tools.ide.tool.uv.UvBasedCommandlet;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+
+/**
+ * Test of {@link Just}.
+ */
+@WireMockTest
+public class JustTest extends AbstractIdeContextTest {
+
+ private static final String PROJECT_UV = "uv";
+
+ /**
+ * Tests that {@link Just} installs via {@code uv tool}.
+ *
+ * @param wireMockRuntimeInfo wireMock server on a random part
+ */
+ @Test
+ void testJustInstall(WireMockRuntimeInfo wireMockRuntimeInfo) {
+
+ // arrange
+ IdeTestContext context = newContext(PROJECT_UV, wireMockRuntimeInfo);
+ context.setSystemInfo(SystemInfoMock.LINUX_X64);
+ Just commandlet = new Just(context);
+
+ // act
+ commandlet.install();
+
+ // assert
+ assertThat(context).logAtInfo().hasMessageContaining("uv tool install rust-just@");
+ assertThat(context).logAtSuccess().hasMessageContaining("Successfully installed just");
+ }
+
+ /**
+ * Tests that {@link Just} is implemented as a {@link com.devonfw.tools.ide.tool.uv.UvBasedCommandlet}.
+ *
+ * @param wireMockRuntimeInfo wireMock server on a random port
+ */
+ @Test
+ void testJustIsUvBasedCommandlet(WireMockRuntimeInfo wireMockRuntimeInfo) {
+
+ //arrange
+ IdeTestContext context = newContext(PROJECT_UV, wireMockRuntimeInfo);
+ context.setSystemInfo(SystemInfoMock.LINUX_X64);
+
+ // act
+ Just commandlet = new Just(context);
+
+ // assert
+ assertThat(commandlet).isInstanceOf(UvBasedCommandlet.class);
+ }
+}
diff --git a/cli/src/test/resources/ide-projects/uv/_ide/urls/pip/dependencies.json b/cli/src/test/resources/ide-projects/uv/_ide/urls/pip/dependencies.json
new file mode 100644
index 0000000000..be63add0a5
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/_ide/urls/pip/dependencies.json
@@ -0,0 +1,8 @@
+{
+ "[23.0,)": [
+ {
+ "tool": "python",
+ "versionRange": "[3.7,3.13)"
+ }
+ ]
+}
diff --git a/cli/src/test/resources/ide-projects/uv/_ide/urls/python/dependencies.json b/cli/src/test/resources/ide-projects/uv/_ide/urls/python/dependencies.json
new file mode 100644
index 0000000000..f00aeca707
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/_ide/urls/python/dependencies.json
@@ -0,0 +1,8 @@
+{
+ "[1.0,)": [
+ {
+ "tool": "uv",
+ "versionRange": "[0.5.0,)"
+ }
+ ]
+}
diff --git a/cli/src/test/resources/ide-projects/uv/_ide/urls/python/python/3.12.12/urls b/cli/src/test/resources/ide-projects/uv/_ide/urls/python/python/3.12.12/urls
new file mode 100644
index 0000000000..32783865ba
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/_ide/urls/python/python/3.12.12/urls
@@ -0,0 +1 @@
+${testbaseurl}/download/python/python/3.12.12/python-3.12.12.tar.gz
diff --git a/cli/src/test/resources/ide-projects/uv/_ide/urls/uv/uv/0.5.11/urls b/cli/src/test/resources/ide-projects/uv/_ide/urls/uv/uv/0.5.11/urls
new file mode 100644
index 0000000000..1463421cd2
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/_ide/urls/uv/uv/0.5.11/urls
@@ -0,0 +1 @@
+${testbaseurl}/download/uv/uv/0.5.11/uv-0.5.11.tar.gz
diff --git a/cli/src/test/resources/ide-projects/uv/project/settings/ide.properties b/cli/src/test/resources/ide-projects/uv/project/settings/ide.properties
new file mode 100644
index 0000000000..fa5f79a97d
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/project/settings/ide.properties
@@ -0,0 +1,2 @@
+# pip test project settings
+PIP_EDITION=pip
diff --git a/cli/src/test/resources/ide-projects/uv/project/workspaces/.gitkeep b/cli/src/test/resources/ide-projects/uv/project/workspaces/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cli/src/test/resources/ide-projects/uv/repository/pypi/rust-just.json b/cli/src/test/resources/ide-projects/uv/repository/pypi/rust-just.json
new file mode 100644
index 0000000000..8a698536d0
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/repository/pypi/rust-just.json
@@ -0,0 +1,7 @@
+{
+ "releases": {
+ "1.35.0": [],
+ "1.36.0": [],
+ "1.37.0": []
+ }
+}
diff --git a/cli/src/test/resources/ide-projects/uv/repository/uv/uv/default/uv b/cli/src/test/resources/ide-projects/uv/repository/uv/uv/default/uv
new file mode 100755
index 0000000000..87d1a9302c
--- /dev/null
+++ b/cli/src/test/resources/ide-projects/uv/repository/uv/uv/default/uv
@@ -0,0 +1,10 @@
+#!/bin/bash
+echo "uv $*"
+cd $(dirname $0)
+cd ../../../../../..
+pwd
+if [ "$1" = "pip" ] && [ "$2" = "install" ]; then
+ cp repository/pip/pip/default/bin/pip project/software/python/bin
+elif [ "$1" = "venv" ] && [ "$2" = "--python" ]; then
+ cp -a repository/python/python/default project/software/.venv
+fi
diff --git a/documentation/LICENSE.adoc b/documentation/LICENSE.adoc
index a517f68f7f..0ba1d04e64 100644
--- a/documentation/LICENSE.adoc
+++ b/documentation/LICENSE.adoc
@@ -108,6 +108,7 @@ The column `inclusion` indicates the way the component is included:
|https://docs.nestjs.com/cli/overview[NestJS CLI] |Optional|https://github.com/nestjs/nest-cli/blob/master/LICENSE[MIT License]
|https://docs.aws.amazon.com/cdk/v2/guide/home.html[AWS CDK CLI] |Optional|https://github.com/aws/aws-cdk-cli/blob/main/LICENSE[Apache 2.0]
|https://www.spyder-ide.org/[Spyder] |Optional|https://github.com/spyder-ide/spyder/blob/master/LICENSE.txt[MIT License]
+|https://github.com/casey/just[just] |Optional|https://github.com/casey/just/blob/master/LICENSE[CC0 1.0]
|https://taskfile.dev/[Task CLI] |Optional|https://github.com/go-task/task/blob/main/LICENSE[MIT License]
|https://developer.konghq.com/inso-cli |Optional|https://github.com/Kong/insomnia/blob/develop/LICENSE[Apache 2.0]
|https://www.rust-lang.org/[Rust] |Optional|https://www.rust-lang.org/policies/licenses[MIT and Apache 2.0]