This is an automated email from the ASF dual-hosted git repository.

jsedding pushed a commit to branch support-multi-artifact-releases
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 1d6d3ee27b2e69b3e91d311abd3e774c51364c6e
Author: Julian Sedding <[email protected]>
AuthorDate: Mon Aug 24 14:45:41 2026 +0200

    SLING-13319 - Support dist-promotion for multiple artifacts
---
 .../sling/cli/impl/release/FinalizeCommand.java    |  38 +-----
 .../sling/cli/impl/release/UpdateDistCommand.java  | 142 ++++++++++++++-------
 .../cli/impl/release/FinalizeCommandTest.java      |  13 +-
 .../cli/impl/release/UpdateDistCommandTest.java    |  78 +++++++++--
 4 files changed, 172 insertions(+), 99 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java 
b/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
index f6bba25..292e689 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/FinalizeCommand.java
@@ -27,7 +27,6 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.CredentialsService;
 import org.apache.sling.cli.impl.ExecutionMode;
-import org.apache.sling.cli.impl.dist.DistRepository;
 import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.jira.Issue;
 import org.apache.sling.cli.impl.jira.VersionClient;
@@ -288,7 +287,7 @@ public class FinalizeCommand implements Command {
             LOGGER.info("SKIPPED (staging repository already promoted; if dist 
still needs updating a PMC"
                     + " member must run update-dist separately)");
         } else {
-            stepUpdateDist(repository, mode);
+            stepUpdateDist(reusableCLIOptions.executionMode);
         }
     }
 
@@ -305,39 +304,8 @@ public class FinalizeCommand implements Command {
         }
     }
 
-    private void stepUpdateDist(StagingRepository repository, ExecutionMode 
mode) throws IOException {
-        // Delegate the download/collect/publish flow to UpdateDistCommand so 
it is not duplicated here.
-        UpdateDistCommand.DistReleasePlan plan = 
UpdateDistCommand.planDistRelease(repositoryService, repository, null);
-
-        if (plan.alreadyPublished()) {
-            LOGGER.info("dist/release already contains {} {}; skipping.", 
plan.artifactId(), plan.newVersion());
-            return;
-        }
-        if (plan.newFiles().isEmpty()) {
-            LOGGER.warn(
-                    "No artifacts were downloaded for {} {}; skipping dist 
update.",
-                    plan.artifactId(),
-                    plan.newVersion());
-            return;
-        }
-
-        if (mode == ExecutionMode.DRY_RUN) {
-            LOGGER.info(
-                    "Would publish {} file(s) to dist/release for {} {}",
-                    plan.newFiles().size(),
-                    plan.artifactId(),
-                    plan.newVersion());
-            LOGGER.info(
-                    "Would remove {} old file(s) from dist/release",
-                    plan.oldFiles().size());
-        } else {
-            DistRepository.publish(
-                    plan.artifactId(),
-                    plan.newVersion(),
-                    plan.newFiles(),
-                    plan.oldFiles(),
-                    credentialsService.getAsfCredentials());
-        }
+    private void stepUpdateDist(ExecutionMode mode) throws IOException {
+        UpdateDistCommand.doUpdateDist(repositoryService, repositoryId, null, 
mode, credentialsService);
     }
 
     private void stepCreateNextJiraVersion(Release release, ExecutionMode 
mode) throws IOException {
diff --git 
a/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java 
b/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
index 8d8677b..9aad6c6 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateDistCommand.java
@@ -19,6 +19,7 @@
 package org.apache.sling.cli.impl.release;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -26,6 +27,7 @@ import java.util.stream.Stream;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.ExecutionMode;
 import org.apache.sling.cli.impl.InputOption;
 import org.apache.sling.cli.impl.UserInput;
 import org.apache.sling.cli.impl.dist.DistRepository;
@@ -33,6 +35,7 @@ import org.apache.sling.cli.impl.nexus.Artifact;
 import org.apache.sling.cli.impl.nexus.LocalRepository;
 import org.apache.sling.cli.impl.nexus.RepositoryService;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -90,38 +93,44 @@ public class UpdateDistCommand implements Command {
 
     @Override
     public Integer call() {
+        Integer ok = doUpdateDist(
+                repositoryService, repositoryId, previousVersion, 
reusableCLIOptions.executionMode, credentialsService);
+        if (ok != null) return ok;
+        return CommandLine.ExitCode.OK;
+    }
+
+    public static @Nullable Integer doUpdateDist(
+            RepositoryService repositoryService,
+            Integer repositoryId,
+            String previousVersion,
+            ExecutionMode executionMode,
+            CredentialsService credentialsService) {
         try {
-            DistReleasePlan plan =
+            List<DistReleasePlan> plans =
                     planDistRelease(repositoryService, 
repositoryService.find(repositoryId), previousVersion);
 
-            if (plan.alreadyPublished()) {
-                LOGGER.info(
-                        "dist/release already contains {} {}; nothing to do.", 
plan.artifactId(), plan.newVersion());
+            if (plans.stream().allMatch(DistReleasePlan::alreadyPublished)) {
+                for (DistReleasePlan plan : plans) {
+                    LOGGER.info(
+                            "dist/release already contains {} {}; nothing to 
do.",
+                            plan.artifactId(),
+                            plan.newVersion());
+                }
                 return CommandLine.ExitCode.OK;
             }
-            if (plan.newFiles().isEmpty()) {
+
+            boolean noArtifacts = plans.stream()
+                    .flatMap(plan -> plan.newFiles().stream())
+                    .findFirst()
+                    .isEmpty();
+            if (noArtifacts) {
                 LOGGER.warn("No artifacts were downloaded for staging 
repository {}.", repositoryId);
                 return CommandLine.ExitCode.USAGE;
             }
 
-            switch (reusableCLIOptions.executionMode) {
-                case DRY_RUN:
-                    LOGGER.info(
-                            "Would publish {} file(s) to dist/release for {} 
{}:",
-                            plan.newFiles().size(),
-                            plan.artifactId(),
-                            plan.newVersion());
-                    plan.newFiles()
-                            .forEach(f -> LOGGER.info(
-                                    "  put {} -> {}{}", f, 
DistRepository.DIST_RELEASE_URL, f.getFileName()));
-                    if (!plan.oldFiles().isEmpty()) {
-                        LOGGER.info(
-                                "Would remove {} old file(s) from 
dist/release:",
-                                plan.oldFiles().size());
-                        plan.oldFiles().forEach(f -> LOGGER.info("  rm {}", 
DistRepository.DIST_RELEASE_URL + f));
-                    }
-                    break;
-                case INTERACTIVE:
+            for (DistReleasePlan plan : plans) {
+                boolean doPerformPublish = executionMode == ExecutionMode.AUTO;
+                if (executionMode == ExecutionMode.INTERACTIVE) {
                     String question = String.format(
                             "Publish %d file(s) for %s %s to dist/release and 
remove %d older file(s) for %s?",
                             plan.newFiles().size(),
@@ -129,31 +138,42 @@ public class UpdateDistCommand implements Command {
                             plan.newVersion(),
                             plan.oldFiles().size(),
                             plan.artifactId());
-                    if (InputOption.YES.equals(UserInput.yesNo(question, 
InputOption.YES))) {
-                        DistRepository.publish(
-                                plan.artifactId(),
-                                plan.newVersion(),
-                                plan.newFiles(),
-                                plan.oldFiles(),
-                                credentialsService.getAsfCredentials());
-                    } else {
+                    doPerformPublish = 
InputOption.YES.equals(UserInput.yesNo(question, InputOption.YES));
+                    if (!doPerformPublish) {
                         LOGGER.info("Aborted.");
                     }
-                    break;
-                case AUTO:
+                }
+
+                if (doPerformPublish) {
                     DistRepository.publish(
                             plan.artifactId(),
                             plan.newVersion(),
                             plan.newFiles(),
                             plan.oldFiles(),
                             credentialsService.getAsfCredentials());
-                    break;
+                } else {
+                    LOGGER.info(
+                            "Would publish {} file(s) to dist/release for {} 
{}:",
+                            plan.newFiles().size(),
+                            plan.artifactId(),
+                            plan.newVersion());
+
+                    plan.newFiles()
+                            .forEach(f -> LOGGER.info(
+                                    "  put {} -> {}{}", f, 
DistRepository.DIST_RELEASE_URL, f.getFileName()));
+                    if (!plan.oldFiles().isEmpty()) {
+                        LOGGER.info(
+                                "Would remove {} old file(s) from 
dist/release:",
+                                plan.oldFiles().size());
+                        plan.oldFiles().forEach(f -> LOGGER.info("  rm {}", 
DistRepository.DIST_RELEASE_URL + f));
+                    }
+                }
             }
-        } catch (IOException e) {
+        } catch (UncheckedIOException | IOException e) {
             LOGGER.warn("Failed executing command", e);
             return CommandLine.ExitCode.SOFTWARE;
         }
-        return CommandLine.ExitCode.OK;
+        return null;
     }
 
     /** What to publish to and remove from dist/release for one staged 
release. */
@@ -169,22 +189,50 @@ public class UpdateDistCommand implements Command {
      * Shared by this command and {@link FinalizeCommand} so the flow is not 
duplicated. When the version is
      * already present in {@code dist/release} the returned plan is marked 
{@link DistReleasePlan#alreadyPublished()}.
      */
-    static DistReleasePlan planDistRelease(
+    static List<DistReleasePlan> planDistRelease(
             RepositoryService repositoryService, StagingRepository repository, 
String previousVersion)
             throws IOException {
         LocalRepository localRepository = 
repositoryService.download(repository);
-        Artifact primary = localRepository.getArtifacts().stream()
+        List<Artifact> artifacts = localRepository.getArtifacts().stream()
                 .filter(a -> "pom".equals(a.getType()))
-                .findFirst()
-                .orElseThrow(() -> new IllegalStateException("No POM artifact 
found in staging repository"));
-        String artifactId = primary.getArtifactId();
-        String newVersion = primary.getVersion();
-        if (DistRepository.isVersionPublished(artifactId, newVersion)) {
-            return new DistReleasePlan(artifactId, newVersion, List.of(), 
List.of(), true);
+                .toList();
+
+        if (artifacts.isEmpty()) {
+            throw new IllegalStateException("No POM artifact found in staging 
repository");
+        }
+
+        List<DistReleasePlan> plans = artifacts.stream()
+                .filter(a -> {
+                    try {
+                        return 
DistRepository.isVersionPublished(a.getArtifactId(), a.getVersion());
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                })
+                .map(a -> new DistReleasePlan(a.getArtifactId(), 
a.getVersion(), List.of(), List.of(), true))
+                .toList();
+
+        if (!plans.isEmpty()) {
+            return plans;
+        } else {
+            return artifacts.stream()
+                    .map(a -> {
+                        try {
+                            String artifactId = a.getArtifactId();
+                            String newVersion = a.getVersion();
+                            List<Path> newFiles = 
collectDownloadedFiles(localRepository.getRootFolder()).stream()
+                                    .filter(path ->
+                                            
path.getFileName().toString().startsWith(artifactId + "-" + newVersion))
+                                    .toList();
+                            List<String> oldFiles =
+                                    
DistRepository.listPreviousReleaseFiles(artifactId, newVersion, 
previousVersion);
+                            return new DistReleasePlan(artifactId, newVersion, 
newFiles, oldFiles, false);
+                        } catch (IOException e) {
+                            throw new UncheckedIOException(e);
+                        }
+                    })
+                    .toList();
         }
-        List<Path> newFiles = 
collectDownloadedFiles(localRepository.getRootFolder());
-        List<String> oldFiles = 
DistRepository.listPreviousReleaseFiles(artifactId, newVersion, 
previousVersion);
-        return new DistReleasePlan(artifactId, newVersion, newFiles, oldFiles, 
false);
     }
 
     /**
diff --git 
a/src/test/java/org/apache/sling/cli/impl/release/FinalizeCommandTest.java 
b/src/test/java/org/apache/sling/cli/impl/release/FinalizeCommandTest.java
index 0b06b43..d044874 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/FinalizeCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/FinalizeCommandTest.java
@@ -159,12 +159,12 @@ public class FinalizeCommandTest {
         try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class);
                 MockedStatic<DistRepository> distRepo = 
mockStatic(DistRepository.class)) {
             dist.when(() -> UpdateDistCommand.planDistRelease(any(), any(), 
any()))
-                    .thenReturn(new UpdateDistCommand.DistReleasePlan(
+                    .thenReturn(List.of(new UpdateDistCommand.DistReleasePlan(
                             "org.apache.sling.cli.test",
                             "1.0.0",
                             
List.of(java.nio.file.Path.of("org.apache.sling.cli.test-1.0.0.pom")),
                             List.of("org.apache.sling.cli.test-0.9.0.pom"),
-                            false));
+                            false)));
             Command command = createCommand(123, ExecutionMode.DRY_RUN);
             assertEquals(CommandLine.ExitCode.OK, (int) command.call());
             assertTrue(logCapture.containsMessage("--- Step 1/6: Update 
dist.apache.org ---"));
@@ -193,14 +193,17 @@ public class FinalizeCommandTest {
         try (MockedStatic<UpdateDistCommand> dist = 
mockStatic(UpdateDistCommand.class);
                 MockedStatic<DistRepository> distRepo = 
mockStatic(DistRepository.class)) {
             dist.when(() -> UpdateDistCommand.planDistRelease(any(), any(), 
any()))
-                    .thenReturn(new UpdateDistCommand.DistReleasePlan(
+                    .thenReturn(List.of(new UpdateDistCommand.DistReleasePlan(
                             "org.apache.sling.cli.test",
                             "1.0.0",
                             
List.of(java.nio.file.Path.of("org.apache.sling.cli.test-1.0.0.pom")),
                             List.of("org.apache.sling.cli.test-0.9.0.pom"),
-                            false));
+                            false)));
+            dist.when(() -> UpdateDistCommand.doUpdateDist(any(), any(), 
any(), any(), any()))
+                    .thenCallRealMethod();
             Command command = createCommand(123, ExecutionMode.AUTO);
-            assertEquals(CommandLine.ExitCode.OK, (int) command.call());
+            int exitCode = command.call();
+            assertEquals(CommandLine.ExitCode.OK, exitCode);
             verify(repositoryService).promote(any());
             // dist upload is actually committed for a PMC member
             distRepo.verify(
diff --git 
a/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java 
b/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
index 836a0df..c2ade92 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/UpdateDistCommandTest.java
@@ -21,6 +21,7 @@ package org.apache.sling.cli.impl.release;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -91,7 +92,7 @@ public class UpdateDistCommandTest {
                 ARTIFACT + "-1.3.4.pom.asc",
                 ARTIFACT + "-1.3.4-source-release.zip",
                 ARTIFACT + "-1.3.4-source-release.zip.asc",
-                ARTIFACT + "-1.3.6.pom", // the new version - must be kept 
(not removed)
+                ARTIFACT + "-1.3.6.pom", // the new newVersion - must be kept 
(not removed)
                 ARTIFACT + "-1.3.6.pom.asc",
                 ARTIFACT + "-extra-1.0.0.pom" // a sibling artifact - must be 
ignored
                 );
@@ -104,7 +105,7 @@ public class UpdateDistCommandTest {
             assertEquals(4, old.size());
             assertTrue(old.contains(ARTIFACT + "-1.3.4.pom"));
             assertTrue(old.contains(ARTIFACT + "-1.3.4-source-release.zip"));
-            // the version being published is never removed
+            // the newVersion being published is never removed
             assertFalse(old.contains(ARTIFACT + "-1.3.6.pom"));
             assertFalse(old.contains(ARTIFACT + "-1.3.6.pom.asc"));
             // sibling artifact with a non-numeric component is ignored
@@ -114,8 +115,8 @@ public class UpdateDistCommandTest {
 
     @Test
     public void 
testAutoDeduceDoesNotConfuseVersionPrefixesAndKeepsNewerVersions() throws 
Exception {
-        // publishing 1.0.14 must not treat 1.0.140 as the same version, and 
must not remove it either:
-        // 1.0.140 > 1.0.14, so it is a newer version and is left untouched 
(nothing older is present)
+        // publishing 1.0.14 must not treat 1.0.140 as the same newVersion, 
and must not remove it either:
+        // 1.0.140 > 1.0.14, so it is a newer newVersion and is left untouched 
(nothing older is present)
         List<String> releaseDir = List.of(ARTIFACT + "-1.0.140.pom", ARTIFACT 
+ "-1.0.14.pom");
         try (MockedStatic<DistRepository> dist = 
mockStatic(DistRepository.class, CALLS_REAL_METHODS)) {
             dist.when(() -> 
DistRepository.listFiles(eq(DistRepository.DIST_RELEASE_URL), anyString()))
@@ -123,13 +124,13 @@ public class UpdateDistCommandTest {
 
             List<String> old = 
DistRepository.listPreviousReleaseFiles(ARTIFACT, "1.0.14", null);
 
-            assertTrue("a newer version must never be removed", old.isEmpty());
+            assertTrue("a newer newVersion must never be removed", 
old.isEmpty());
         }
     }
 
     @Test
     public void testAutoDeduceRemovesOnlyClosestOlderVersionAcrossStreams() 
throws Exception {
-        // parallel maintenance streams: publishing 2.0.4 must remove 2.0.2 
(the closest older version)
+        // parallel maintenance streams: publishing 2.0.4 must remove 2.0.2 
(the closest older newVersion)
         // but keep 1.2.4 (a different, still-maintained stream)
         List<String> releaseDir = List.of(
                 ARTIFACT + "-1.2.4.pom",
@@ -202,7 +203,49 @@ public class UpdateDistCommandTest {
 
             List<String> old = 
DistRepository.listPreviousReleaseFiles(ARTIFACT, "2.0.0", null);
 
-            assertTrue("a different major version must never be removed", 
old.isEmpty());
+            assertTrue("a different major newVersion must never be removed", 
old.isEmpty());
+        }
+    }
+
+    @Test
+    public void testAutoDeduceIdentifiesMultiArtifactReleases() throws 
Exception {
+        List<ArtifactUpdate> artifacts = List.of(
+                new ArtifactUpdate("org.apache.sling.servlets.resolver", 
"2.12.0", "2.11.4"),
+                new ArtifactUpdate("org.apache.sling.servlets.resolver", 
"3.0.10", "3.0.8"));
+
+        List<String> oldFiles = List.of(
+                "org.apache.sling.servlets.resolver-3.0.8.pom",
+                "org.apache.sling.servlets.resolver-3.0.8.pom.asc",
+                "org.apache.sling.servlets.resolver-3.0.8-source-release.zip",
+                
"org.apache.sling.servlets.resolver-3.0.8-source-release.zip.asc",
+                "org.apache.sling.servlets.resolver-2.11.4.pom",
+                "org.apache.sling.servlets.resolver-2.11.4.pom.asc",
+                "org.apache.sling.servlets.resolver-2.11.4-source-release.zip",
+                
"org.apache.sling.servlets.resolver-2.11.4-source-release.zip.asc",
+
+                // unrelated files
+                "org.apache.sling.servlets.resolver-3.0.6.pom",
+                "org.apache.sling.servlets.resolver-3.0.6.pom.asc");
+
+        try (MockedStatic<DistRepository> dist = 
mockStatic(DistRepository.class, CALLS_REAL_METHODS)) {
+            dist.when(() -> 
DistRepository.listFiles(eq(DistRepository.DIST_RELEASE_URL), anyString()))
+                    .thenReturn(oldFiles);
+
+            artifacts.forEach(a -> {
+                try {
+                    List<String> old = 
DistRepository.listPreviousReleaseFiles(a.artifactId(), a.newVersion(), null);
+
+                    assertEquals(4, old.size());
+
+                    String oldArtifact = a.oldArtifact();
+                    assertTrue(old.contains(oldArtifact + ".pom"));
+                    assertTrue(old.contains(oldArtifact + ".pom.asc"));
+                    assertTrue(old.contains(oldArtifact + 
"-source-release.zip"));
+                    assertTrue(old.contains(oldArtifact + 
"-source-release.zip.asc"));
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            });
         }
     }
 
@@ -215,7 +258,7 @@ public class UpdateDistCommandTest {
             List<String> old = 
DistRepository.listPreviousReleaseFiles(ARTIFACT, "1.3.6", "1.3.4");
 
             assertEquals(List.of(ARTIFACT + "-1.3.4.pom"), old);
-            // when an explicit version is given, the directory is not 
enumerated with the bare prefix
+            // when an explicit newVersion is given, the directory is not 
enumerated with the bare prefix
             dist.verify(
                     () -> 
DistRepository.listFiles(eq(DistRepository.DIST_RELEASE_URL), eq(ARTIFACT + 
"-")), never());
         }
@@ -223,10 +266,10 @@ public class UpdateDistCommandTest {
 
     @Test
     public void testAutoDeduceKeepsNewVersionWithClassifierAndExtension() 
throws Exception {
-        // files for the version being published (with both an extension '.' 
and a classifier '-' right
-        // after the version) must be kept, exercising belongsToVersion's 
trailing-character check
+        // files for the newVersion being published (with both an extension 
'.' and a classifier '-' right
+        // after the newVersion) must be kept, exercising belongsToVersion's 
trailing-character check
         List<String> releaseDir = List.of(
-                ARTIFACT + "-1.3.6", // exact match: filename equals the 
version prefix with no extension
+                ARTIFACT + "-1.3.6", // exact match: filename equals the 
newVersion prefix with no extension
                 ARTIFACT + "-1.3.6.pom",
                 ARTIFACT + "-1.3.6-source-release.zip",
                 ARTIFACT + "-1.3.4.pom");
@@ -340,7 +383,7 @@ public class UpdateDistCommandTest {
         try (MockedStatic<DistRepository> dist = 
mockStatic(DistRepository.class, CALLS_REAL_METHODS)) {
             dist.when(() -> 
DistRepository.listFiles(eq(DistRepository.DIST_RELEASE_URL), eq(ARTIFACT + 
"-1.3.4")))
                     .thenReturn(List.of(ARTIFACT + "-1.3.4.pom"));
-            // the already-published probe queries the new version's prefix; 
it is not yet in dist/release
+            // the already-published probe queries the new newVersion's 
prefix; it is not yet in dist/release
             dist.when(() -> 
DistRepository.listFiles(eq(DistRepository.DIST_RELEASE_URL), eq(ARTIFACT + 
"-1.3.6")))
                     .thenReturn(List.of());
             dist.when(() -> DistRepository.publish(any(), any(), any(), any(), 
any()))
@@ -539,4 +582,15 @@ public class UpdateDistCommandTest {
                 result instanceof UpdateDistCommand);
         return result;
     }
+
+    private record ArtifactUpdate(String artifactId, String newVersion, String 
oldVersion) {
+        String oldArtifact() {
+            return artifactId() + "-" + oldVersion();
+        }
+
+        String newArtifact() {
+            return artifactId() + "-" + newVersion();
+        }
+    }
+    ;
 }

Reply via email to