diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdater.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdater.java new file mode 100644 index 0000000000..77cfc2c790 --- /dev/null +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdater.java @@ -0,0 +1,67 @@ +package com.devonfw.tools.ide.url.tool.claude; + +import com.devonfw.tools.ide.url.model.folder.UrlVersion; +import com.devonfw.tools.ide.url.updater.GithubUrlTagUpdater; +import com.devonfw.tools.ide.version.VersionIdentifier; + +/** + * {@link GithubUrlTagUpdater} for GitHub Claude Code CLI. + *

+ * Follows the official installation structure from GitHub's claude-code repository: https://github.com/anthropics/claude-code. + *

+ * Download URL pattern: https://github.com/anthropics/claude-code/releases/download/v${VERSION}/claude-${PLATFORM}-${ARCH}.tar.gz + */ +public class ClaudeUrlUpdater extends GithubUrlTagUpdater { + + private static final VersionIdentifier MIN_CLAUDE_VID = VersionIdentifier.of("2.1.117"); + private static final VersionIdentifier EXCLUDED_VERSION = VersionIdentifier.of("2.1.120"); + + + @Override + public String getTool() { + return "claude"; + } + + @Override + protected String getGithubOrganization() { + return "anthropics"; + } + + @Override + protected String getGithubRepository() { + return "claude-code"; + } + + @Override + protected void addVersion(UrlVersion urlVersion) { + String baseUrl = createGithubReleaseDownloadUrl("v${version}", "claude-"); + VersionIdentifier vid = urlVersion.getVersionIdentifier(); + + if (vid.compareVersion(MIN_CLAUDE_VID).isGreater() && !vid.compareVersion(EXCLUDED_VERSION).isEqual()) { + + doAddVersion(urlVersion, baseUrl + "linux-x64.tar.gz", LINUX, X64); + doAddVersion(urlVersion, baseUrl + "linux-arm64.tar.gz", LINUX, ARM64); + + doAddVersion(urlVersion, baseUrl + "darwin-x64.tar.gz", MAC, X64); + doAddVersion(urlVersion, baseUrl + "darwin-arm64.tar.gz", MAC, ARM64); + + doAddVersion(urlVersion, baseUrl + "win32-x64.zip", WINDOWS, X64); + doAddVersion(urlVersion, baseUrl + "win32-arm64.zip", WINDOWS, ARM64); + } + } + + @Override + protected String getVersionPrefixToRemove() { + return "v"; + } + + @Override + public String getCpeVendor() { + return "claude-code"; + } + + @Override + public String getCpeProduct() { + return "claude-code"; + } +} diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java index fcadcb2121..067098b97f 100644 --- a/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/updater/UpdateManager.java @@ -12,6 +12,7 @@ import com.devonfw.tools.ide.url.tool.androidstudio.AndroidStudioUrlUpdater; import com.devonfw.tools.ide.url.tool.aws.AwsUrlUpdater; import com.devonfw.tools.ide.url.tool.az.AzureUrlUpdater; +import com.devonfw.tools.ide.url.tool.claude.ClaudeUrlUpdater; import com.devonfw.tools.ide.url.tool.copilot.CopilotUrlUpdater; import com.devonfw.tools.ide.url.tool.corepack.CorepackUrlUpdater; import com.devonfw.tools.ide.url.tool.docker.DockerDesktopUrlUpdater; @@ -68,7 +69,7 @@ public class UpdateManager extends AbstractProcessorWithTimeout { private final UrlFinalReport urlFinalReport; private final List updaters = List.of( - new AndroidStudioUrlUpdater(), new AwsUrlUpdater(), new AzureUrlUpdater(), new CopilotUrlUpdater(), new CorepackUrlUpdater(), + new AndroidStudioUrlUpdater(), new AwsUrlUpdater(), new AzureUrlUpdater(), new ClaudeUrlUpdater(), new CopilotUrlUpdater(), new CorepackUrlUpdater(), new DockerDesktopUrlUpdater(), new DotNetUrlUpdater(), new EclipseCppUrlUpdater(), new EclipseJeeUrlUpdater(), new EclipseJavaUrlUpdater(), new GCloudUrlUpdater(), diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterMock.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterMock.java new file mode 100644 index 0000000000..7deacb8d50 --- /dev/null +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterMock.java @@ -0,0 +1,31 @@ +package com.devonfw.tools.ide.url.tool.claude; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; + +/** + * Mock of {@link ClaudeUrlUpdater} to allow integration testing with wiremock. + */ +public class ClaudeUrlUpdaterMock extends ClaudeUrlUpdater { + + private final String baseUrl; + + private final WireMockRuntimeInfo wmRuntimeInfo; + + ClaudeUrlUpdaterMock(WireMockRuntimeInfo wireMockRuntimeInfo) { + super(); + this.wmRuntimeInfo = wireMockRuntimeInfo; + this.baseUrl = wireMockRuntimeInfo.getHttpBaseUrl(); + } + + @Override + protected String getDownloadBaseUrl() { + return this.baseUrl; + } + + @Override + protected String doGetVersionUrl() { + return this.baseUrl + "/repos/" + getGithubOrganization() + "/" + getGithubRepository() + "/git/refs/tags"; + } +} + + diff --git a/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterTest.java b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterTest.java new file mode 100644 index 0000000000..6c406901a3 --- /dev/null +++ b/url-updater/src/test/java/com/devonfw/tools/ide/url/tool/claude/ClaudeUrlUpdaterTest.java @@ -0,0 +1,53 @@ +package com.devonfw.tools.ide.url.tool.claude; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; + +import java.io.IOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.devonfw.tools.ide.url.model.folder.UrlRepository; +import com.devonfw.tools.ide.url.updater.AbstractUrlUpdaterTest; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +/** + * Test of {@link ClaudeUrlUpdater}. + */ +@WireMockTest +class ClaudeUrlUpdaterTest extends AbstractUrlUpdaterTest { + + /** + * Test of {@link ClaudeUrlUpdater} for the creation of download URLs and checksums. + * + * @param tempDir Path to a temporary directory + * @param wmRuntimeInfo the {@link WireMockRuntimeInfo}. + * @throws IOException test fails + */ + @Test + void testClaudeUrlUpdater(@TempDir Path tempDir, WireMockRuntimeInfo wmRuntimeInfo) throws IOException { + // arrange + stubFor(get(urlMatching("/repos/anthropics/claude-code/git/refs/tags")).willReturn(aResponse().withStatus(200) + .withBody(readAndResolve(PATH_INTEGRATION_TEST.resolve("ClaudeUrlUpdater").resolve("claude-tags.json"), wmRuntimeInfo)))); + + stubFor(any(urlMatching("/anthropics/claude-code/releases/download/.*")).willReturn(aResponse().withStatus(200).withBody(DOWNLOAD_CONTENT))); + + UrlRepository urlRepository = UrlRepository.load(tempDir); + ClaudeUrlUpdaterMock updater = new ClaudeUrlUpdaterMock(wmRuntimeInfo); + + // act + updater.update(urlRepository); + + // assert + Path claudeDir = tempDir.resolve("claude").resolve("claude"); + assertUrlVersionOsX64(claudeDir.resolve("2.1.118")); + } +} + + diff --git a/url-updater/src/test/resources/integrationtest/ClaudeUrlUpdater/claude-tags.json b/url-updater/src/test/resources/integrationtest/ClaudeUrlUpdater/claude-tags.json new file mode 100644 index 0000000000..4507c653ab --- /dev/null +++ b/url-updater/src/test/resources/integrationtest/ClaudeUrlUpdater/claude-tags.json @@ -0,0 +1,9 @@ +[ + { + "ref": "refs/tags/v2.1.118" + }, + { + "ref": "refs/tags/v2.1.118-rc1" + } +] +