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

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-website.git


The following commit(s) were added to refs/heads/main by this push:
     new 00609ab4 feat: publish lts and X.Y Camel CLI manifest aliases (#1838)
00609ab4 is described below

commit 00609ab4371592b6e455cfc7ac90d57f1d5eb6fc
Author: Federico Mariani <[email protected]>
AuthorDate: Wed Oct 28 15:24:09 2026 +0100

    feat: publish lts and X.Y Camel CLI manifest aliases (#1838)
    
    * feat: publish lts and X.Y Camel CLI manifest aliases
    
    Adds lts.properties (newest LTS release) and X.Y.properties (newest patch
    of each release line) next to latest.properties under
    static/camel-cli/releases, for the installer selectors of CAMEL-26345.
    
    - Re-vendor scripts/WebsiteManifestGenerator.java, which now maintains
      the aliases forward-only and has a --rebuild-aliases [--check] mode.
    - update-camel-cli-manifest.sh reads kind/draft/category from
      content/releases/release-<version>.md, passes --lts true for LTS
      releases, fails for a missing or draft release page, and checks the
      aliases afterwards. CAMEL_GENERATOR_SRC and CAMEL_INSTALL_SRC_DIR take
      the generator and installers from a local apache/camel checkout.
    - scripts/camel-cli-aliases.sh derives the LTS versions from the release
      pages and runs the generator to check or rebuild the aliases.
    - Backfill: lts -> 4.22.2, 4.13 ... 4.23 -> the newest patch of each line.
    - CI: a pr.yaml job checks the aliases against the exact-version manifests.
    - Document the manifests in content/download/camel-cli-launcher.md.
    
    Refs apache/camel-website#1837
    
    * chore: re-vendor the manifest generator from apache/camel main, and 
require JDK 21 for it
---
 .github/workflows/pr.yaml                 |  17 ++
 content/download/camel-cli-launcher.md    |  21 +-
 scripts/WebsiteManifestGenerator.java     | 305 +++++++++++++++++++++++++++---
 scripts/camel-cli-aliases.sh              | 101 ++++++++++
 static/camel-cli/releases/4.13.properties |  20 ++
 static/camel-cli/releases/4.14.properties |  20 ++
 static/camel-cli/releases/4.15.properties |  20 ++
 static/camel-cli/releases/4.16.properties |  20 ++
 static/camel-cli/releases/4.17.properties |  20 ++
 static/camel-cli/releases/4.18.properties |  20 ++
 static/camel-cli/releases/4.19.properties |  20 ++
 static/camel-cli/releases/4.20.properties |  20 ++
 static/camel-cli/releases/4.21.properties |  20 ++
 static/camel-cli/releases/4.22.properties |  20 ++
 static/camel-cli/releases/4.23.properties |  20 ++
 static/camel-cli/releases/lts.properties  |  20 ++
 update-camel-cli-manifest.sh              |  57 +++++-
 17 files changed, 704 insertions(+), 37 deletions(-)

diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index c8f408d7..8b208039 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -75,6 +75,23 @@ jobs:
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
+  # latest.properties, lts.properties and every X.Y.properties under
+  # static/camel-cli/releases must be copies of the exact-version manifests
+  # they select (lts from `kind: lts` in content/releases). Fails on drift;
+  # fix it with `scripts/camel-cli-aliases.sh rebuild`.
+  camel-cli-manifests:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v5
+      - name: Set up JDK
+        uses: actions/setup-java@v5
+        with:
+          distribution: temurin
+          java-version: '21'
+      - name: Check Camel CLI alias manifests
+        run: scripts/camel-cli-aliases.sh check
+
   checks:
     needs: detect-drafts
     if: needs.detect-drafts.outputs.drafts-only != 'true'
diff --git a/content/download/camel-cli-launcher.md 
b/content/download/camel-cli-launcher.md
index 1f146d2d..3b7ffd19 100644
--- a/content/download/camel-cli-launcher.md
+++ b/content/download/camel-cli-launcher.md
@@ -10,13 +10,15 @@ or elevation.
 
 ## Requirements
 
-- A Java 17 or newer runtime discoverable on your system.
+- A Java runtime discoverable on your system: Java 21 or newer for Camel 4.24 
onwards, Java 17 or newer
+  for older versions such as 4.22.x.
 - `curl` (macOS and Linux) or PowerShell 5.1+ (Windows).
 
 ## Quick install
 
 With no arguments the installer resolves and installs the latest published
-release.
+release. The latest release is not always an LTS release: to install an LTS
+release, pin its exact version (for example `4.22.2`) as shown below.
 
 ### macOS and Linux
 
@@ -60,8 +62,21 @@ but verifies it against a SHA-256 checksum recorded in a 
manifest served from
 hosts, so tampering would require compromising both at once. The installer also
 rejects archives containing absolute paths, `../` traversal, escaping symlinks,
 or more than one top-level directory, and runs the staged launcher once to
-confirm a Java 17+ runtime before activating it. If that check fails, any
+confirm a suitable Java runtime before activating it. If that check fails, any
 previously installed version is left untouched.
 
+The manifests live under `https://camel.apache.org/camel-cli/releases/`:
+
+| Manifest | Selects |
+| --- | --- |
+| `X.Y.Z.properties` (e.g. `4.22.2.properties`) | That exact release. It never 
changes once published. |
+| `latest.properties` | The newest release. This is what the installer uses by 
default. |
+| `lts.properties` | The newest LTS release. |
+| `X.Y.properties` (e.g. `4.22.properties`) | The newest patch release of the 
`X.Y` line. |
+
+`latest.properties`, `lts.properties` and `X.Y.properties` are copies of the
+exact-version manifest they select, and only move forward to a newer version:
+a patch on an older LTS line does not move `lts.properties` back.
+
 The installers are maintained in the Apache Camel repository
 ([apache/camel#24682](https://github.com/apache/camel/pull/24682)).
diff --git a/scripts/WebsiteManifestGenerator.java 
b/scripts/WebsiteManifestGenerator.java
index 64ad8f15..fb143ea0 100644
--- a/scripts/WebsiteManifestGenerator.java
+++ b/scripts/WebsiteManifestGenerator.java
@@ -34,6 +34,7 @@ import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HexFormat;
 import java.util.LinkedHashMap;
@@ -41,26 +42,54 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 /**
  * JDK-only tool that computes SHA-256 checksums for a release TAR/ZIP pair 
and writes the
  * website's four-field release manifest ({@code format}, {@code version}, 
{@code tar_sha256},
- * {@code zip_sha256}). Per-version manifests are immutable; {@code 
latest.properties} can only
- * move forward to a higher semantic version and cannot silently change 
checksums for the same
- * version. Writes are atomic.
+ * {@code zip_sha256}). Writes are atomic.
  *
  * <p>
- * Usage: {@code java WebsiteManifestGenerator.java --version X.Y.Z --tar 
<path> --zip <path>
- * --output <dir> --latest <true|false>}
+ * Manifests live in {@code <output>/releases}:
+ * <ul>
+ * <li>{@code X.Y.Z.properties}: one per release, immutable.</li>
+ * <li>{@code X.Y.properties}: the newest release of the {@code X.Y} line.</li>
+ * <li>{@code lts.properties}: the newest LTS release.</li>
+ * <li>{@code latest.properties}: the newest release.</li>
+ * </ul>
+ * An alias is a byte-identical copy of the {@code X.Y.Z.properties} it points 
at. When publishing,
+ * aliases only move forward to a higher version (numeric ordering) and never 
silently change the
+ * checksums of the version they already point at, so a patch on an older LTS 
line (for example
+ * 4.18.6 after 4.22.2) leaves {@code lts.properties} unchanged.
+ *
+ * <p>
+ * Publish a release: {@code java WebsiteManifestGenerator.java --version 
X.Y.Z --tar <path>
+ * --zip <path> --output <dir> --latest <true|false> [--lts <true|false>] 
[--install-sh <path>
+ * --install-ps1 <path>]}
+ *
+ * <p>
+ * Rebuild every alias from the existing {@code X.Y.Z.properties} files and 
the list of LTS
+ * versions (backfill), or with {@code --check} only verify them and exit with 
status 1 on drift:
+ * {@code java WebsiteManifestGenerator.java --rebuild-aliases --output <dir>
+ * --lts-versions <X.Y.Z,...> [--check]}
  */
 public class WebsiteManifestGenerator {
 
     private static final Pattern VERSION_PATTERN = 
Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)$");
+    private static final Pattern VERSION_FILE_PATTERN = 
Pattern.compile("^(\\d+\\.\\d+\\.\\d+)\\.properties$");
+    private static final Pattern LINE_ALIAS_FILE_PATTERN = 
Pattern.compile("^\\d+\\.\\d+\\.properties$");
+    private static final String LATEST_FILE = "latest.properties";
+    private static final String LTS_FILE = "lts.properties";
     private static final Set<String> REQUIRED_OPTIONS
             = new LinkedHashSet<>(List.of("--version", "--tar", "--zip", 
"--output", "--latest"));
-    private static final Set<String> OPTIONAL_OPTIONS = Set.of("--install-sh", 
"--install-ps1");
+    private static final Set<String> OPTIONAL_OPTIONS = Set.of("--install-sh", 
"--install-ps1", "--lts");
+    private static final String REBUILD_FLAG = "--rebuild-aliases";
+    private static final String CHECK_FLAG = "--check";
+    private static final Set<String> REBUILD_REQUIRED_OPTIONS = new 
LinkedHashSet<>(List.of("--output", "--lts-versions"));
     private static final Set<String> MANIFEST_KEYS
             = new LinkedHashSet<>(List.of("format", "version", "tar_sha256", 
"zip_sha256"));
     private static final String MANIFEST_FORMAT = "1";
@@ -99,6 +128,14 @@ public class WebsiteManifestGenerator {
     }
 
     private static void run(String[] args) throws IOException {
+        if (Arrays.asList(args).contains(REBUILD_FLAG)) {
+            rebuildAliases(args);
+        } else {
+            publish(args);
+        }
+    }
+
+    private static void publish(String[] args) throws IOException {
         Map<String, String> options = parseArgs(args);
 
         String version = options.get("--version");
@@ -132,15 +169,8 @@ public class WebsiteManifestGenerator {
             }
         }
 
-        String latestValue = options.get("--latest");
-        boolean latest;
-        if ("true".equals(latestValue)) {
-            latest = true;
-        } else if ("false".equals(latestValue)) {
-            latest = false;
-        } else {
-            throw new UsageException("--latest must be 'true' or 'false' (got 
'" + latestValue + "').");
-        }
+        boolean latest = parseBoolean("--latest", options.get("--latest"));
+        boolean lts = options.containsKey("--lts") && parseBoolean("--lts", 
options.get("--lts"));
 
         Path output = Paths.get(options.get("--output"));
 
@@ -149,11 +179,29 @@ public class WebsiteManifestGenerator {
         byte[] manifest = renderManifest(version, tarSha256, zipSha256);
 
         Path releasesDir = output.resolve("releases");
+        if (!latest) {
+            rejectNewHighestWithoutLatest(releasesDir, version);
+        }
         Files.createDirectories(releasesDir);
         writeVersionManifest(releasesDir.resolve(version + ".properties"), 
manifest, version);
 
+        // Validate every alias before touching any of them, so a rejected 
alias (for example latest
+        // moving backward) does not leave the others half-updated. The 
immutable version manifest
+        // above is still written, as before.
+        Map<Path, byte[]> aliasUpdates = new LinkedHashMap<>();
+        // An older release (a late patch on the same line, or a patch on an 
older LTS line) leaves the
+        // X.Y and lts aliases where they are. latest is only requested for a 
new release, so asking
+        // to move it backward is an error.
+        planForwardOnlyAlias(releasesDir.resolve(lineOf(version) + 
".properties"), manifest, version, false,
+                aliasUpdates);
+        if (lts) {
+            planForwardOnlyAlias(releasesDir.resolve(LTS_FILE), manifest, 
version, false, aliasUpdates);
+        }
         if (latest) {
-            writeLatestManifest(releasesDir.resolve("latest.properties"), 
manifest, version);
+            planForwardOnlyAlias(releasesDir.resolve(LATEST_FILE), manifest, 
version, true, aliasUpdates);
+        }
+        for (Map.Entry<Path, byte[]> update : aliasUpdates.entrySet()) {
+            atomicWrite(update.getKey(), update.getValue());
         }
 
         if (installSh != null && installPs1 != null) {
@@ -161,6 +209,40 @@ public class WebsiteManifestGenerator {
         }
     }
 
+    /**
+     * {@code --rebuild-aliases --check} expects {@code latest.properties} to 
match the highest {@code X.Y.Z} manifest.
+     * Publishing a version higher than every existing manifest without {@code 
--latest true} would leave
+     * {@code latest} behind and fail that check after the manifests were 
written, so refuse it before writing anything.
+     * An empty (or missing) releases directory, such as a fresh staging 
build, has nothing to compare with.
+     */
+    private static void rejectNewHighestWithoutLatest(Path releasesDir, String 
version) throws IOException {
+        if (!Files.isDirectory(releasesDir)) {
+            return;
+        }
+        String highest = null;
+        try (Stream<Path> files = Files.list(releasesDir)) {
+            for (Path file : files.toList()) {
+                Matcher m = 
VERSION_FILE_PATTERN.matcher(file.getFileName().toString());
+                if (m.matches() && (highest == null || 
compareSemver(m.group(1), highest) > 0)) {
+                    highest = m.group(1);
+                }
+            }
+        }
+        if (highest != null && compareSemver(version, highest) > 0) {
+            throw new UsageException(version + " is higher than every 
published release (highest is " + highest
+                                     + "), so latest.properties must move to 
it - pass --latest true.");
+        }
+    }
+
+    private static boolean parseBoolean(String option, String value) {
+        if ("true".equals(value)) {
+            return true;
+        } else if ("false".equals(value)) {
+            return false;
+        }
+        throw new UsageException(option + " must be 'true' or 'false' (got '" 
+ value + "').");
+    }
+
     private static Map<String, String> parseArgs(String[] args) {
         Map<String, String> options = new LinkedHashMap<>();
         int i = 0;
@@ -207,33 +289,199 @@ public class WebsiteManifestGenerator {
         atomicWrite(versionFile, manifest);
     }
 
-    private static void writeLatestManifest(Path latestFile, byte[] manifest, 
String version) throws IOException {
-        if (!Files.exists(latestFile)) {
-            atomicWrite(latestFile, manifest);
+    /**
+     * Checks whether {@code aliasFile} moves to {@code version} and, if it 
needs to change, records the new content in
+     * {@code updates}. It never moves backward: an older {@code version} is 
an error when {@code failIfOlder} is set,
+     * and leaves the alias unchanged otherwise. Different checksums for the 
version it already points at are always an
+     * error.
+     */
+    private static void planForwardOnlyAlias(
+            Path aliasFile, byte[] manifest, String version, boolean 
failIfOlder, Map<Path, byte[]> updates)
+            throws IOException {
+        String aliasName = aliasFile.getFileName().toString();
+        if (!Files.exists(aliasFile)) {
+            updates.put(aliasFile, manifest);
             return;
         }
 
-        byte[] existing = Files.readAllBytes(latestFile);
+        byte[] existing = Files.readAllBytes(aliasFile);
         if (Arrays.equals(existing, manifest)) {
             return;
         }
 
-        Map<String, String> existingFields = parseStrictManifest(existing, 
latestFile);
+        Map<String, String> existingFields = parseStrictManifest(existing, 
aliasFile);
         String existingVersion = existingFields.get("version");
+        if (LINE_ALIAS_FILE_PATTERN.matcher(aliasName).matches()
+                && !(lineOf(existingVersion) + 
".properties").equals(aliasName)) {
+            throw new ConflictException(aliasName + " points at version " + 
existingVersion
+                                         + ", which is not on that release 
line - refusing to update it.");
+        }
         int cmp = compareSemver(version, existingVersion);
-        if (cmp < 0) {
+        if (cmp < 0 && !failIfOlder) {
+            System.out.println(aliasName + " stays at " + existingVersion + " 
(newer than " + version + ").");
+            return;
+        } else if (cmp < 0) {
             throw new ConflictException(
-                    "latest.properties already points at a newer version (" + 
existingVersion + "); refusing to"
+                    aliasName + " already points at a newer version (" + 
existingVersion + "); refusing to"
                                          + " move backward to " + version + 
".");
         } else if (cmp == 0) {
-            throw new ConflictException("latest.properties already has 
different checksums for version " + version
+            throw new ConflictException(aliasName + " already has different 
checksums for version " + version
                                          + " - refusing to overwrite.");
         }
-        atomicWrite(latestFile, manifest);
+        updates.put(aliasFile, manifest);
+    }
+
+    /**
+     * Regenerates {@code latest}, {@code lts} and every {@code X.Y} alias 
from the immutable {@code X.Y.Z.properties}
+     * files. Unlike publishing, this is not forward-only: the immutable 
manifests and the LTS list are the source of
+     * truth. With {@code --check} nothing is written and any drift fails with 
status 1.
+     */
+    private static void rebuildAliases(String[] args) throws IOException {
+        Map<String, String> options = new LinkedHashMap<>();
+        boolean check = false;
+        int i = 0;
+        while (i < args.length) {
+            String key = args[i];
+            if (REBUILD_FLAG.equals(key) || CHECK_FLAG.equals(key)) {
+                if (CHECK_FLAG.equals(key)) {
+                    check = true;
+                }
+                i++;
+                continue;
+            }
+            if (!REBUILD_REQUIRED_OPTIONS.contains(key)) {
+                throw new UsageException("unknown option '" + key + "' for " + 
REBUILD_FLAG + ".");
+            }
+            if (i + 1 >= args.length) {
+                throw new UsageException("option '" + key + "' requires a 
value.");
+            }
+            if (options.containsKey(key)) {
+                throw new UsageException("option '" + key + "' was specified 
more than once.");
+            }
+            options.put(key, args[i + 1]);
+            i += 2;
+        }
+        for (String required : REBUILD_REQUIRED_OPTIONS) {
+            if (!options.containsKey(required)) {
+                throw new UsageException("missing required option '" + 
required + "'.");
+            }
+        }
+
+        Set<String> ltsVersions = new LinkedHashSet<>();
+        for (String entry : options.get("--lts-versions").split(",", -1)) {
+            String v = entry.trim();
+            if (v.isEmpty()) {
+                continue;
+            }
+            if (!VERSION_PATTERN.matcher(v).matches()) {
+                throw new UsageException("'" + v + "' in --lts-versions is not 
a valid release version (expected X.Y.Z).");
+            }
+            ltsVersions.add(v);
+        }
+
+        Path releasesDir = 
Paths.get(options.get("--output")).resolve("releases");
+        if (!Files.isDirectory(releasesDir)) {
+            throw new UsageException("releases directory not found: " + 
releasesDir);
+        }
+
+        // Read every immutable manifest and every existing alias.
+        Map<String, byte[]> versions = new 
TreeMap<>(WebsiteManifestGenerator::compareSemver);
+        Set<String> existingAliases = new TreeSet<>();
+        try (Stream<Path> files = Files.list(releasesDir)) {
+            for (Path file : files.sorted().toList()) {
+                String name = file.getFileName().toString();
+                Matcher m = VERSION_FILE_PATTERN.matcher(name);
+                if (m.matches()) {
+                    byte[] bytes = Files.readAllBytes(file);
+                    String declared = parseStrictManifest(bytes, 
file).get("version");
+                    if (!declared.equals(m.group(1))) {
+                        throw new ConflictException(file + " declares version 
" + declared + ", expected " + m.group(1) + ".");
+                    }
+                    versions.put(declared, bytes);
+                } else if (LATEST_FILE.equals(name) || LTS_FILE.equals(name)
+                        || LINE_ALIAS_FILE_PATTERN.matcher(name).matches()) {
+                    existingAliases.add(name);
+                }
+            }
+        }
+        if (versions.isEmpty()) {
+            throw new ConflictException("no X.Y.Z.properties manifests found 
in " + releasesDir + ".");
+        }
+        for (String v : ltsVersions) {
+            if (!versions.containsKey(v)) {
+                throw new ConflictException("LTS version " + v + " has no 
manifest (" + releasesDir.resolve(v + ".properties")
+                                             + ") - refusing to pick another 
version.");
+            }
+        }
+
+        // Expected alias -> version. versions is sorted ascending, so later 
entries win.
+        Map<String, String> expected = new TreeMap<>();
+        for (String v : versions.keySet()) {
+            expected.put(lineOf(v) + ".properties", v);
+            expected.put(LATEST_FILE, v);
+            if (ltsVersions.contains(v)) {
+                expected.put(LTS_FILE, v);
+            }
+        }
+
+        List<String> drift = new ArrayList<>();
+        Map<Path, byte[]> writes = new LinkedHashMap<>();
+        for (Map.Entry<String, String> alias : expected.entrySet()) {
+            Path file = releasesDir.resolve(alias.getKey());
+            byte[] want = versions.get(alias.getValue());
+            if (!Files.exists(file)) {
+                drift.add(alias.getKey() + " is missing (expected " + 
alias.getValue() + ")");
+                writes.put(file, want);
+                continue;
+            }
+            byte[] have = Files.readAllBytes(file);
+            if (!Arrays.equals(have, want)) {
+                String described;
+                try {
+                    described = "points at " + parseStrictManifest(have, 
file).get("version");
+                } catch (ConflictException e) {
+                    described = "is malformed";
+                }
+                drift.add(alias.getKey() + " " + described + " (expected a 
copy of " + alias.getValue() + ".properties)");
+                writes.put(file, want);
+            }
+        }
+        List<String> unexpected = new ArrayList<>();
+        for (String name : existingAliases) {
+            if (!expected.containsKey(name)) {
+                unexpected.add(name);
+                drift.add(name + " is not expected (no matching release 
manifest or LTS version)");
+            }
+        }
+
+        if (check) {
+            if (!drift.isEmpty()) {
+                for (String d : drift) {
+                    System.err.println("drift: " + d);
+                }
+                throw new ConflictException(drift.size() + " alias manifest(s) 
in " + releasesDir
+                                             + " are out of date; regenerate 
them with " + REBUILD_FLAG + ".");
+            }
+            System.out.println("All " + expected.size() + " alias manifests in 
" + releasesDir + " are up to date.");
+            return;
+        }
+
+        if (!unexpected.isEmpty()) {
+            throw new ConflictException("unexpected alias manifest(s) " + 
unexpected + " in " + releasesDir
+                                         + " - refusing to delete published 
files; remove them manually.");
+        }
+        for (Map.Entry<Path, byte[]> write : writes.entrySet()) {
+            atomicWrite(write.getKey(), write.getValue());
+            System.out.println("wrote " + write.getKey().getFileName() + " -> "
+                               + 
expected.get(write.getKey().getFileName().toString()));
+        }
+        if (writes.isEmpty()) {
+            System.out.println("All " + expected.size() + " alias manifests in 
" + releasesDir + " are up to date.");
+        }
     }
 
     // install.sh/install.ps1 aren't versioned the way the release archive is 
- they always describe
-    // "how to install whatever is currently latest," so unlike 
writeVersionManifest/writeLatestManifest
+    // "how to install whatever is currently latest," so unlike 
writeVersionManifest/planForwardOnlyAlias
     // there's no immutability or monotonic-version invariant to enforce here: 
this file is simply
     // overwritten every release with the checksums of whatever 
install.sh/install.ps1 currently are.
     private static void writeInstallChecksums(Path websiteRoot, Path 
installSh, Path installPs1) throws IOException {
@@ -296,6 +544,11 @@ public class WebsiteManifestGenerator {
         return 0;
     }
 
+    private static String lineOf(String version) {
+        int[] parts = semverParts(version);
+        return parts[0] + "." + parts[1];
+    }
+
     private static int[] semverParts(String version) {
         Matcher m = VERSION_PATTERN.matcher(version);
         if (!m.matches()) {
diff --git a/scripts/camel-cli-aliases.sh b/scripts/camel-cli-aliases.sh
new file mode 100755
index 00000000..1b7b36ee
--- /dev/null
+++ b/scripts/camel-cli-aliases.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# camel-cli-aliases.sh - maintain the Camel CLI alias manifests in
+# static/camel-cli/releases (latest.properties, lts.properties, 
X.Y.properties).
+#
+# Usage:
+#   scripts/camel-cli-aliases.sh check            verify the aliases, write 
nothing (CI)
+#   scripts/camel-cli-aliases.sh rebuild          regenerate the aliases 
(backfill)
+#   scripts/camel-cli-aliases.sh kind <X.Y.Z>     print "lts" or "regular" for 
a release
+#
+# The LTS versions come from the release pages: a release is LTS when
+# content/releases/release-<version>.md has `kind: lts` in its front matter.
+# Every exact-version manifest (X.Y.Z.properties) must have a published
+# (`draft: false`, `category: camel`) release page. The alias rules themselves
+# live in the vendored scripts/WebsiteManifestGenerator.java 
(--rebuild-aliases).
+set -eu
+
+repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
+releases_dir="$repo_root/static/camel-cli/releases"
+pages_dir="$repo_root/content/releases"
+generator="$repo_root/scripts/WebsiteManifestGenerator.java"
+
+die() { echo "error: $*" >&2; exit 1; }
+usage() { echo "Usage: $0 check | rebuild | kind <X.Y.Z>" >&2; exit 2; }
+
+# Prints the value of a top-level key from the YAML front matter of a page,
+# without surrounding whitespace or quotes. Prints nothing if the key is 
absent.
+front_matter_value() {
+    awk -v key="$2" '
+        NR == 1 { if ($0 != "---") exit; next }
+        $0 == "---" { exit }
+        index($0, key ":") == 1 {
+            v = substr($0, length(key) + 2)
+            gsub(/^[ \t]+|[ \t\r]+$/, "", v)
+            gsub(/^["\047]|["\047]$/, "", v)
+            print v
+            exit
+        }
+    ' "$1"
+}
+
+# Prints "lts" or "regular" for a published Camel release; fails if its release
+# page is missing, is a draft, or is not a Camel release.
+release_kind() {
+    page="$pages_dir/release-$1.md"
+    [ -f "$page" ] || die "release page not found for $1: 
content/releases/release-$1.md"
+    [ "$(front_matter_value "$page" draft)" != "true" ] \
+        || die "release page for $1 is a draft: content/releases/release-$1.md"
+    category=$(front_matter_value "$page" category)
+    [ "$category" = "camel" ] \
+        || die "release page for $1 has category '$category', expected 
'camel': content/releases/release-$1.md"
+    if [ "$(front_matter_value "$page" kind)" = "lts" ]; then echo lts; else 
echo regular; fi
+}
+
+# Comma-separated LTS versions among the exact-version manifests.
+lts_versions() {
+    list=""
+    for manifest in "$releases_dir"/*.properties; do
+        v=$(basename "$manifest" .properties)
+        echo "$v" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || continue
+        kind=$(release_kind "$v") || exit 1
+        [ "$kind" = "lts" ] && list="$list${list:+,}$v"
+    done
+    echo "$list"
+}
+
+rebuild_aliases() {
+    command -v java >/dev/null 2>&1 || die "java (JDK 21+) is required to run 
$generator"
+    [ -f "$generator" ] || die "generator not found: $generator"
+    lts=$(lts_versions) || exit 1
+    java "$generator" --rebuild-aliases "$@" \
+        --output "$repo_root/static/camel-cli" \
+        --lts-versions "$lts"
+}
+
+[ $# -ge 1 ] || usage
+case $1 in
+    check) [ $# -eq 1 ] || usage; rebuild_aliases --check ;;
+    rebuild) [ $# -eq 1 ] || usage; rebuild_aliases ;;
+    kind)
+        [ $# -eq 2 ] || usage
+        echo "$2" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || die "version must 
be X.Y.Z: $2"
+        release_kind "$2"
+        ;;
+    *) usage ;;
+esac
diff --git a/static/camel-cli/releases/4.13.properties 
b/static/camel-cli/releases/4.13.properties
new file mode 100644
index 00000000..99626823
--- /dev/null
+++ b/static/camel-cli/releases/4.13.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.13.0
+tar_sha256=3d1a31b2ecb6d345fa491574e400c6099d68b5a7349b402c7ee4c01cc840645b
+zip_sha256=80c45a45e500998c21a6b111b9ddf80ea2543913b7955f1d92a3ec16affbdd72
diff --git a/static/camel-cli/releases/4.14.properties 
b/static/camel-cli/releases/4.14.properties
new file mode 100644
index 00000000..101d85b0
--- /dev/null
+++ b/static/camel-cli/releases/4.14.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.14.9
+tar_sha256=7424eb9f925262292a0b57bf47a7ebdc8e65a666cee92357a2e8938e1dfbae13
+zip_sha256=3c9258a146f3e9ba19749d1ec8c9a0e3de96baa9af12b8fc8448202942dc7c4b
diff --git a/static/camel-cli/releases/4.15.properties 
b/static/camel-cli/releases/4.15.properties
new file mode 100644
index 00000000..09424fd9
--- /dev/null
+++ b/static/camel-cli/releases/4.15.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.15.0
+tar_sha256=bc7d40e4ce18b8a195e1e6333e8ec147efe697a2500dd15840c6b9ca6abbf14a
+zip_sha256=38506074a7bb4f39cd48bbc3f9c3a25a2a93e9c0ed62962cd03e4fd53d1ac1aa
diff --git a/static/camel-cli/releases/4.16.properties 
b/static/camel-cli/releases/4.16.properties
new file mode 100644
index 00000000..fcaffbd8
--- /dev/null
+++ b/static/camel-cli/releases/4.16.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.16.0
+tar_sha256=19e784eee53213b18cb620e3b3888dcc1fc4d925c41b48846db7825d38b28653
+zip_sha256=1ea3368b629f60443b907e28a9f8eb7d57bc0ba5036e4cfbfd30f1a9fed79602
diff --git a/static/camel-cli/releases/4.17.properties 
b/static/camel-cli/releases/4.17.properties
new file mode 100644
index 00000000..080572f1
--- /dev/null
+++ b/static/camel-cli/releases/4.17.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.17.0
+tar_sha256=6aae94b30a33a04c2daed938333956f6d41615fc0edd9a27219dbe9665595f72
+zip_sha256=9d6bdd6552020d6aac3dea83de32765e03e2aa5b9cf278598c1575d2416258c6
diff --git a/static/camel-cli/releases/4.18.properties 
b/static/camel-cli/releases/4.18.properties
new file mode 100644
index 00000000..c3d841a7
--- /dev/null
+++ b/static/camel-cli/releases/4.18.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.18.5
+tar_sha256=69416c52cf97a13866f90dcb96e0fae8bbecf12946d51268ab713b23a28d8d2f
+zip_sha256=be224addb60e47896cfd79e119b878d41cfe1641a6d98b883f8805e71b447366
diff --git a/static/camel-cli/releases/4.19.properties 
b/static/camel-cli/releases/4.19.properties
new file mode 100644
index 00000000..28b8370d
--- /dev/null
+++ b/static/camel-cli/releases/4.19.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.19.0
+tar_sha256=21b198c34b0028f3e0241e14f81af6e840a10e6ba0f2fb28f16055270a2c35d3
+zip_sha256=2eb1a146a9b3c6f9fd10a5493263296a428ad2ada11280df779c36524b510a2c
diff --git a/static/camel-cli/releases/4.20.properties 
b/static/camel-cli/releases/4.20.properties
new file mode 100644
index 00000000..f66452aa
--- /dev/null
+++ b/static/camel-cli/releases/4.20.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.20.0
+tar_sha256=5f2332580392f9c77b2489c728e3052848fe5b8c201ae701fe2bf37b83351782
+zip_sha256=086a7b16d0a9ffc951f2d5f4dcb8c09a15a08da73ced6e002d56279664667de6
diff --git a/static/camel-cli/releases/4.21.properties 
b/static/camel-cli/releases/4.21.properties
new file mode 100644
index 00000000..305bcf3d
--- /dev/null
+++ b/static/camel-cli/releases/4.21.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.21.0
+tar_sha256=24c247b9418292bce794b8ad2333691263c3d0de9a4a0fbc767942669b71984c
+zip_sha256=949d39606fc5d4f246a9ea5166f1f8597e75229332653f48b667da73fb87d80e
diff --git a/static/camel-cli/releases/4.22.properties 
b/static/camel-cli/releases/4.22.properties
new file mode 100644
index 00000000..d08aa63c
--- /dev/null
+++ b/static/camel-cli/releases/4.22.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.22.2
+tar_sha256=df23414b5a817f1dfe63b6618b0afa47eb01e272f89d90f41a826f28f75c5e1e
+zip_sha256=434dd3a0ef30e87d540c00047a13e379e1dba33dd920b4c2b7affc3bbb999fc1
diff --git a/static/camel-cli/releases/4.23.properties 
b/static/camel-cli/releases/4.23.properties
new file mode 100644
index 00000000..8f7c638e
--- /dev/null
+++ b/static/camel-cli/releases/4.23.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.23.0
+tar_sha256=d81b341141f29323932be6a1010027f4968634b9fded4c6eac6750920fa387af
+zip_sha256=2fa53a229fb5ef8e819cbc24a015d75a16105c9c0b7897742d13adea1695c8e5
diff --git a/static/camel-cli/releases/lts.properties 
b/static/camel-cli/releases/lts.properties
new file mode 100644
index 00000000..d08aa63c
--- /dev/null
+++ b/static/camel-cli/releases/lts.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+format=1
+version=4.22.2
+tar_sha256=df23414b5a817f1dfe63b6618b0afa47eb01e272f89d90f41a826f28f75c5e1e
+zip_sha256=434dd3a0ef30e87d540c00047a13e379e1dba33dd920b4c2b7affc3bbb999fc1
diff --git a/update-camel-cli-manifest.sh b/update-camel-cli-manifest.sh
index 5b78a0a8..cc6d93a9 100755
--- a/update-camel-cli-manifest.sh
+++ b/update-camel-cli-manifest.sh
@@ -12,6 +12,18 @@
 #    delegates to scripts/WebsiteManifestGenerator.java (a vendored copy of the
 #    apache/camel tool) to compute SHA-256 and write the release manifests.
 # 3. With --latest, updates latest.properties, but only if <version> is newer.
+#    Always advances <X.Y>.properties (the newest patch of the release line) 
and,
+#    when content/releases/release-<version>.md has `kind: lts`, 
lts.properties.
+#    Both only move forward: a patch on an older LTS line leaves lts.properties
+#    alone. The release page must exist and must not be a draft.
+# 4. Checks that every alias matches the exact-version manifests
+#    (scripts/camel-cli-aliases.sh check, also run in CI).
+#
+# For local work, take the sources from a local apache/camel checkout instead 
of
+# apache/camel main:
+#   CAMEL_GENERATOR_SRC=/path/to/WebsiteManifestGenerator.java
+#   CAMEL_INSTALL_SRC_DIR=/path/to/dir/with/install.sh+install.ps1
+#     (e.g. dsl/camel-jbang/camel-launcher/src/install)
 #
 # The manifest format (format=1, version, tar_sha256, zip_sha256) and all
 # immutability / forward-only rules live in the single Java implementation;
@@ -45,8 +57,19 @@ done
 
 echo "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || die "version must be 
X.Y.Z: $version"
 
-command -v java >/dev/null 2>&1 || die "java (JDK 17+) is required to run 
$generator"
+command -v java >/dev/null 2>&1 || die "java (JDK 21+) is required to run 
$generator"
 [ -f "$generator" ] || die "generator not found: $generator"
+if [ -n "${CAMEL_GENERATOR_SRC:-}" ]; then
+    [ -f "$CAMEL_GENERATOR_SRC" ] || die "CAMEL_GENERATOR_SRC not found: 
$CAMEL_GENERATOR_SRC"
+fi
+if [ -n "${CAMEL_INSTALL_SRC_DIR:-}" ]; then
+    [ -d "$CAMEL_INSTALL_SRC_DIR" ] || die "CAMEL_INSTALL_SRC_DIR not found: 
$CAMEL_INSTALL_SRC_DIR"
+fi
+
+# Fails if the release page is missing, a draft, or not a Camel release.
+kind=$("$repo_root/scripts/camel-cli-aliases.sh" kind "$version") || exit 1
+if [ "$kind" = "lts" ]; then lts=true; else lts=false; fi
+echo "Release $version is $kind (content/releases/release-$version.md)"
 
 tmp=$(mktemp -d)
 trap 'rm -rf "$tmp"' EXIT INT TERM
@@ -54,16 +77,28 @@ trap 'rm -rf "$tmp"' EXIT INT TERM
 # --- 1. Vendor the installer scripts and manifest generator from apache/camel 
main ------
 mkdir -p "$static_dir"
 for script in install.sh install.ps1; do
-    echo "Fetching $script from apache/camel main"
-    curl -fsSL "$MAIN_RAW/$script" -o "$tmp/$script" \
-        || die "could not download $script from $MAIN_RAW/$script"
+    if [ -n "${CAMEL_INSTALL_SRC_DIR:-}" ]; then
+        echo "Copying $script from $CAMEL_INSTALL_SRC_DIR"
+        cp "$CAMEL_INSTALL_SRC_DIR/$script" "$tmp/$script" \
+            || die "could not copy $script from $CAMEL_INSTALL_SRC_DIR"
+    else
+        echo "Fetching $script from apache/camel main"
+        curl -fsSL "$MAIN_RAW/$script" -o "$tmp/$script" \
+            || die "could not download $script from $MAIN_RAW/$script"
+    fi
     mv "$tmp/$script" "$static_dir/$script"
     echo "  wrote static/$script"
 done
 
-echo "Fetching WebsiteManifestGenerator.java from apache/camel main"
-curl -fsSL "$GENERATOR_RAW_URL" -o "$tmp/WebsiteManifestGenerator.java" \
-    || die "could not download WebsiteManifestGenerator.java from 
$GENERATOR_RAW_URL"
+if [ -n "${CAMEL_GENERATOR_SRC:-}" ]; then
+    echo "Copying WebsiteManifestGenerator.java from $CAMEL_GENERATOR_SRC"
+    cp "$CAMEL_GENERATOR_SRC" "$tmp/WebsiteManifestGenerator.java" \
+        || die "could not copy $CAMEL_GENERATOR_SRC"
+else
+    echo "Fetching WebsiteManifestGenerator.java from apache/camel main"
+    curl -fsSL "$GENERATOR_RAW_URL" -o "$tmp/WebsiteManifestGenerator.java" \
+        || die "could not download WebsiteManifestGenerator.java from 
$GENERATOR_RAW_URL"
+fi
 {
     printf '// VENDORED COPY - do not edit here.\n'
     printf '// Source of truth: apache/camel\n'
@@ -86,7 +121,9 @@ curl -fsSL "$zip_url" -o "$tmp/archive.zip" || die "could 
not download $zip_url"
 
 # --- 3. Write the manifests via the vendored Java generator ------------------
 # The generator computes checksums, writes 
static/camel-cli/releases/<version>.properties
-# (immutable) and, when --latest is given, advances latest.properties 
forward-only.
+# (immutable), advances <X.Y>.properties forward-only, with --lts true advances
+# lts.properties forward-only and, when --latest is given, advances 
latest.properties
+# forward-only.
 if [ "$update_latest" -eq 1 ]; then latest=true; else latest=false; fi
 echo "Generating manifests via scripts/WebsiteManifestGenerator.java"
 java "$generator" \
@@ -95,8 +132,12 @@ java "$generator" \
     --zip "$tmp/archive.zip" \
     --output "$static_dir/camel-cli" \
     --latest "$latest" \
+    --lts "$lts" \
     --install-sh "$static_dir/install.sh" \
     --install-ps1 "$static_dir/install.ps1" \
     || die "WebsiteManifestGenerator failed"
 
+# --- 4. Check every alias against the exact-version manifests ---------------
+"$repo_root/scripts/camel-cli-aliases.sh" check || die "alias manifests are 
inconsistent"
+
 echo "Done."

Reply via email to