Copilot commented on code in PR #4019:
URL: 
https://github.com/apache/incubator-kie-tools/pull/4019#discussion_r4492234000


##########
.ci/jenkins/Jenkinsfile.103xplus.release-candidate:
##########
@@ -0,0 +1,377 @@
+/*
+ * 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.
+ */
+
+@Library('jenkins-pipeline-shared-libraries')_
+
+// PHASE 1: Create Release Candidate for Apache Voting
+// This job builds artifacts, signs them, and publishes to Apache SVN for 
community vote
+// Does NOT publish to npm, VSCode Marketplace, or Chrome Web Store
+//
+// NOTE: kogito-data-index-ephemeral is NOT included in this pipeline because 
the
+// packages/kogito-data-index-ephemeral-image package no longer exists in this 
repo.
+// Its presence in older Jenkinsfiles (Jenkinsfile.release-candidate RUNNERS 
list and
+// release-jobs/Jenkinsfile.kogito-data-index-ephemeral-image) is a historical 
artifact
+// from pre-10.3 automation and can be cleaned up in a follow-up PR.
+
+pipeline {
+    agent {
+        docker {
+            image 'docker.io/apache/incubator-kie-tools-ci-build:main'
+            args '--shm-size=2g --privileged --group-add docker'
+            label util.avoidFaultyNodes()
+        }
+    }
+
+    options {
+        timeout(time: 480, unit: 'MINUTES')
+    }
+
+    parameters {
+        string(
+            name: 'RELEASE_VERSION',
+            description: 'MANDATORY. Exact release version, e.g. 10.3.0',
+            defaultValue: '0.0.0'
+        )
+        string(
+            name: 'TAG_NAME',
+            description: 'MANDATORY. RC tag name, e.g. 10.3.0-rc1 (must start 
with RELEASE_VERSION and be a new tag)',
+            defaultValue: '0.0.0-rc1'
+        )
+        booleanParam(
+            name: 'CREATE_STREAM_BRANCH',
+            description: 'Branch cut, part 1: create branch X.Y.x (derived 
from RELEASE_VERSION) from main and push it. Only for the first RC of a new 
minor (e.g. 10.3.0-rc1). Fails if X.Y.x already exists',
+            defaultValue: false
+        )
+        booleanParam(
+            name: 'UPDATE_STREAM_VERSION',
+            description: 'Branch cut, part 2: set the development version in 
all files on X.Y.x (project version X.Y.999, kogito maven X.Y.999-SNAPSHOT, 
stream name X.Y.x), commit and push. Use together with CREATE_STREAM_BRANCH for 
a normal branch cut, or alone if X.Y.x was already created',
+            defaultValue: false
+        )
+    }
+
+    environment {
+        KIE_TOOLS_BUILD__runLinters = 'false'
+        KIE_TOOLS_BUILD__runTests = 'false'
+        KIE_TOOLS_BUILD__runEndToEndTests = 'false'
+        KIE_TOOLS_BUILD__buildContainerImages = 'true'
+
+        CHROME_EXTENSION__routerTargetOrigin = 'https://apache.github.io'
+        CHROME_EXTENSION__routerRelativePath = 
"incubator-kie-kogito-online/chrome-extension/${params.RELEASE_VERSION}"
+        CHROME_EXTENSION__manifestFile = 'manifest.prod.json'
+        CHROME_EXTENSION__onlineEditorUrl = 
'https://apache.github.io/incubator-kie-kogito-online'
+
+        ONLINE_EDITOR__corsProxyUrl = 
'https://cors-proxy-kie-sandbox.rhba-0ad6762cc85bcef5745bb684498c2436-0000.us-south.containers.appdomain.cloud'
+        ONLINE_EDITOR__buildInfo = "${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__extendedServicesCompatibleVersion = 
"${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentBaseImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentBaseImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentBaseImageName = 
'incubator-kie-sandbox-dev-deployment-base'
+        ONLINE_EDITOR__devDeploymentBaseImageTag = "${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageName = 
'incubator-kie-sandbox-dev-deployment-dmn-form-webapp'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag = 
"${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName = 
'incubator-kie-sandbox-dev-deployment-quarkus-blank-app'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag = 
"${params.RELEASE_VERSION}"
+    }
+
+    stages {
+        stage('Load local shared scripts') {
+            steps {
+                script {
+                    pipelineVars = load 
'.ci/jenkins/shared-scripts/pipelineVars.groovy'
+                    buildUtils = load 
'.ci/jenkins/shared-scripts/buildUtils.groovy'
+                    githubUtils = load 
'.ci/jenkins/shared-scripts/githubUtils.groovy'
+                    releaseUtils = load 
'.ci/jenkins/shared-scripts/releaseUtils.groovy'
+                }
+            }
+        }
+
+        // The stream branch X.Y.x is derived from RELEASE_VERSION. A branch 
cut starts from main;
+        // every other run (rc2, patch releases) builds from the existing 
X.Y.x.
+        stage('Resolve release ref') {
+            steps {
+                script {
+                    def m = (params.RELEASE_VERSION =~ /^(\d+)\.(\d+)\..+$/)
+                    if (!m.matches()) {
+                        error("Cannot derive stream branch from 
RELEASE_VERSION '${params.RELEASE_VERSION}' (expected e.g. 10.3.0)")
+                    }
+                    streamBranch = "${m.group(1)}.${m.group(2)}.x"
+                    devVersion = "${m.group(1)}.${m.group(2)}.999"
+                    m = null
+
+                    if (!params.TAG_NAME.startsWith(params.RELEASE_VERSION)) {
+                        error("TAG_NAME '${params.TAG_NAME}' must start with 
RELEASE_VERSION '${params.RELEASE_VERSION}'")

Review Comment:
   `startsWith` accepts the final version itself as `TAG_NAME`, such as 
`10.3.0`. That makes this job push the official release tag before the vote, 
and the promote job subsequently cannot create that tag. Validate the complete 
RC tag format, including the `-rc` suffix and candidate number, before any 
branch or tag is pushed.



##########
.ci/jenkins/Jenkinsfile.103xplus.release-promote:
##########
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+@Library('jenkins-pipeline-shared-libraries')_
+
+// 10.3.x+ promote / tag-release pipeline for incubator-kie-tools.
+// Run after the IPMC vote passes to:
+//   1. Create and push the official release Git tag (e.g. 10.3.0) pointing to 
the approved RC commit.
+//   2. Create the GitHub Release for incubator-kie-tools (required before 
release-publish).
+
+pipeline {
+    agent {
+        label util.avoidFaultyNodes()
+    }
+
+    options {
+        timeout(time: 60, unit: 'MINUTES')
+    }
+
+    parameters {
+        string(
+            name: 'RELEASE_VERSION',
+            description: 'Release version (e.g., 10.3.0)',
+            defaultValue: ''
+        )
+        string(
+            name: 'BASE_REF',
+            description: 'Approved RC tag (e.g. 10.3.0-rc1) to tag from',
+            defaultValue: ''
+        )
+    }
+
+    stages {
+        stage('Validate parameters') {
+            steps {
+                script {
+                    if (params.RELEASE_VERSION == '') {
+                        error("RELEASE_VERSION is required (e.g. 10.3.0)")
+                    }
+                    if (params.BASE_REF == '') {
+                        error("BASE_REF (approved RC tag, e.g. 10.3.0-rc1) is 
required")
+                    }
+                    if 
(!params.BASE_REF.startsWith("${params.RELEASE_VERSION}-rc")) {
+                        error("BASE_REF '${params.BASE_REF}' must be an RC tag 
for RELEASE_VERSION '${params.RELEASE_VERSION}' (e.g. 
${params.RELEASE_VERSION}-rc1)")
+                    }
+                    currentBuild.displayName = "${params.RELEASE_VERSION} 
(${params.BASE_REF})"
+                }
+            }
+        }
+
+        stage('Load local shared scripts') {
+            steps {
+                script {
+                    pipelineVars = load 
'.ci/jenkins/shared-scripts/pipelineVars.groovy'
+                    githubUtils = load 
'.ci/jenkins/shared-scripts/githubUtils.groovy'
+                }
+            }
+        }
+
+        stage('Clean workspace') {
+            steps {
+                cleanWs(deleteDirs: true, disableDeferredWipeout: true)
+            }
+        }
+
+        stage('Checkout incubator-kie-tools') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        githubUtils.checkoutRepo(
+                            
"https://github.com/${pipelineVars.githubRepositorySlug}.git";,
+                            "${params.BASE_REF}",
+                            "${pipelineVars.asfGithubPushCredentialsId}"
+                        )
+                    }
+                }
+            }
+        }
+
+        stage('Tag final release') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        withCredentials([
+                            usernamePassword(credentialsId: 
"${pipelineVars.asfGithubPushCredentialsId}", usernameVariable: 'GITHUB_USER', 
passwordVariable: 'GITHUB_TOKEN')
+                        ]) {
+                            sh """#!/bin/bash -el
+                            set -euo pipefail
+                            git config user.email "[email protected]"
+                            git config user.name  "Apache KIE Release Bot"
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+
+                            # Fetch the approved RC tag
+                            git fetch origin 
"refs/tags/${params.BASE_REF}:refs/tags/${params.BASE_REF}"
+
+                            # Create and push the final release tag pointing 
to the RC commit
+                            git tag -a "${params.RELEASE_VERSION}" 
"${params.BASE_REF}" -m "Release ${params.RELEASE_VERSION}"
+                            git push origin "${params.RELEASE_VERSION}"

Review Comment:
   If pushing the final tag succeeds but GitHub Release creation subsequently 
fails, rerunning this job stops here with an already-existing-tag error and 
never reaches the failed stage. The pushed tag persists despite workspace 
cleanup. Make promotion retry-safe: accept an existing final tag only when its 
dereferenced commit matches the approved RC, and create the GitHub Release only 
if missing rather than deleting an existing release and its assets.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";

Review Comment:
   The production defaults here omit the accelerator settings, which are 
exported only after the Sandbox image has been built. Without external 
overrides, that image embeds the development repository URL and `main` ref, and 
the later Pages build does not repair it. Initialize the accelerator defaults 
here, before any build, so the image uses the released accelerator.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";
+
+ARTIFACTS_DIR="${REPO_ROOT}/release-artifacts"
+
+echo "=========================================="
+echo "KIE Tools Release"
+echo "Version:    ${VERSION}"
+echo "RC mode:    ${RC_MODE}"
+echo "Publish:    ${PUBLISH}"
+echo "Skip build: ${SKIP_BUILD}"
+echo "Registry:   ${REGISTRY}"
+echo "=========================================="
+
+cd "${REPO_ROOT}"
+if [[ "${SKIP_BUILD}" == "false" ]]; then
+    echo ""
+    echo "--- Bumping workspace version to ${VERSION} ---"
+    pnpm update-version-to "${VERSION}"
+    pnpm update-kogito-version-to --maven "${VERSION}"
+    pnpm update-stream-name-to "${VERSION}"
+fi
+
+# ---------------------------------------------------------------------------
+# Helper: sign and upload a single asset to a GitHub Release
+# ---------------------------------------------------------------------------
+upload_github_asset() {
+    local file="$1"
+    local asset_name="$2"
+    local content_type="${3:-application/octet-stream}"
+    if [[ ! -f "${file}" ]]; then
+        echo "  SKIP  ${asset_name} (file not found)"
+        return
+    fi
+    echo "  UPLOAD  ${asset_name}"
+    curl -sSf \
+        -H "Authorization: token ${GITHUB_TOKEN}" \
+        -H "Content-Type: ${content_type}" \
+        --data-binary "@${file}" \
+        "${UPLOAD_URL}?name=${asset_name}"
+}
+
+# ---------------------------------------------------------------------------
+# 1. NPM packages
+# ---------------------------------------------------------------------------
+release_npm_packages() {
+    echo ""
+    echo "--- NPM packages ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building public NPM packages..."
+        local filter
+        filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)..."; fi')
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        local tmp_dir="${ARTIFACTS_DIR}/npm-packages-tmp"
+        local 
zip_file="apache-kie-${VERSION}-incubating-tools-npm-packages.zip"
+        mkdir -p "${tmp_dir}" "${ARTIFACTS_DIR}"
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c "pnpm pack --pack-destination 
${tmp_dir}"
+        (cd "${tmp_dir}" && zip -r "${ARTIFACTS_DIR}/${zip_file}" .)
+        rm -rf "${tmp_dir}"
+        echo "RC artifact: ${ARTIFACTS_DIR}/${zip_file}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${NPM_TOKEN:-}" ]]; then
+            echo "ERROR: NPM_TOKEN is required for publishing npm packages"
+            return 1
+        fi
+        local npmrc_tmp
+        npmrc_tmp=$(mktemp)
+        trap "rm -f '${npmrc_tmp}'" EXIT
+        echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${npmrc_tmp}"
+        export NPM_CONFIG_USERCONFIG="${npmrc_tmp}"
+
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c '
+            PKG_NAME=$(jq -r ".name" package.json)
+            if ! npm view ${PKG_NAME}@'"${VERSION}"' name --userconfig 
"'"${npmrc_tmp}"'" &>/dev/null; then
+                echo "Publishing ${PKG_NAME}@'"${VERSION}"'"
+                pnpm publish --no-git-checks --access public --userconfig 
"'"${npmrc_tmp}"'"
+            else
+                echo "Skipping ${PKG_NAME}@'"${VERSION}"' (already published)"
+            fi
+        '
+        rm -f "${npmrc_tmp}"
+        trap - EXIT
+        echo "NPM packages published."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 2. Chrome extensions
+# ---------------------------------------------------------------------------
+release_chrome_extensions() {
+    echo ""
+    echo "--- Chrome extensions ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building Chrome extensions..."
+        pnpm -F "chrome-extension-pack-kogito-kie-editors..." build:prod
+    fi
+
+    local kie_editors_zip
+    kie_editors_zip=$(find 
"packages/chrome-extension-pack-kogito-kie-editors/dist" \
+        -maxdepth 1 -name "chrome_extension_kogito_kie_editors_*.zip" 
2>/dev/null | head -1 || true)
+    if [[ -z "${kie_editors_zip}" ]]; then
+        echo "ERROR: chrome_extension_kogito_kie_editors_*.zip not found — was 
the build successful?"
+        return 1
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        cp "${kie_editors_zip}" \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension.zip"
+
+        local tmp_kie="${ARTIFACTS_DIR}/kie-editors-tmp"
+        mkdir -p "${tmp_kie}"
+        [[ -d "packages/chrome-extension-pack-kogito-kie-editors/dist/fonts" 
]] && cp -r packages/chrome-extension-pack-kogito-kie-editors/dist/fonts 
"${tmp_kie}/"
+        cp packages/chrome-extension-pack-kogito-kie-editors/dist/*-envelope.* 
"${tmp_kie}/" 2>/dev/null || true
+        (cd "${tmp_kie}" && zip -r \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension-editors.zip"
 .)
+        rm -rf "${tmp_kie}"
+        echo "RC artifacts created in: ${ARTIFACTS_DIR}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${CHROME_CLIENT_ID:-}" || -z "${CHROME_CLIENT_SECRET:-}" || 
-z "${CHROME_REFRESH_TOKEN:-}" ]]; then
+            echo "ERROR: CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, and 
CHROME_REFRESH_TOKEN are required"
+            return 1
+        fi
+
+        local token_res
+        token_res=$(curl -sSf -X POST "https://oauth2.googleapis.com/token"; \
+            -d "client_id=${CHROME_CLIENT_ID}" \
+            -d "client_secret=${CHROME_CLIENT_SECRET}" \
+            -d "refresh_token=${CHROME_REFRESH_TOKEN}" \
+            -d "grant_type=refresh_token")
+        local access_token
+        access_token=$(echo "${token_res}" | jq -r '.access_token // empty')
+
+        if [[ -z "${access_token}" ]]; then
+            echo "ERROR: Failed to obtain Chrome Web Store OAuth token: 
${token_res}"
+            return 1
+        fi
+
+        _chrome_upload() {
+            local ext_id="$1" zip="$2"
+            local res state
+            res=$(curl -sSf -X PUT \
+                
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/${ext_id}"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -T "${zip}")
+            state=$(echo "${res}" | jq -r '.uploadState // empty')
+            if [[ "${state}" != "SUCCESS" ]]; then
+                echo "ERROR: Chrome Web Store upload failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+        _chrome_publish() {
+            local ext_id="$1"
+            local res pub_status
+            res=$(curl -sSf -X POST \
+                
"https://www.googleapis.com/chromewebstore/v1.1/items/${ext_id}/publish"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -H "Content-Length: 0")
+            pub_status=$(echo "${res}" | jq -r '.status[0] // .status // 
empty')
+            if [[ "${pub_status}" != "OK" && "${pub_status}" != 
"PUBLISHED_WITH_FRICTION_WARNING" ]]; then
+                echo "ERROR: Chrome Web Store publish failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+
+        if [[ -n "${CHROME_KIE_EDITORS_EXTENSION_ID:-}" ]]; then
+            _chrome_upload "${CHROME_KIE_EDITORS_EXTENSION_ID}" 
"${kie_editors_zip}"
+            _chrome_publish "${CHROME_KIE_EDITORS_EXTENSION_ID}"
+            echo "Chrome extension published."
+        else
+            echo "WARN: CHROME_KIE_EDITORS_EXTENSION_ID not set — skipping 
Chrome publish"
+        fi
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 3. VSCode extensions
+# ---------------------------------------------------------------------------
+release_vscode() {
+    echo ""
+    echo "--- VSCode extensions ---"
+
+    declare -A EXTENSIONS=(
+        ["bpmn-vscode-extension"]="bpmn-vscode-extension"
+        ["dmn-vscode-extension"]="dmn-vscode-extension"
+        ["drl-vscode-extension"]="drl-vscode-extension"
+        ["pmml-vscode-extension"]="pmml-vscode-extension"
+        ["vscode-extension-kogito-bundle"]="kogito-bundle-vscode-extension"
+        
["vscode-extension-kie-ba-bundle"]="business-automation-bundle-vscode-extension"
+        
["extended-services-vscode-extension"]="extended-services-vscode-extension"
+    )
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        local filter=""
+        for ext in "${!EXTENSIONS[@]}"; do filter="${filter} -F ${ext}..."; 
done
+        echo "Building VSCode extensions..."
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        for ext in "${!EXTENSIONS[@]}"; do
+            local suffix="${EXTENSIONS[$ext]}"
+            local vsix
+            vsix=$(find "packages/${ext}" -maxdepth 2 -name "*.vsix" 
2>/dev/null | head -1 || true)
+            if [[ -n "${vsix}" ]]; then
+                local 
dest="${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-${suffix}.vsix"
+                echo "  COPY  $(basename "${dest}")"
+                cp "${vsix}" "${dest}"
+            else
+                echo "  SKIP  ${ext} (no .vsix found)"
+            fi
+        done
+        echo "VSIX files in: ${ARTIFACTS_DIR}/"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${VSCE_PAT:-}" ]]; then
+            echo "ERROR: VSCE_PAT is required for publishing VSCode extensions"
+            return 1
+        fi
+        echo "Publishing to VSCode Marketplace..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                echo "  Publishing: ${vsix}"
+                npx vsce publish --packagePath "${vsix}" --pat "${VSCE_PAT}"
+            done
+        done
+        echo "VSCode extensions published."
+    fi
+
+    if [[ "${PUBLISH}" == "true" && -n "${UPLOAD_URL:-}" && -n 
"${GITHUB_TOKEN:-}" ]]; then
+        echo "Uploading .vsix files to GitHub Release..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                upload_github_asset "${vsix}" "$(basename "${vsix}")" 
"application/zip"
+            done
+        done
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 4. Container images
+# ---------------------------------------------------------------------------
+release_container_images() {
+    echo ""
+    echo "--- Container images ---"
+
+    declare -A IMAGES=(
+        ["cors-proxy"]="packages/cors-proxy-image:incubator-kie-cors-proxy"
+        
["sandbox-webapp"]="packages/kie-sandbox-webapp-image:incubator-kie-sandbox-webapp"
+        
["sandbox-extended-services"]="packages/kie-sandbox-extended-services-image:incubator-kie-sandbox-extended-services"
+        
["sandbox-dev-deployment-base"]="packages/dev-deployment-base-image:incubator-kie-sandbox-dev-deployment-base"
+        
["sandbox-dev-deployment-dmn-form-webapp"]="packages/dev-deployment-dmn-form-webapp-image:incubator-kie-sandbox-dev-deployment-dmn-form-webapp"
+        
["sandbox-dev-deployment-quarkus-blank-app"]="packages/dev-deployment-quarkus-blank-app-image:incubator-kie-sandbox-dev-deployment-quarkus-blank-app"
+        
["kogito-management-console"]="packages/kogito-management-console:incubator-kie-kogito-management-console"
+    )
+
+    export KIE_TOOLS_BUILD__buildContainerImages=true
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        if ! docker info &>/dev/null; then
+            echo "WARN: Docker is not running — skipping container image build"
+            return 0
+        fi
+        echo "Building container images..."
+        for artifact_name in "${!IMAGES[@]}"; do
+            local mapping="${IMAGES[$artifact_name]}"
+            local pkg_path="${mapping%%:*}"
+            if [[ ! -d "${pkg_path}" ]]; then
+                echo "ERROR: Image package directory ${pkg_path} not found"
+                return 1
+            fi
+            local pkg_name
+            pkg_name=$(node -p "require('./${pkg_path}/package.json').name" 
2>/dev/null || basename "${pkg_path}")
+            echo "  BUILD  ${artifact_name} (${pkg_name})"
+            pnpm --filter "${pkg_name}..." build:prod
+        done
+    else
+        if ! docker info &>/dev/null; then
+            echo "SKIP: Docker is not running — cannot save container image 
tarballs with --skip-build"
+            return 0
+        fi
+    fi
+
+    local output_dir="${ARTIFACTS_DIR}"
+    [[ "${RC_MODE}" == "true" ]] && mkdir -p "${output_dir}"
+
+    echo "Tagging images..."
+    for artifact_name in "${!IMAGES[@]}"; do
+        local mapping="${IMAGES[$artifact_name]}"
+        local pkg_path="${mapping%%:*}"
+        local image_name="${mapping##*:}"
+        if [[ ! -d "${pkg_path}" ]]; then
+            echo "ERROR: Image package directory ${pkg_path} not found"
+            return 1
+        fi
+        local target_image="${REGISTRY}/${image_name}:${VERSION}"
+
+        # Resolve built image from local docker daemon
+        local source_image=""
+        for candidate in \
+            "docker.io/apache/${image_name}:${VERSION}" \
+            "apache/${image_name}:${VERSION}" \
+            "${image_name}:${VERSION}" \
+            "docker.io/apache/${image_name}:latest" \
+            "apache/${image_name}:latest" \
+            "${image_name}:latest"; do
+            if docker image inspect "${candidate}" &>/dev/null; then
+                source_image="${candidate}"
+                break
+            fi
+        done
+
+        if [[ -n "${source_image}" ]]; then
+            docker tag "${source_image}" "${target_image}"
+        else
+            echo "  WARN  could not find local image for ${artifact_name} 
(${image_name})"
+        fi
+
+        if [[ "${RC_MODE}" == "true" ]]; then
+            if docker image inspect "${target_image}" &>/dev/null; then
+                local 
tarball="${output_dir}/apache-kie-${VERSION}-incubating-${artifact_name}-image.tar.gz"
+                echo "  SAVE  ${tarball}"
+                docker save "${target_image}" | gzip > "${tarball}"
+            else
+                echo "ERROR: Image ${target_image} not found locally for RC 
export"
+                return 1
+            fi
+        fi
+    done
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${DOCKER_USERNAME:-}" || -z "${DOCKER_PASSWORD:-}" ]]; then
+            echo "ERROR: DOCKER_USERNAME and DOCKER_PASSWORD are required for 
publishing container images"
+            return 1
+        fi
+        echo "Pushing images to ${REGISTRY}..."
+        echo "${DOCKER_PASSWORD}" | docker login "$(echo "${REGISTRY}" | cut 
-d/ -f1)" \
+            -u "${DOCKER_USERNAME}" --password-stdin
+        for artifact_name in "${!IMAGES[@]}"; do
+            local mapping="${IMAGES[$artifact_name]}"
+            local image_name="${mapping##*:}"
+            local full_image="${REGISTRY}/${image_name}:${VERSION}"
+            echo "  PUSH  ${full_image}"
+            docker push "${full_image}"
+        done
+        docker logout "$(echo "${REGISTRY}" | cut -d/ -f1)"
+        echo "Container images pushed."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 5. Helm charts
+# ---------------------------------------------------------------------------
+release_helm_charts() {
+    echo ""
+    echo "--- Helm charts ---"
+
+    if ! command -v helm &>/dev/null; then
+        echo "WARN: helm not found — skipping Helm chart release"
+        return 0
+    fi
+
+    declare -A CHARTS=(
+        ["sandbox-helm-chart"]="packages/kie-sandbox-helm-chart"
+        
["runtime-tools-console-helm-chart"]="packages/runtime-tools-consoles-helm-chart"
+    )
+
+    local output_dir="${ARTIFACTS_DIR}"
+    mkdir -p "${output_dir}"
+
+    local helm_tmp_pkg
+    helm_tmp_pkg=$(mktemp -d)
+    trap "rm -rf ${helm_tmp_pkg}" EXIT
+
+    echo "Packaging Helm charts..."
+    for slug in "${!CHARTS[@]}"; do
+        local chart_pkg="${CHARTS[$slug]}"
+        [[ -d "${chart_pkg}" ]] || { echo "ERROR: Helm chart directory 
${chart_pkg} not found"; return 1; }
+
+        local chart_yaml=""
+        for candidate in "${chart_pkg}/src/Chart.yaml" 
"${chart_pkg}/Chart.yaml"; do
+            [[ -f "${candidate}" ]] && { chart_yaml="${candidate}"; break; }
+        done
+        [[ -z "${chart_yaml}" ]] && { echo "ERROR: Chart.yaml not found in 
${chart_pkg}"; return 1; }
+
+        local chart_dir
+        chart_dir="$(dirname "${chart_yaml}")"
+        local chart_name
+        chart_name=$(grep '^name:' "${chart_yaml}" | head -1 | awk '{print 
$2}' | tr -d '"'"'")
+        local tmp_dir
+        tmp_dir=$(mktemp -d)
+        cp -r "${chart_dir}/." "${tmp_dir}/chart"
+        sed -i.bak "s/^version:.*/version: ${VERSION}/" 
"${tmp_dir}/chart/Chart.yaml"
+        sed -i.bak "s/^appVersion:.*/appVersion: \"${VERSION}\"/" 
"${tmp_dir}/chart/Chart.yaml"
+        rm -f "${tmp_dir}/chart/Chart.yaml.bak"
+        helm package "${tmp_dir}/chart" --destination "${helm_tmp_pkg}"
+        rm -rf "${tmp_dir}"
+
+        local found=""
+        if [[ -n "${chart_name}" && -f 
"${helm_tmp_pkg}/${chart_name}-${VERSION}.tgz" ]]; then
+            found="${helm_tmp_pkg}/${chart_name}-${VERSION}.tgz"
+        else
+            found=$(find "${helm_tmp_pkg}" -maxdepth 1 -name 
"*-${VERSION}.tgz" | head -1)
+        fi
+        if [[ -n "${found}" ]]; then
+            cp "${found}" 
"${output_dir}/apache-kie-${VERSION}-incubating-${slug}.tar.gz"
+            echo "  DONE  
${output_dir}/apache-kie-${VERSION}-incubating-${slug}.tar.gz"
+        else
+            echo "ERROR: Failed to package helm chart for ${slug}"
+            return 1
+        fi
+    done
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        local helm_reg="${HELM_REGISTRY:-docker.io/apache}"
+        if [[ -z "${helm_reg}" ]]; then
+            echo "ERROR: HELM_REGISTRY is required for publishing Helm charts"
+            return 1
+        fi
+        if [[ -n "${HELM_USERNAME:-}" && -n "${HELM_PASSWORD:-}" ]]; then
+            echo "${HELM_PASSWORD}" | helm registry login "$(echo 
"${helm_reg}" | cut -d/ -f1)" \
+                --username "${HELM_USERNAME}" --password-stdin
+        fi
+        for tgz in "${helm_tmp_pkg}"/*-"${VERSION}".tgz; do
+            if [[ -f "${tgz}" ]]; then
+                echo "  PUSH  ${tgz} -> oci://${helm_reg}"
+                helm push "${tgz}" "oci://${helm_reg}"
+            fi
+        done
+        echo "Helm charts pushed."
+    fi
+    rm -rf "${helm_tmp_pkg}"
+    trap - EXIT
+}
+
+# ---------------------------------------------------------------------------
+# 6. GitHub Pages / webapp / accelerator
+# ---------------------------------------------------------------------------
+release_github_pages() {
+    echo ""
+    echo "--- GitHub Pages / webapp / accelerator ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building online-editor, 
chrome-extension-pack-kogito-kie-editors, kie-sandbox-accelerator-quarkus..."
+        export 
ONLINE_EDITOR__buildInfo="${ONLINE_EDITOR__buildInfo:-${VERSION}}"
+        export 
ONLINE_EDITOR__extendedServicesCompatibleVersion="${ONLINE_EDITOR__extendedServicesCompatibleVersion:-${VERSION}}"
+        export 
ONLINE_EDITOR__corsProxyUrl="${ONLINE_EDITOR__corsProxyUrl:-https://cors-proxy-kie-sandbox.rhba-0ad6762cc85bcef5745bb684498c2436-0000.us-south.containers.appdomain.cloud}";
+        export 
ONLINE_EDITOR__quarkusAcceleratorGitRepoUrl="${ONLINE_EDITOR__quarkusAcceleratorGitRepoUrl:-https://github.com/apache/incubator-kie-sandbox-quarkus-accelerator.git}";
+        export 
ONLINE_EDITOR__quarkusAcceleratorGitRef="${ONLINE_EDITOR__quarkusAcceleratorGitRef:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageRegistry="${ONLINE_EDITOR__devDeploymentBaseImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageAccount="${ONLINE_EDITOR__devDeploymentBaseImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageName="${ONLINE_EDITOR__devDeploymentBaseImageName:-incubator-kie-sandbox-dev-deployment-base}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageTag="${ONLINE_EDITOR__devDeploymentBaseImageTag:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageName="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageName:-incubator-kie-sandbox-dev-deployment-dmn-form-webapp}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName:-incubator-kie-sandbox-dev-deployment-quarkus-blank-app}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag:-${VERSION}}"
+        pnpm \
+            -F "@kie-tools/online-editor..." \
+            -F "@kie-tools/chrome-extension-pack-kogito-kie-editors..." \
+            -F "@kie-tools/kie-sandbox-accelerator-quarkus..." \
+            build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        for src_path_suffix in \
+            
"packages/online-editor/dist:apache-kie-${VERSION}-incubating-sandbox-webapp.zip"
 \
+            
"packages/kie-sandbox-accelerator-quarkus/dist:apache-kie-${VERSION}-incubating-sandbox-accelerator-quarkus.zip"
+        do
+            local src="${REPO_ROOT}/${src_path_suffix%%:*}"
+            local zip_name="${src_path_suffix##*:}"
+            if [[ -d "${src}" ]]; then
+                echo "  ZIP  ${zip_name}"
+                (cd "${src}" && zip -r "${ARTIFACTS_DIR}/${zip_name}" .)
+            else
+                echo "  SKIP  ${zip_name} (${src} not found)"

Review Comment:
   With `--rc --skip-build`, a missing webapp or accelerator output directory 
is silently omitted while this step reports success. Return an error for an 
absent required build directory so an incomplete candidate is not reported as 
ready.



##########
.ci/jenkins/Jenkinsfile.103xplus.release-publish:
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+@Library('jenkins-pipeline-shared-libraries')_
+
+// PHASE 2: Publish Release After Vote Passes
+// This job publishes the approved release candidate to public registries
+// Publishes to: npm, VSCode Marketplace, Chrome Web Store, GitHub Releases
+//
+// NOTE: kogito-data-index-ephemeral is NOT included in this pipeline because 
the
+// packages/kogito-data-index-ephemeral-image package no longer exists in this 
repo.
+// Its presence in older Jenkinsfiles (Jenkinsfile.release-candidate RUNNERS 
list and
+// release-jobs/Jenkinsfile.kogito-data-index-ephemeral-image) is a historical 
artifact
+// from pre-10.3 automation and can be cleaned up in a follow-up PR.
+
+pipeline {
+    agent {
+        docker {
+            image 'docker.io/apache/incubator-kie-tools-ci-build:main'
+            args '--shm-size=2g --privileged --group-add docker'
+            label util.avoidFaultyNodes()
+        }
+    }
+
+    options {
+        timeout(time: 480, unit: 'MINUTES')
+    }
+
+    parameters {
+        string(
+            name: 'RELEASE_VERSION',
+            description: 'Release version (e.g., 1.0.0) - must match the voted 
RC',
+            defaultValue: '0.0.0'
+        )
+        string(
+            name: 'BASE_REF',
+            description: 'Git tag to release from (should be the RC tag)',
+            defaultValue: '0.0.0-rc1'
+        )
+        string(
+            name: 'GITHUB_RELEASE_TAG',
+            description: 'GitHub release tag (required: binary assets are 
uploaded to this existing GitHub Release)',
+            defaultValue: ''
+        )
+    }
+
+    environment {
+        KIE_TOOLS_BUILD__runLinters = 'false'
+        KIE_TOOLS_BUILD__runTests = 'false'
+        KIE_TOOLS_BUILD__runEndToEndTests = 'false'
+        KIE_TOOLS_BUILD__buildContainerImages = 'true'
+
+        CHROME_EXTENSION__routerTargetOrigin = 'https://apache.github.io'
+        CHROME_EXTENSION__routerRelativePath = 
"incubator-kie-kogito-online/chrome-extension/${params.RELEASE_VERSION}"
+        CHROME_EXTENSION__manifestFile = 'manifest.prod.json'
+        CHROME_EXTENSION__onlineEditorUrl = 
'https://apache.github.io/incubator-kie-kogito-online'
+
+        ONLINE_EDITOR__corsProxyUrl = 
'https://cors-proxy-kie-sandbox.rhba-0ad6762cc85bcef5745bb684498c2436-0000.us-south.containers.appdomain.cloud'
+        ONLINE_EDITOR__buildInfo = "${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__extendedServicesCompatibleVersion = 
"${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentBaseImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentBaseImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentBaseImageName = 
'incubator-kie-sandbox-dev-deployment-base'
+        ONLINE_EDITOR__devDeploymentBaseImageTag = "${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageName = 
'incubator-kie-sandbox-dev-deployment-dmn-form-webapp'
+        ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag = 
"${params.RELEASE_VERSION}"
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry = 'docker.io'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount = 'apache'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName = 
'incubator-kie-sandbox-dev-deployment-quarkus-blank-app'
+        ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag = 
"${params.RELEASE_VERSION}"
+
+        DOCKER_CONFIG = "${WORKSPACE}/.docker"
+        HELM_REGISTRY = 'docker.io/apache'
+    }
+
+    stages {
+        stage('Validate parameters') {
+            steps {
+                script {
+                    if (params.RELEASE_VERSION == '' || params.RELEASE_VERSION 
== '0.0.0') {
+                        error("RELEASE_VERSION must be set to the voted 
release version")
+                    }
+                    if (params.BASE_REF == '' || params.BASE_REF == 
'0.0.0-rc1') {
+                        error("BASE_REF must be set to the voted RC tag")
+                    }
+                    // release-all.sh --publish uploads binary assets to the 
GitHub Release and fails without it,
+                    // after other registries would already have been 
published. Fail fast instead.
+                    if (params.GITHUB_RELEASE_TAG == '') {
+                        error("GITHUB_RELEASE_TAG must be set to an existing 
GitHub Release tag")
+                    }
+                }
+            }
+        }
+
+        stage('Load local shared scripts') {
+            steps {
+                script {
+                    pipelineVars = load 
'.ci/jenkins/shared-scripts/pipelineVars.groovy'
+                    buildUtils = load 
'.ci/jenkins/shared-scripts/buildUtils.groovy'
+                    githubUtils = load 
'.ci/jenkins/shared-scripts/githubUtils.groovy'
+                }
+            }
+        }
+
+        stage('Start required services') {
+            steps {
+                script {
+                    buildUtils.startRequiredServices()
+                }
+            }
+        }
+
+        stage('Clean workspace') {
+            steps {
+                cleanWs(deleteDirs: true, disableDeferredWipeout: true)
+            }
+        }
+
+        stage('Checkout kie-tools') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        githubUtils.checkoutRepo(
+                            
"http://github.com/${pipelineVars.githubRepositorySlug}.git";,
+                            "${params.BASE_REF}",
+                            "${pipelineVars.kieToolsBotGithubCredentialsId}"
+                        )
+                    }
+                }
+            }
+        }
+
+        stage('Verify version') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        def packageVersion = sh(
+                            returnStdout: true,
+                            script: "#!/bin/bash -el \n node -p 
\"require('./package.json').version\""
+                        ).trim()
+                        
+                        if (packageVersion != params.RELEASE_VERSION) {
+                            error("Version mismatch! package.json has 
${packageVersion} but RELEASE_VERSION is ${params.RELEASE_VERSION}")
+                        }
+                        
+                        echo "✅ Version verified: ${packageVersion}"
+                    }
+                }
+            }
+        }
+
+        stage('Setup PNPM') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        
buildUtils.setupPnpm("${pipelineVars.mavenSettingsReleaseConfigFileId}")
+                    }
+                }
+            }
+        }
+
+        stage('Bootstrap') {
+            steps {
+                dir('kie-tools') {
+                    sh '''#!/bin/bash -el
+                    pnpm bootstrap
+                    '''
+                }
+            }
+        }
+
+        stage('Build and Publish to Registries') {
+            steps {
+                dir('kie-tools') {
+                    script {
+                        echo "Publishing release to public registries..."
+                        echo "Version: ${params.RELEASE_VERSION}"
+
+                        withCredentials([
+                            string(credentialsId: 
"${pipelineVars.npmTokenCredentialsId}",                  variable: 
'NPM_TOKEN'),
+                            string(credentialsId: 
"${pipelineVars.vscePatCredentialsId}",                   variable: 'VSCE_PAT'),
+                            usernamePassword(credentialsId: 
"${pipelineVars.chromeStoreCredentialsId}",
+                                             usernameVariable: 
'CHROME_CLIENT_ID',
+                                             passwordVariable: 
'CHROME_CLIENT_SECRET'),
+                            string(credentialsId: 
"${pipelineVars.chromeStoreRefreshTokenCredentialsId}",   variable: 
'CHROME_REFRESH_TOKEN'),
+                            string(credentialsId: 
"${pipelineVars.chromeExtensionIdCredentialsId}",         variable: 
'CHROME_KIE_EDITORS_EXTENSION_ID'),
+                            string(credentialsId: 
"${pipelineVars.dockerHubUserCredentialsId}",             variable: 
'DOCKER_USERNAME'),
+                            string(credentialsId: 
"${pipelineVars.dockerHubTokenCredentialsId}",            variable: 
'DOCKER_PASSWORD'),
+                            // Helm charts use the same Docker Hub credentials 
as the old helm jobs
+                            string(credentialsId: 
"${pipelineVars.dockerHubUserCredentialsId}",             variable: 
'HELM_USERNAME'),
+                            string(credentialsId: 
"${pipelineVars.dockerHubTokenCredentialsId}",            variable: 
'HELM_PASSWORD'),
+                            string(credentialsId: 
"${pipelineVars.kieToolsBotGithubTokenCredentialsId}",    variable: 
'GITHUB_TOKEN')
+                        ]) {
+                            def releaseJson = sh(
+                                returnStdout: true,
+                                script: """#!/bin/bash -el
+                                set +x
+                                curl -sSf -H "Authorization: token 
\${GITHUB_TOKEN}" 
"https://api.github.com/repos/${pipelineVars.githubRepositorySlug}/releases/tags/${params.GITHUB_RELEASE_TAG}";
+                                """
+                            ).trim()
+                            def releaseInfo = readJSON text: releaseJson
+                            def uploadUrl = 
githubUtils.parseReleaseAssetUploadUrl("${releaseInfo.upload_url}")
+
+                            sh """#!/bin/bash -el
+                            ./scripts/release/release-all.sh 
${params.RELEASE_VERSION} --publish --upload-url '${uploadUrl}'

Review Comment:
   On a clean agent without a global Git identity, the accelerator build fails 
because it reads the checkout's `user.email` and `user.name` to create a 
commit. Configure both before invoking the release script so publication does 
not fail after earlier components have already been published.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";
+
+ARTIFACTS_DIR="${REPO_ROOT}/release-artifacts"
+
+echo "=========================================="
+echo "KIE Tools Release"
+echo "Version:    ${VERSION}"
+echo "RC mode:    ${RC_MODE}"
+echo "Publish:    ${PUBLISH}"
+echo "Skip build: ${SKIP_BUILD}"
+echo "Registry:   ${REGISTRY}"
+echo "=========================================="
+
+cd "${REPO_ROOT}"
+if [[ "${SKIP_BUILD}" == "false" ]]; then
+    echo ""
+    echo "--- Bumping workspace version to ${VERSION} ---"
+    pnpm update-version-to "${VERSION}"
+    pnpm update-kogito-version-to --maven "${VERSION}"
+    pnpm update-stream-name-to "${VERSION}"
+fi
+
+# ---------------------------------------------------------------------------
+# Helper: sign and upload a single asset to a GitHub Release
+# ---------------------------------------------------------------------------
+upload_github_asset() {
+    local file="$1"
+    local asset_name="$2"
+    local content_type="${3:-application/octet-stream}"
+    if [[ ! -f "${file}" ]]; then
+        echo "  SKIP  ${asset_name} (file not found)"
+        return
+    fi
+    echo "  UPLOAD  ${asset_name}"
+    curl -sSf \
+        -H "Authorization: token ${GITHUB_TOKEN}" \
+        -H "Content-Type: ${content_type}" \
+        --data-binary "@${file}" \
+        "${UPLOAD_URL}?name=${asset_name}"
+}
+
+# ---------------------------------------------------------------------------
+# 1. NPM packages
+# ---------------------------------------------------------------------------
+release_npm_packages() {
+    echo ""
+    echo "--- NPM packages ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building public NPM packages..."
+        local filter
+        filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)..."; fi')
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        local tmp_dir="${ARTIFACTS_DIR}/npm-packages-tmp"
+        local 
zip_file="apache-kie-${VERSION}-incubating-tools-npm-packages.zip"
+        mkdir -p "${tmp_dir}" "${ARTIFACTS_DIR}"
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c "pnpm pack --pack-destination 
${tmp_dir}"
+        (cd "${tmp_dir}" && zip -r "${ARTIFACTS_DIR}/${zip_file}" .)
+        rm -rf "${tmp_dir}"
+        echo "RC artifact: ${ARTIFACTS_DIR}/${zip_file}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${NPM_TOKEN:-}" ]]; then
+            echo "ERROR: NPM_TOKEN is required for publishing npm packages"
+            return 1
+        fi
+        local npmrc_tmp
+        npmrc_tmp=$(mktemp)
+        trap "rm -f '${npmrc_tmp}'" EXIT
+        echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${npmrc_tmp}"
+        export NPM_CONFIG_USERCONFIG="${npmrc_tmp}"
+
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c '
+            PKG_NAME=$(jq -r ".name" package.json)
+            if ! npm view ${PKG_NAME}@'"${VERSION}"' name --userconfig 
"'"${npmrc_tmp}"'" &>/dev/null; then
+                echo "Publishing ${PKG_NAME}@'"${VERSION}"'"
+                pnpm publish --no-git-checks --access public --userconfig 
"'"${npmrc_tmp}"'"
+            else
+                echo "Skipping ${PKG_NAME}@'"${VERSION}"' (already published)"
+            fi
+        '
+        rm -f "${npmrc_tmp}"
+        trap - EXIT
+        echo "NPM packages published."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 2. Chrome extensions
+# ---------------------------------------------------------------------------
+release_chrome_extensions() {
+    echo ""
+    echo "--- Chrome extensions ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building Chrome extensions..."
+        pnpm -F "chrome-extension-pack-kogito-kie-editors..." build:prod
+    fi
+
+    local kie_editors_zip
+    kie_editors_zip=$(find 
"packages/chrome-extension-pack-kogito-kie-editors/dist" \
+        -maxdepth 1 -name "chrome_extension_kogito_kie_editors_*.zip" 
2>/dev/null | head -1 || true)
+    if [[ -z "${kie_editors_zip}" ]]; then
+        echo "ERROR: chrome_extension_kogito_kie_editors_*.zip not found — was 
the build successful?"
+        return 1
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        cp "${kie_editors_zip}" \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension.zip"
+
+        local tmp_kie="${ARTIFACTS_DIR}/kie-editors-tmp"
+        mkdir -p "${tmp_kie}"
+        [[ -d "packages/chrome-extension-pack-kogito-kie-editors/dist/fonts" 
]] && cp -r packages/chrome-extension-pack-kogito-kie-editors/dist/fonts 
"${tmp_kie}/"
+        cp packages/chrome-extension-pack-kogito-kie-editors/dist/*-envelope.* 
"${tmp_kie}/" 2>/dev/null || true
+        (cd "${tmp_kie}" && zip -r \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension-editors.zip"
 .)
+        rm -rf "${tmp_kie}"
+        echo "RC artifacts created in: ${ARTIFACTS_DIR}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${CHROME_CLIENT_ID:-}" || -z "${CHROME_CLIENT_SECRET:-}" || 
-z "${CHROME_REFRESH_TOKEN:-}" ]]; then
+            echo "ERROR: CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, and 
CHROME_REFRESH_TOKEN are required"
+            return 1
+        fi
+
+        local token_res
+        token_res=$(curl -sSf -X POST "https://oauth2.googleapis.com/token"; \
+            -d "client_id=${CHROME_CLIENT_ID}" \
+            -d "client_secret=${CHROME_CLIENT_SECRET}" \
+            -d "refresh_token=${CHROME_REFRESH_TOKEN}" \
+            -d "grant_type=refresh_token")
+        local access_token
+        access_token=$(echo "${token_res}" | jq -r '.access_token // empty')
+
+        if [[ -z "${access_token}" ]]; then
+            echo "ERROR: Failed to obtain Chrome Web Store OAuth token: 
${token_res}"
+            return 1
+        fi
+
+        _chrome_upload() {
+            local ext_id="$1" zip="$2"
+            local res state
+            res=$(curl -sSf -X PUT \
+                
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/${ext_id}"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -T "${zip}")
+            state=$(echo "${res}" | jq -r '.uploadState // empty')
+            if [[ "${state}" != "SUCCESS" ]]; then
+                echo "ERROR: Chrome Web Store upload failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+        _chrome_publish() {
+            local ext_id="$1"
+            local res pub_status
+            res=$(curl -sSf -X POST \
+                
"https://www.googleapis.com/chromewebstore/v1.1/items/${ext_id}/publish"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -H "Content-Length: 0")
+            pub_status=$(echo "${res}" | jq -r '.status[0] // .status // 
empty')
+            if [[ "${pub_status}" != "OK" && "${pub_status}" != 
"PUBLISHED_WITH_FRICTION_WARNING" ]]; then
+                echo "ERROR: Chrome Web Store publish failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+
+        if [[ -n "${CHROME_KIE_EDITORS_EXTENSION_ID:-}" ]]; then
+            _chrome_upload "${CHROME_KIE_EDITORS_EXTENSION_ID}" 
"${kie_editors_zip}"
+            _chrome_publish "${CHROME_KIE_EDITORS_EXTENSION_ID}"
+            echo "Chrome extension published."
+        else
+            echo "WARN: CHROME_KIE_EDITORS_EXTENSION_ID not set — skipping 
Chrome publish"
+        fi
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 3. VSCode extensions
+# ---------------------------------------------------------------------------
+release_vscode() {
+    echo ""
+    echo "--- VSCode extensions ---"
+
+    declare -A EXTENSIONS=(
+        ["bpmn-vscode-extension"]="bpmn-vscode-extension"
+        ["dmn-vscode-extension"]="dmn-vscode-extension"
+        ["drl-vscode-extension"]="drl-vscode-extension"
+        ["pmml-vscode-extension"]="pmml-vscode-extension"
+        ["vscode-extension-kogito-bundle"]="kogito-bundle-vscode-extension"
+        
["vscode-extension-kie-ba-bundle"]="business-automation-bundle-vscode-extension"
+        
["extended-services-vscode-extension"]="extended-services-vscode-extension"
+    )
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        local filter=""
+        for ext in "${!EXTENSIONS[@]}"; do filter="${filter} -F ${ext}..."; 
done
+        echo "Building VSCode extensions..."
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        for ext in "${!EXTENSIONS[@]}"; do
+            local suffix="${EXTENSIONS[$ext]}"
+            local vsix
+            vsix=$(find "packages/${ext}" -maxdepth 2 -name "*.vsix" 
2>/dev/null | head -1 || true)
+            if [[ -n "${vsix}" ]]; then
+                local 
dest="${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-${suffix}.vsix"
+                echo "  COPY  $(basename "${dest}")"
+                cp "${vsix}" "${dest}"
+            else
+                echo "  SKIP  ${ext} (no .vsix found)"
+            fi
+        done
+        echo "VSIX files in: ${ARTIFACTS_DIR}/"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${VSCE_PAT:-}" ]]; then
+            echo "ERROR: VSCE_PAT is required for publishing VSCode extensions"
+            return 1
+        fi
+        echo "Publishing to VSCode Marketplace..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                echo "  Publishing: ${vsix}"
+                npx vsce publish --packagePath "${vsix}" --pat "${VSCE_PAT}"
+            done
+        done
+        echo "VSCode extensions published."
+    fi
+
+    if [[ "${PUBLISH}" == "true" && -n "${UPLOAD_URL:-}" && -n 
"${GITHUB_TOKEN:-}" ]]; then
+        echo "Uploading .vsix files to GitHub Release..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                upload_github_asset "${vsix}" "$(basename "${vsix}")" 
"application/zip"
+            done
+        done
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 4. Container images
+# ---------------------------------------------------------------------------
+release_container_images() {
+    echo ""
+    echo "--- Container images ---"
+
+    declare -A IMAGES=(
+        ["cors-proxy"]="packages/cors-proxy-image:incubator-kie-cors-proxy"
+        
["sandbox-webapp"]="packages/kie-sandbox-webapp-image:incubator-kie-sandbox-webapp"
+        
["sandbox-extended-services"]="packages/kie-sandbox-extended-services-image:incubator-kie-sandbox-extended-services"
+        
["sandbox-dev-deployment-base"]="packages/dev-deployment-base-image:incubator-kie-sandbox-dev-deployment-base"
+        
["sandbox-dev-deployment-dmn-form-webapp"]="packages/dev-deployment-dmn-form-webapp-image:incubator-kie-sandbox-dev-deployment-dmn-form-webapp"
+        
["sandbox-dev-deployment-quarkus-blank-app"]="packages/dev-deployment-quarkus-blank-app-image:incubator-kie-sandbox-dev-deployment-quarkus-blank-app"
+        
["kogito-management-console"]="packages/kogito-management-console:incubator-kie-kogito-management-console"
+    )
+
+    export KIE_TOOLS_BUILD__buildContainerImages=true
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        if ! docker info &>/dev/null; then
+            echo "WARN: Docker is not running — skipping container image build"
+            return 0
+        fi
+        echo "Building container images..."
+        for artifact_name in "${!IMAGES[@]}"; do
+            local mapping="${IMAGES[$artifact_name]}"
+            local pkg_path="${mapping%%:*}"
+            if [[ ! -d "${pkg_path}" ]]; then
+                echo "ERROR: Image package directory ${pkg_path} not found"
+                return 1
+            fi
+            local pkg_name
+            pkg_name=$(node -p "require('./${pkg_path}/package.json').name" 
2>/dev/null || basename "${pkg_path}")
+            echo "  BUILD  ${artifact_name} (${pkg_name})"
+            pnpm --filter "${pkg_name}..." build:prod
+        done
+    else
+        if ! docker info &>/dev/null; then
+            echo "SKIP: Docker is not running — cannot save container image 
tarballs with --skip-build"
+            return 0
+        fi
+    fi
+
+    local output_dir="${ARTIFACTS_DIR}"
+    [[ "${RC_MODE}" == "true" ]] && mkdir -p "${output_dir}"
+
+    echo "Tagging images..."
+    for artifact_name in "${!IMAGES[@]}"; do
+        local mapping="${IMAGES[$artifact_name]}"
+        local pkg_path="${mapping%%:*}"
+        local image_name="${mapping##*:}"
+        if [[ ! -d "${pkg_path}" ]]; then
+            echo "ERROR: Image package directory ${pkg_path} not found"
+            return 1
+        fi
+        local target_image="${REGISTRY}/${image_name}:${VERSION}"
+
+        # Resolve built image from local docker daemon
+        local source_image=""
+        for candidate in \
+            "docker.io/apache/${image_name}:${VERSION}" \
+            "apache/${image_name}:${VERSION}" \
+            "${image_name}:${VERSION}" \
+            "docker.io/apache/${image_name}:latest" \
+            "apache/${image_name}:latest" \
+            "${image_name}:latest"; do

Review Comment:
   Falling back to `latest` can release an unrelated build. For example, with 
`--skip-build`, if the requested version is absent but a previous development 
image exists as `latest`, it is retagged as `${VERSION}` and then signed for 
the RC or pushed publicly. Resolve only an explicitly version-matching image, 
or require an explicit, validated source-image override instead of silently 
treating `latest` as this release.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";
+
+ARTIFACTS_DIR="${REPO_ROOT}/release-artifacts"
+
+echo "=========================================="
+echo "KIE Tools Release"
+echo "Version:    ${VERSION}"
+echo "RC mode:    ${RC_MODE}"
+echo "Publish:    ${PUBLISH}"
+echo "Skip build: ${SKIP_BUILD}"
+echo "Registry:   ${REGISTRY}"
+echo "=========================================="
+
+cd "${REPO_ROOT}"
+if [[ "${SKIP_BUILD}" == "false" ]]; then
+    echo ""
+    echo "--- Bumping workspace version to ${VERSION} ---"
+    pnpm update-version-to "${VERSION}"
+    pnpm update-kogito-version-to --maven "${VERSION}"
+    pnpm update-stream-name-to "${VERSION}"
+fi
+
+# ---------------------------------------------------------------------------
+# Helper: sign and upload a single asset to a GitHub Release
+# ---------------------------------------------------------------------------
+upload_github_asset() {
+    local file="$1"
+    local asset_name="$2"
+    local content_type="${3:-application/octet-stream}"
+    if [[ ! -f "${file}" ]]; then
+        echo "  SKIP  ${asset_name} (file not found)"
+        return
+    fi
+    echo "  UPLOAD  ${asset_name}"
+    curl -sSf \
+        -H "Authorization: token ${GITHUB_TOKEN}" \
+        -H "Content-Type: ${content_type}" \
+        --data-binary "@${file}" \
+        "${UPLOAD_URL}?name=${asset_name}"
+}
+
+# ---------------------------------------------------------------------------
+# 1. NPM packages
+# ---------------------------------------------------------------------------
+release_npm_packages() {
+    echo ""
+    echo "--- NPM packages ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building public NPM packages..."
+        local filter
+        filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)..."; fi')
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        local tmp_dir="${ARTIFACTS_DIR}/npm-packages-tmp"
+        local 
zip_file="apache-kie-${VERSION}-incubating-tools-npm-packages.zip"
+        mkdir -p "${tmp_dir}" "${ARTIFACTS_DIR}"
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c "pnpm pack --pack-destination 
${tmp_dir}"
+        (cd "${tmp_dir}" && zip -r "${ARTIFACTS_DIR}/${zip_file}" .)
+        rm -rf "${tmp_dir}"
+        echo "RC artifact: ${ARTIFACTS_DIR}/${zip_file}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${NPM_TOKEN:-}" ]]; then
+            echo "ERROR: NPM_TOKEN is required for publishing npm packages"
+            return 1
+        fi
+        local npmrc_tmp
+        npmrc_tmp=$(mktemp)
+        trap "rm -f '${npmrc_tmp}'" EXIT
+        echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${npmrc_tmp}"
+        export NPM_CONFIG_USERCONFIG="${npmrc_tmp}"
+
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c '
+            PKG_NAME=$(jq -r ".name" package.json)
+            if ! npm view ${PKG_NAME}@'"${VERSION}"' name --userconfig 
"'"${npmrc_tmp}"'" &>/dev/null; then
+                echo "Publishing ${PKG_NAME}@'"${VERSION}"'"
+                pnpm publish --no-git-checks --access public --userconfig 
"'"${npmrc_tmp}"'"
+            else
+                echo "Skipping ${PKG_NAME}@'"${VERSION}"' (already published)"
+            fi
+        '
+        rm -f "${npmrc_tmp}"
+        trap - EXIT
+        echo "NPM packages published."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 2. Chrome extensions
+# ---------------------------------------------------------------------------
+release_chrome_extensions() {
+    echo ""
+    echo "--- Chrome extensions ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building Chrome extensions..."
+        pnpm -F "chrome-extension-pack-kogito-kie-editors..." build:prod
+    fi
+
+    local kie_editors_zip
+    kie_editors_zip=$(find 
"packages/chrome-extension-pack-kogito-kie-editors/dist" \
+        -maxdepth 1 -name "chrome_extension_kogito_kie_editors_*.zip" 
2>/dev/null | head -1 || true)
+    if [[ -z "${kie_editors_zip}" ]]; then
+        echo "ERROR: chrome_extension_kogito_kie_editors_*.zip not found — was 
the build successful?"
+        return 1
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        cp "${kie_editors_zip}" \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension.zip"
+
+        local tmp_kie="${ARTIFACTS_DIR}/kie-editors-tmp"
+        mkdir -p "${tmp_kie}"
+        [[ -d "packages/chrome-extension-pack-kogito-kie-editors/dist/fonts" 
]] && cp -r packages/chrome-extension-pack-kogito-kie-editors/dist/fonts 
"${tmp_kie}/"
+        cp packages/chrome-extension-pack-kogito-kie-editors/dist/*-envelope.* 
"${tmp_kie}/" 2>/dev/null || true
+        (cd "${tmp_kie}" && zip -r \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension-editors.zip"
 .)
+        rm -rf "${tmp_kie}"
+        echo "RC artifacts created in: ${ARTIFACTS_DIR}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${CHROME_CLIENT_ID:-}" || -z "${CHROME_CLIENT_SECRET:-}" || 
-z "${CHROME_REFRESH_TOKEN:-}" ]]; then
+            echo "ERROR: CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, and 
CHROME_REFRESH_TOKEN are required"
+            return 1
+        fi
+
+        local token_res
+        token_res=$(curl -sSf -X POST "https://oauth2.googleapis.com/token"; \
+            -d "client_id=${CHROME_CLIENT_ID}" \
+            -d "client_secret=${CHROME_CLIENT_SECRET}" \
+            -d "refresh_token=${CHROME_REFRESH_TOKEN}" \
+            -d "grant_type=refresh_token")
+        local access_token
+        access_token=$(echo "${token_res}" | jq -r '.access_token // empty')
+
+        if [[ -z "${access_token}" ]]; then
+            echo "ERROR: Failed to obtain Chrome Web Store OAuth token: 
${token_res}"
+            return 1
+        fi
+
+        _chrome_upload() {
+            local ext_id="$1" zip="$2"
+            local res state
+            res=$(curl -sSf -X PUT \
+                
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/${ext_id}"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -T "${zip}")
+            state=$(echo "${res}" | jq -r '.uploadState // empty')
+            if [[ "${state}" != "SUCCESS" ]]; then
+                echo "ERROR: Chrome Web Store upload failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+        _chrome_publish() {
+            local ext_id="$1"
+            local res pub_status
+            res=$(curl -sSf -X POST \
+                
"https://www.googleapis.com/chromewebstore/v1.1/items/${ext_id}/publish"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -H "Content-Length: 0")
+            pub_status=$(echo "${res}" | jq -r '.status[0] // .status // 
empty')
+            if [[ "${pub_status}" != "OK" && "${pub_status}" != 
"PUBLISHED_WITH_FRICTION_WARNING" ]]; then
+                echo "ERROR: Chrome Web Store publish failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+
+        if [[ -n "${CHROME_KIE_EDITORS_EXTENSION_ID:-}" ]]; then
+            _chrome_upload "${CHROME_KIE_EDITORS_EXTENSION_ID}" 
"${kie_editors_zip}"
+            _chrome_publish "${CHROME_KIE_EDITORS_EXTENSION_ID}"
+            echo "Chrome extension published."
+        else
+            echo "WARN: CHROME_KIE_EDITORS_EXTENSION_ID not set — skipping 
Chrome publish"
+        fi
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 3. VSCode extensions
+# ---------------------------------------------------------------------------
+release_vscode() {
+    echo ""
+    echo "--- VSCode extensions ---"
+
+    declare -A EXTENSIONS=(
+        ["bpmn-vscode-extension"]="bpmn-vscode-extension"
+        ["dmn-vscode-extension"]="dmn-vscode-extension"
+        ["drl-vscode-extension"]="drl-vscode-extension"
+        ["pmml-vscode-extension"]="pmml-vscode-extension"
+        ["vscode-extension-kogito-bundle"]="kogito-bundle-vscode-extension"
+        
["vscode-extension-kie-ba-bundle"]="business-automation-bundle-vscode-extension"
+        
["extended-services-vscode-extension"]="extended-services-vscode-extension"
+    )
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        local filter=""
+        for ext in "${!EXTENSIONS[@]}"; do filter="${filter} -F ${ext}..."; 
done
+        echo "Building VSCode extensions..."
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        for ext in "${!EXTENSIONS[@]}"; do
+            local suffix="${EXTENSIONS[$ext]}"
+            local vsix
+            vsix=$(find "packages/${ext}" -maxdepth 2 -name "*.vsix" 
2>/dev/null | head -1 || true)
+            if [[ -n "${vsix}" ]]; then
+                local 
dest="${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-${suffix}.vsix"
+                echo "  COPY  $(basename "${dest}")"
+                cp "${vsix}" "${dest}"
+            else
+                echo "  SKIP  ${ext} (no .vsix found)"
+            fi
+        done
+        echo "VSIX files in: ${ARTIFACTS_DIR}/"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${VSCE_PAT:-}" ]]; then
+            echo "ERROR: VSCE_PAT is required for publishing VSCode extensions"
+            return 1
+        fi
+        echo "Publishing to VSCode Marketplace..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                echo "  Publishing: ${vsix}"
+                npx vsce publish --packagePath "${vsix}" --pat "${VSCE_PAT}"
+            done
+        done
+        echo "VSCode extensions published."
+    fi
+
+    if [[ "${PUBLISH}" == "true" && -n "${UPLOAD_URL:-}" && -n 
"${GITHUB_TOKEN:-}" ]]; then
+        echo "Uploading .vsix files to GitHub Release..."
+        for ext in "${!EXTENSIONS[@]}"; do
+            find "packages/${ext}" -maxdepth 2 -name "*.vsix" | while read -r 
vsix; do
+                upload_github_asset "${vsix}" "$(basename "${vsix}")" 
"application/zip"
+            done
+        done
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 4. Container images
+# ---------------------------------------------------------------------------
+release_container_images() {
+    echo ""
+    echo "--- Container images ---"
+
+    declare -A IMAGES=(
+        ["cors-proxy"]="packages/cors-proxy-image:incubator-kie-cors-proxy"
+        
["sandbox-webapp"]="packages/kie-sandbox-webapp-image:incubator-kie-sandbox-webapp"
+        
["sandbox-extended-services"]="packages/kie-sandbox-extended-services-image:incubator-kie-sandbox-extended-services"
+        
["sandbox-dev-deployment-base"]="packages/dev-deployment-base-image:incubator-kie-sandbox-dev-deployment-base"
+        
["sandbox-dev-deployment-dmn-form-webapp"]="packages/dev-deployment-dmn-form-webapp-image:incubator-kie-sandbox-dev-deployment-dmn-form-webapp"
+        
["sandbox-dev-deployment-quarkus-blank-app"]="packages/dev-deployment-quarkus-blank-app-image:incubator-kie-sandbox-dev-deployment-quarkus-blank-app"
+        
["kogito-management-console"]="packages/kogito-management-console:incubator-kie-kogito-management-console"
+    )
+
+    export KIE_TOOLS_BUILD__buildContainerImages=true
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        if ! docker info &>/dev/null; then
+            echo "WARN: Docker is not running — skipping container image build"
+            return 0
+        fi
+        echo "Building container images..."
+        for artifact_name in "${!IMAGES[@]}"; do
+            local mapping="${IMAGES[$artifact_name]}"
+            local pkg_path="${mapping%%:*}"
+            if [[ ! -d "${pkg_path}" ]]; then
+                echo "ERROR: Image package directory ${pkg_path} not found"
+                return 1
+            fi
+            local pkg_name
+            pkg_name=$(node -p "require('./${pkg_path}/package.json').name" 
2>/dev/null || basename "${pkg_path}")
+            echo "  BUILD  ${artifact_name} (${pkg_name})"
+            pnpm --filter "${pkg_name}..." build:prod
+        done
+    else
+        if ! docker info &>/dev/null; then
+            echo "SKIP: Docker is not running — cannot save container image 
tarballs with --skip-build"
+            return 0
+        fi
+    fi
+
+    local output_dir="${ARTIFACTS_DIR}"
+    [[ "${RC_MODE}" == "true" ]] && mkdir -p "${output_dir}"
+
+    echo "Tagging images..."
+    for artifact_name in "${!IMAGES[@]}"; do
+        local mapping="${IMAGES[$artifact_name]}"
+        local pkg_path="${mapping%%:*}"
+        local image_name="${mapping##*:}"
+        if [[ ! -d "${pkg_path}" ]]; then
+            echo "ERROR: Image package directory ${pkg_path} not found"
+            return 1
+        fi
+        local target_image="${REGISTRY}/${image_name}:${VERSION}"
+
+        # Resolve built image from local docker daemon
+        local source_image=""
+        for candidate in \
+            "docker.io/apache/${image_name}:${VERSION}" \
+            "apache/${image_name}:${VERSION}" \
+            "${image_name}:${VERSION}" \
+            "docker.io/apache/${image_name}:latest" \
+            "apache/${image_name}:latest" \
+            "${image_name}:latest"; do
+            if docker image inspect "${candidate}" &>/dev/null; then
+                source_image="${candidate}"
+                break
+            fi
+        done
+
+        if [[ -n "${source_image}" ]]; then
+            docker tag "${source_image}" "${target_image}"
+        else
+            echo "  WARN  could not find local image for ${artifact_name} 
(${image_name})"
+        fi
+
+        if [[ "${RC_MODE}" == "true" ]]; then
+            if docker image inspect "${target_image}" &>/dev/null; then
+                local 
tarball="${output_dir}/apache-kie-${VERSION}-incubating-${artifact_name}-image.tar.gz"
+                echo "  SAVE  ${tarball}"
+                docker save "${target_image}" | gzip > "${tarball}"
+            else
+                echo "ERROR: Image ${target_image} not found locally for RC 
export"
+                return 1
+            fi
+        fi
+    done
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${DOCKER_USERNAME:-}" || -z "${DOCKER_PASSWORD:-}" ]]; then
+            echo "ERROR: DOCKER_USERNAME and DOCKER_PASSWORD are required for 
publishing container images"
+            return 1
+        fi
+        echo "Pushing images to ${REGISTRY}..."
+        echo "${DOCKER_PASSWORD}" | docker login "$(echo "${REGISTRY}" | cut 
-d/ -f1)" \
+            -u "${DOCKER_USERNAME}" --password-stdin
+        for artifact_name in "${!IMAGES[@]}"; do
+            local mapping="${IMAGES[$artifact_name]}"
+            local image_name="${mapping##*:}"
+            local full_image="${REGISTRY}/${image_name}:${VERSION}"
+            echo "  PUSH  ${full_image}"
+            docker push "${full_image}"
+        done
+        docker logout "$(echo "${REGISTRY}" | cut -d/ -f1)"
+        echo "Container images pushed."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 5. Helm charts
+# ---------------------------------------------------------------------------
+release_helm_charts() {
+    echo ""
+    echo "--- Helm charts ---"
+
+    if ! command -v helm &>/dev/null; then
+        echo "WARN: helm not found — skipping Helm chart release"
+        return 0
+    fi
+
+    declare -A CHARTS=(
+        ["sandbox-helm-chart"]="packages/kie-sandbox-helm-chart"
+        
["runtime-tools-console-helm-chart"]="packages/runtime-tools-consoles-helm-chart"
+    )
+
+    local output_dir="${ARTIFACTS_DIR}"
+    mkdir -p "${output_dir}"
+
+    local helm_tmp_pkg
+    helm_tmp_pkg=$(mktemp -d)
+    trap "rm -rf ${helm_tmp_pkg}" EXIT
+
+    echo "Packaging Helm charts..."
+    for slug in "${!CHARTS[@]}"; do
+        local chart_pkg="${CHARTS[$slug]}"
+        [[ -d "${chart_pkg}" ]] || { echo "ERROR: Helm chart directory 
${chart_pkg} not found"; return 1; }
+
+        local chart_yaml=""
+        for candidate in "${chart_pkg}/src/Chart.yaml" 
"${chart_pkg}/Chart.yaml"; do
+            [[ -f "${candidate}" ]] && { chart_yaml="${candidate}"; break; }
+        done
+        [[ -z "${chart_yaml}" ]] && { echo "ERROR: Chart.yaml not found in 
${chart_pkg}"; return 1; }
+
+        local chart_dir
+        chart_dir="$(dirname "${chart_yaml}")"
+        local chart_name
+        chart_name=$(grep '^name:' "${chart_yaml}" | head -1 | awk '{print 
$2}' | tr -d '"'"'")
+        local tmp_dir
+        tmp_dir=$(mktemp -d)
+        cp -r "${chart_dir}/." "${tmp_dir}/chart"
+        sed -i.bak "s/^version:.*/version: ${VERSION}/" 
"${tmp_dir}/chart/Chart.yaml"
+        sed -i.bak "s/^appVersion:.*/appVersion: \"${VERSION}\"/" 
"${tmp_dir}/chart/Chart.yaml"
+        rm -f "${tmp_dir}/chart/Chart.yaml.bak"
+        helm package "${tmp_dir}/chart" --destination "${helm_tmp_pkg}"
+        rm -rf "${tmp_dir}"
+
+        local found=""
+        if [[ -n "${chart_name}" && -f 
"${helm_tmp_pkg}/${chart_name}-${VERSION}.tgz" ]]; then
+            found="${helm_tmp_pkg}/${chart_name}-${VERSION}.tgz"
+        else
+            found=$(find "${helm_tmp_pkg}" -maxdepth 1 -name 
"*-${VERSION}.tgz" | head -1)
+        fi
+        if [[ -n "${found}" ]]; then
+            cp "${found}" 
"${output_dir}/apache-kie-${VERSION}-incubating-${slug}.tar.gz"
+            echo "  DONE  
${output_dir}/apache-kie-${VERSION}-incubating-${slug}.tar.gz"
+        else
+            echo "ERROR: Failed to package helm chart for ${slug}"
+            return 1
+        fi
+    done
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        local helm_reg="${HELM_REGISTRY:-docker.io/apache}"
+        if [[ -z "${helm_reg}" ]]; then
+            echo "ERROR: HELM_REGISTRY is required for publishing Helm charts"
+            return 1
+        fi
+        if [[ -n "${HELM_USERNAME:-}" && -n "${HELM_PASSWORD:-}" ]]; then
+            echo "${HELM_PASSWORD}" | helm registry login "$(echo 
"${helm_reg}" | cut -d/ -f1)" \
+                --username "${HELM_USERNAME}" --password-stdin
+        fi
+        for tgz in "${helm_tmp_pkg}"/*-"${VERSION}".tgz; do
+            if [[ -f "${tgz}" ]]; then
+                echo "  PUSH  ${tgz} -> oci://${helm_reg}"
+                helm push "${tgz}" "oci://${helm_reg}"
+            fi
+        done
+        echo "Helm charts pushed."
+    fi
+    rm -rf "${helm_tmp_pkg}"
+    trap - EXIT
+}
+
+# ---------------------------------------------------------------------------
+# 6. GitHub Pages / webapp / accelerator
+# ---------------------------------------------------------------------------
+release_github_pages() {
+    echo ""
+    echo "--- GitHub Pages / webapp / accelerator ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building online-editor, 
chrome-extension-pack-kogito-kie-editors, kie-sandbox-accelerator-quarkus..."
+        export 
ONLINE_EDITOR__buildInfo="${ONLINE_EDITOR__buildInfo:-${VERSION}}"
+        export 
ONLINE_EDITOR__extendedServicesCompatibleVersion="${ONLINE_EDITOR__extendedServicesCompatibleVersion:-${VERSION}}"
+        export 
ONLINE_EDITOR__corsProxyUrl="${ONLINE_EDITOR__corsProxyUrl:-https://cors-proxy-kie-sandbox.rhba-0ad6762cc85bcef5745bb684498c2436-0000.us-south.containers.appdomain.cloud}";
+        export 
ONLINE_EDITOR__quarkusAcceleratorGitRepoUrl="${ONLINE_EDITOR__quarkusAcceleratorGitRepoUrl:-https://github.com/apache/incubator-kie-sandbox-quarkus-accelerator.git}";
+        export 
ONLINE_EDITOR__quarkusAcceleratorGitRef="${ONLINE_EDITOR__quarkusAcceleratorGitRef:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageRegistry="${ONLINE_EDITOR__devDeploymentBaseImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageAccount="${ONLINE_EDITOR__devDeploymentBaseImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageName="${ONLINE_EDITOR__devDeploymentBaseImageName:-incubator-kie-sandbox-dev-deployment-base}"
+        export 
ONLINE_EDITOR__devDeploymentBaseImageTag="${ONLINE_EDITOR__devDeploymentBaseImageTag:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageName="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageName:-incubator-kie-sandbox-dev-deployment-dmn-form-webapp}"
+        export 
ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag="${ONLINE_EDITOR__devDeploymentDmnFormWebappImageTag:-${VERSION}}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageRegistry:-docker.io}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageAccount:-apache}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageName:-incubator-kie-sandbox-dev-deployment-quarkus-blank-app}"
+        export 
ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag="${ONLINE_EDITOR__devDeploymentQuarkusBlankAppImageTag:-${VERSION}}"
+        pnpm \
+            -F "@kie-tools/online-editor..." \
+            -F "@kie-tools/chrome-extension-pack-kogito-kie-editors..." \
+            -F "@kie-tools/kie-sandbox-accelerator-quarkus..." \
+            build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        for src_path_suffix in \
+            
"packages/online-editor/dist:apache-kie-${VERSION}-incubating-sandbox-webapp.zip"
 \
+            
"packages/kie-sandbox-accelerator-quarkus/dist:apache-kie-${VERSION}-incubating-sandbox-accelerator-quarkus.zip"
+        do
+            local src="${REPO_ROOT}/${src_path_suffix%%:*}"
+            local zip_name="${src_path_suffix##*:}"
+            if [[ -d "${src}" ]]; then
+                echo "  ZIP  ${zip_name}"
+                (cd "${src}" && zip -r "${ARTIFACTS_DIR}/${zip_name}" .)
+            else
+                echo "  SKIP  ${zip_name} (${src} not found)"
+            fi
+        done
+        echo "RC artifacts in: ${ARTIFACTS_DIR}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${GITHUB_TOKEN:-}" ]]; then
+            echo "ERROR: GITHUB_TOKEN is required for publishing to GitHub 
Pages"
+            return 1
+        fi
+
+        local 
kogito_online_repo="https://github.com/apache/incubator-kie-kogito-online.git";
+        local 
accelerator_repo="https://github.com/apache/incubator-kie-sandbox-quarkus-accelerator.git";
+        local tmp_dir
+        tmp_dir=$(mktemp -d)
+        trap "rm -rf ${tmp_dir}" EXIT
+
+        # Write a credential store file so the token is never exposed on the
+        # command line (avoids leaking it via process listings or CI log 
echoing).
+        local git_cred_file="${tmp_dir}/.git-credentials"
+        echo "https://x-token:${GITHUB_TOKEN}@github.com"; > "${git_cred_file}"
+        chmod 600 "${git_cred_file}"
+        local git_auth=(-c "credential.helper=store --file=${git_cred_file}")
+
+        git "${git_auth[@]}" clone --branch gh-pages --depth 1 
"${kogito_online_repo}" "${tmp_dir}/kogito-online"
+        (
+            cd "${tmp_dir}/kogito-online"
+            git config user.email "[email protected]"
+            git config user.name "Apache KIE Release Bot"
+            git config credential.helper "store --file=${git_cred_file}"
+            # Exclude .git so subsequent git operations in this subshell are 
not broken.
+            find . -maxdepth 1 ! -name '.' ! -name '.git' ! -name 'dev' ! 
-name 'editors' ! -name 'standalone' \
+                ! -name 'chrome-extension' ! -name '.nojekyll' ! -name 'CNAME' 
-exec rm -rf {} +
+
+            local online_dist="${REPO_ROOT}/packages/online-editor/dist"
+            if [[ -d "${online_dist}" ]]; then

Review Comment:
   With `--publish --skip-build`, a missing Online Editor build still reaches 
this deletion, but the replacement copy is skipped. The script then commits and 
pushes the removal of the live site's root assets. Validate the required 
output, starting with `dist/index.html`, before deleting any live-site files 
and abort if it is absent.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";
+
+ARTIFACTS_DIR="${REPO_ROOT}/release-artifacts"
+
+echo "=========================================="
+echo "KIE Tools Release"
+echo "Version:    ${VERSION}"
+echo "RC mode:    ${RC_MODE}"
+echo "Publish:    ${PUBLISH}"
+echo "Skip build: ${SKIP_BUILD}"
+echo "Registry:   ${REGISTRY}"
+echo "=========================================="
+
+cd "${REPO_ROOT}"
+if [[ "${SKIP_BUILD}" == "false" ]]; then
+    echo ""
+    echo "--- Bumping workspace version to ${VERSION} ---"
+    pnpm update-version-to "${VERSION}"
+    pnpm update-kogito-version-to --maven "${VERSION}"
+    pnpm update-stream-name-to "${VERSION}"
+fi
+
+# ---------------------------------------------------------------------------
+# Helper: sign and upload a single asset to a GitHub Release
+# ---------------------------------------------------------------------------
+upload_github_asset() {
+    local file="$1"
+    local asset_name="$2"
+    local content_type="${3:-application/octet-stream}"
+    if [[ ! -f "${file}" ]]; then
+        echo "  SKIP  ${asset_name} (file not found)"
+        return
+    fi
+    echo "  UPLOAD  ${asset_name}"
+    curl -sSf \
+        -H "Authorization: token ${GITHUB_TOKEN}" \
+        -H "Content-Type: ${content_type}" \
+        --data-binary "@${file}" \
+        "${UPLOAD_URL}?name=${asset_name}"
+}
+
+# ---------------------------------------------------------------------------
+# 1. NPM packages
+# ---------------------------------------------------------------------------
+release_npm_packages() {
+    echo ""
+    echo "--- NPM packages ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building public NPM packages..."
+        local filter
+        filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)..."; fi')
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        local tmp_dir="${ARTIFACTS_DIR}/npm-packages-tmp"
+        local 
zip_file="apache-kie-${VERSION}-incubating-tools-npm-packages.zip"
+        mkdir -p "${tmp_dir}" "${ARTIFACTS_DIR}"
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c "pnpm pack --pack-destination 
${tmp_dir}"
+        (cd "${tmp_dir}" && zip -r "${ARTIFACTS_DIR}/${zip_file}" .)
+        rm -rf "${tmp_dir}"
+        echo "RC artifact: ${ARTIFACTS_DIR}/${zip_file}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${NPM_TOKEN:-}" ]]; then
+            echo "ERROR: NPM_TOKEN is required for publishing npm packages"
+            return 1
+        fi
+        local npmrc_tmp
+        npmrc_tmp=$(mktemp)
+        trap "rm -f '${npmrc_tmp}'" EXIT
+        echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${npmrc_tmp}"
+        export NPM_CONFIG_USERCONFIG="${npmrc_tmp}"
+
+        local pub_filter
+        pub_filter=$(pnpm -r exec bash -c \
+            'if [[ $(jq -r ".private" package.json) != "true" ]]; then echo 
"-F $(jq -r ".name" package.json)"; fi')
+        pnpm ${pub_filter} exec bash -c '
+            PKG_NAME=$(jq -r ".name" package.json)
+            if ! npm view ${PKG_NAME}@'"${VERSION}"' name --userconfig 
"'"${npmrc_tmp}"'" &>/dev/null; then
+                echo "Publishing ${PKG_NAME}@'"${VERSION}"'"
+                pnpm publish --no-git-checks --access public --userconfig 
"'"${npmrc_tmp}"'"
+            else
+                echo "Skipping ${PKG_NAME}@'"${VERSION}"' (already published)"
+            fi
+        '
+        rm -f "${npmrc_tmp}"
+        trap - EXIT
+        echo "NPM packages published."
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 2. Chrome extensions
+# ---------------------------------------------------------------------------
+release_chrome_extensions() {
+    echo ""
+    echo "--- Chrome extensions ---"
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        echo "Building Chrome extensions..."
+        pnpm -F "chrome-extension-pack-kogito-kie-editors..." build:prod
+    fi
+
+    local kie_editors_zip
+    kie_editors_zip=$(find 
"packages/chrome-extension-pack-kogito-kie-editors/dist" \
+        -maxdepth 1 -name "chrome_extension_kogito_kie_editors_*.zip" 
2>/dev/null | head -1 || true)
+    if [[ -z "${kie_editors_zip}" ]]; then
+        echo "ERROR: chrome_extension_kogito_kie_editors_*.zip not found — was 
the build successful?"
+        return 1
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then
+        mkdir -p "${ARTIFACTS_DIR}"
+        cp "${kie_editors_zip}" \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension.zip"
+
+        local tmp_kie="${ARTIFACTS_DIR}/kie-editors-tmp"
+        mkdir -p "${tmp_kie}"
+        [[ -d "packages/chrome-extension-pack-kogito-kie-editors/dist/fonts" 
]] && cp -r packages/chrome-extension-pack-kogito-kie-editors/dist/fonts 
"${tmp_kie}/"
+        cp packages/chrome-extension-pack-kogito-kie-editors/dist/*-envelope.* 
"${tmp_kie}/" 2>/dev/null || true
+        (cd "${tmp_kie}" && zip -r \
+            
"${ARTIFACTS_DIR}/apache-kie-${VERSION}-incubating-business-automation-chrome-extension-editors.zip"
 .)
+        rm -rf "${tmp_kie}"
+        echo "RC artifacts created in: ${ARTIFACTS_DIR}"
+    fi
+
+    if [[ "${PUBLISH}" == "true" ]]; then
+        if [[ -z "${CHROME_CLIENT_ID:-}" || -z "${CHROME_CLIENT_SECRET:-}" || 
-z "${CHROME_REFRESH_TOKEN:-}" ]]; then
+            echo "ERROR: CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, and 
CHROME_REFRESH_TOKEN are required"
+            return 1
+        fi
+
+        local token_res
+        token_res=$(curl -sSf -X POST "https://oauth2.googleapis.com/token"; \
+            -d "client_id=${CHROME_CLIENT_ID}" \
+            -d "client_secret=${CHROME_CLIENT_SECRET}" \
+            -d "refresh_token=${CHROME_REFRESH_TOKEN}" \
+            -d "grant_type=refresh_token")
+        local access_token
+        access_token=$(echo "${token_res}" | jq -r '.access_token // empty')
+
+        if [[ -z "${access_token}" ]]; then
+            echo "ERROR: Failed to obtain Chrome Web Store OAuth token: 
${token_res}"
+            return 1
+        fi
+
+        _chrome_upload() {
+            local ext_id="$1" zip="$2"
+            local res state
+            res=$(curl -sSf -X PUT \
+                
"https://www.googleapis.com/upload/chromewebstore/v1.1/items/${ext_id}"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -T "${zip}")
+            state=$(echo "${res}" | jq -r '.uploadState // empty')
+            if [[ "${state}" != "SUCCESS" ]]; then
+                echo "ERROR: Chrome Web Store upload failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+        _chrome_publish() {
+            local ext_id="$1"
+            local res pub_status
+            res=$(curl -sSf -X POST \
+                
"https://www.googleapis.com/chromewebstore/v1.1/items/${ext_id}/publish"; \
+                -H "Authorization: Bearer ${access_token}" \
+                -H "x-goog-api-version:2" \
+                -H "Content-Length: 0")
+            pub_status=$(echo "${res}" | jq -r '.status[0] // .status // 
empty')
+            if [[ "${pub_status}" != "OK" && "${pub_status}" != 
"PUBLISHED_WITH_FRICTION_WARNING" ]]; then
+                echo "ERROR: Chrome Web Store publish failed for ${ext_id}: 
${res}"
+                return 1
+            fi
+        }
+
+        if [[ -n "${CHROME_KIE_EDITORS_EXTENSION_ID:-}" ]]; then
+            _chrome_upload "${CHROME_KIE_EDITORS_EXTENSION_ID}" 
"${kie_editors_zip}"
+            _chrome_publish "${CHROME_KIE_EDITORS_EXTENSION_ID}"
+            echo "Chrome extension published."
+        else
+            echo "WARN: CHROME_KIE_EDITORS_EXTENSION_ID not set — skipping 
Chrome publish"
+        fi
+    fi
+}
+
+# ---------------------------------------------------------------------------
+# 3. VSCode extensions
+# ---------------------------------------------------------------------------
+release_vscode() {
+    echo ""
+    echo "--- VSCode extensions ---"
+
+    declare -A EXTENSIONS=(
+        ["bpmn-vscode-extension"]="bpmn-vscode-extension"
+        ["dmn-vscode-extension"]="dmn-vscode-extension"
+        ["drl-vscode-extension"]="drl-vscode-extension"
+        ["pmml-vscode-extension"]="pmml-vscode-extension"
+        ["vscode-extension-kogito-bundle"]="kogito-bundle-vscode-extension"
+        
["vscode-extension-kie-ba-bundle"]="business-automation-bundle-vscode-extension"
+        
["extended-services-vscode-extension"]="extended-services-vscode-extension"
+    )
+
+    if [[ "${SKIP_BUILD}" == "false" ]]; then
+        local filter=""
+        for ext in "${!EXTENSIONS[@]}"; do filter="${filter} -F ${ext}..."; 
done
+        echo "Building VSCode extensions..."
+        pnpm ${filter} build:prod
+    fi
+
+    if [[ "${RC_MODE}" == "true" ]]; then

Review Comment:
   With `--skip-build`, an extension with no VSIX is silently omitted from RC 
collection and executes zero iterations during publication, yet the component 
reports success. Check every expected extension for a package before collecting 
or publishing any VSIX files. The publication loop at line 333 needs the same 
validation.



##########
scripts/release/release-all.sh:
##########
@@ -0,0 +1,810 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+set -euo pipefail
+
+# Release script for Apache KIE Tools — builds and optionally releases all 
components
+# under a single unified build.
+#
+# Usage:
+#   ./release-all.sh <version>            # dry run (build only, nothing 
published)
+#   ./release-all.sh <version> --rc       # build + collect Apache RC artifacts
+#   ./release-all.sh <version> --publish  # build + publish to all public 
registries
+#
+# Optional flags:
+#   --skip-build           Skip pnpm build:prod (use existing dist/ output)
+#   --upload-url <url>     GitHub Release upload URL (required by --publish 
for binary assets)
+#   --registry <url>       Container registry (default: docker.io/apache)
+#   --git-ref <ref>        Git reference for source tarball (default: HEAD)
+#
+# Credentials (required only for --publish, sourced from env):
+#   NPM_TOKEN, VSCE_PAT,
+#   CHROME_CLIENT_ID, CHROME_CLIENT_SECRET, CHROME_REFRESH_TOKEN, 
CHROME_KIE_EDITORS_EXTENSION_ID,
+#   DOCKER_USERNAME, DOCKER_PASSWORD,
+#   HELM_REGISTRY (default: docker.io/apache), HELM_USERNAME (optional), 
HELM_PASSWORD (optional),
+#   GITHUB_TOKEN
+
+export KIE_TOOLS_BUILD__runLinters=false
+export KIE_TOOLS_BUILD__runTests=false
+export KIE_TOOLS_BUILD__runEndToEndTests=false
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+# ---------------------------------------------------------------------------
+# Argument parsing
+# ---------------------------------------------------------------------------
+VERSION="${1:-}"
+PUBLISH=false
+RC_MODE=false
+SKIP_BUILD=false
+UPLOAD_URL=""
+REGISTRY="docker.io/apache"
+GIT_REF="HEAD"
+
+shift || true
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --publish)     PUBLISH=true;        shift ;;
+        --rc)          RC_MODE=true;        shift ;;
+        --skip-build)  SKIP_BUILD=true;     shift ;;
+        --upload-url)  UPLOAD_URL="${2:-}";  shift 2 ;;
+        --registry)    REGISTRY="${2:-}";   shift 2 ;;
+        --git-ref)     GIT_REF="${2:-}";    shift 2 ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] 
[--upload-url <url>] [--registry <url>] [--git-ref <ref>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "Usage: $0 <version> [--publish] [--rc] [--skip-build] [--upload-url 
<url>] [--registry <url>]"
+    echo "Example: $0 10.2.0 --rc"
+    echo "Example: $0 10.2.0 --publish"
+    exit 1
+fi
+
+if [[ "${PUBLISH}" == "true" && -z "${UPLOAD_URL}" ]]; then
+    echo "ERROR: --upload-url is required when --publish is specified."
+    exit 1
+fi
+
+# Production defaults for Chrome extension and Online Editor builds
+export 
CHROME_EXTENSION__routerTargetOrigin="${CHROME_EXTENSION__routerTargetOrigin:-https://apache.github.io}";
+export 
CHROME_EXTENSION__routerRelativePath="${CHROME_EXTENSION__routerRelativePath:-incubator-kie-kogito-online/chrome-extension/${VERSION}}"
+export 
CHROME_EXTENSION__manifestFile="${CHROME_EXTENSION__manifestFile:-manifest.prod.json}"
+export 
CHROME_EXTENSION__onlineEditorUrl="${CHROME_EXTENSION__onlineEditorUrl:-https://apache.github.io/incubator-kie-kogito-online}";
+
+ARTIFACTS_DIR="${REPO_ROOT}/release-artifacts"
+
+echo "=========================================="
+echo "KIE Tools Release"
+echo "Version:    ${VERSION}"
+echo "RC mode:    ${RC_MODE}"
+echo "Publish:    ${PUBLISH}"
+echo "Skip build: ${SKIP_BUILD}"
+echo "Registry:   ${REGISTRY}"
+echo "=========================================="
+
+cd "${REPO_ROOT}"
+if [[ "${SKIP_BUILD}" == "false" ]]; then
+    echo ""
+    echo "--- Bumping workspace version to ${VERSION} ---"
+    pnpm update-version-to "${VERSION}"
+    pnpm update-kogito-version-to --maven "${VERSION}"
+    pnpm update-stream-name-to "${VERSION}"
+fi
+
+# ---------------------------------------------------------------------------
+# Helper: sign and upload a single asset to a GitHub Release
+# ---------------------------------------------------------------------------
+upload_github_asset() {
+    local file="$1"
+    local asset_name="$2"
+    local content_type="${3:-application/octet-stream}"
+    if [[ ! -f "${file}" ]]; then
+        echo "  SKIP  ${asset_name} (file not found)"
+        return

Review Comment:
   A missing asset returns success here. With `--publish --skip-build`, an 
existing but incomplete upload-service `dist/` directory therefore causes 
required platform binaries to be skipped while the component reports successful 
publication. Return a nonzero status for missing required assets so the release 
cannot finish successfully without them.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to