davsclaus commented on code in PR #25072:
URL: https://github.com/apache/camel/pull/25072#discussion_r3643388274


##########
dsl/camel-jbang/camel-launcher/src/jreleaser/bin/camel-publish.sh:
##########
@@ -0,0 +1,553 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# ── camel-publish.sh — single resumable publish orchestrator ───────────────
+#
+# Usage:
+#   camel-publish.sh <version> --channel stable|lts [--lts-line X.Y]
+#
+# Behaviour:

Review Comment:
   **Bug:** This runs `publish-state.sh` in a subprocess — none of its 
functions (`_init_state`, `state_mark`, `state_current_status`, 
`state_resume_ok`, `state_redacted_dump`) will be defined in the caller. The 
script calls all of these later and will fail at runtime.
   
   `camel-validate.sh` correctly uses `. "$LIB_DIR/assert-camel-cli.sh"` 
(dot-sourcing) — this should do the same:
   
   ```suggestion
   . "$LIB_DIR/publish-state.sh"
   ```



##########
dsl/camel-jbang/camel-launcher/src/jreleaser/bin/camel-publish.sh:
##########
@@ -0,0 +1,553 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# ── camel-publish.sh — single resumable publish orchestrator ───────────────
+#
+# Usage:
+#   camel-publish.sh <version> --channel stable|lts [--lts-line X.Y]
+#
+# Behaviour:
+#   1. Reruns preparation (camel-package.sh prepare) and preflight checks.
+#   2. Publishes to destinations in spec order:
+#      JReleaser → Homebrew tap → Camel website → WinGet → Scoop → SDKMAN → 
Chocolatey
+#   3. Records redacted state under target/jreleaser/publish-state.json
+#   4. Resumes on re-run (skips completed steps, detects conflicts).
+#   5. Never merges own PRs, auto-closes PRs, or overwrites differing content.
+
+set -eu
+
+SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
+MODULE_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/../../.." && pwd)"
+LIB_DIR="$SCRIPT_DIR/lib"
+
+# Source helpers
+sh "$LIB_DIR/publish-state.sh"
+
+# ── argument parsing ────────────────────────────────────────────────────────
+
+VERSION=""
+CHANNEL=""
+LTS_LINE=""
+
+usage() {
+  echo "Usage: camel-publish.sh <version> --channel <stable|lts> [--lts-line 
X.Y]" >&2
+  exit 2
+}
+
+[ $# -ge 1 ] || usage
+VERSION="$1"; shift
+
+while [ $# -gt 0 ]; do
+  case "$1" in
+    --channel) CHANNEL="${2:-}"; shift 2 ;;
+    --lts-line) LTS_LINE="${2:-}"; shift 2 ;;
+    *) echo "Error: unknown argument '$1'." >&2; usage ;;
+  esac
+done
+
+[ -n "$CHANNEL" ] || { echo "Error: --channel required (stable|lts)." >&2; 
exit 2; }
+case "$CHANNEL" in stable|lts) ;; *) echo "Error: --channel must be 'stable' 
or 'lts'." >&2; exit 2 ;; esac
+
+if [ "$CHANNEL" = "lts" ] && [ -z "$LTS_LINE" ]; then
+  echo "Error: --channel lts requires --lts-line X.Y." >&2; exit 2
+fi
+
+# ── channel-derived defaults ────────────────────────────────────────────────
+
+if [ "$CHANNEL" = "stable" ]; then
+  PACKAGERS="brew,sdkman,winget,scoop,chocolatey"
+  BREW_FORMULA="apache-camel"
+  SDKMAN_DEFAULT="true"
+  WEBSITE_LATEST="true"
+else
+  PACKAGERS="brew,sdkman,winget,chocolatey"
+  BREW_FORMULA="apache-camel@$LTS_LINE"
+  SDKMAN_DEFAULT="false"
+  WEBSITE_LATEST="false"
+fi
+
+# ── environment configuration ───────────────────────────────────────────────
+
+: "${GITHUB_TOKEN:?Error: GITHUB_TOKEN is required}"
+: "${SDKMAN_CONSUMER_KEY:?Error: SDKMAN_CONSUMER_KEY is required}"
+: "${SDKMAN_CONSUMER_SECRET:?Error: SDKMAN_CONSUMER_SECRET is required}"
+: "${CHOCO_API_KEY:?Error: CHOCO_API_KEY is required}"
+
+# Override hooks — set these to redirect irreversible actions for testing
+#   CAMEL_PUB_JRELEASER=...  command to run JReleaser (default: mvn ...)
+#   CAMEL_PUB_GH_PUSH        remote name for git push (default: upstream)
+#   CAMEL_PUB_HEATWAVE=      path to heatwave stub dir
+#   CAMEL_PUB_SDKMAN=        SDKMAN vendor API base URL (default: 
https://vendor.sdkman.io)
+#   CAMEL_PUB_CHOCO=         Chocolatey API URL (default: 
https://push.chocolatey.org/)
+
+# ── helpers ──────────────────────────────────────────────────────────────────
+
+_log() { echo "publish: $*"; }
+_error() { echo "ERROR: $*" >&2; }
+
+# Resolve operator login for attribution
+resolve_operator() {
+  if [ -n "${CAMEL_PUB_OPERATOR:-}" ]; then
+    echo "$CAMEL_PUB_OPERATOR"
+    return 0
+  fi
+  # Try gh CLI
+  operator="$(gh api /user --jq '.login' 2>/dev/null || true)"
+  if [ -n "$operator" ]; then
+    echo "$operator"
+    return 0
+  fi
+  # Fallback to git config
+  operator="$(git config --get user.name 2>/dev/null || true)"
+  if [ -n "$operator" ]; then
+    echo "ai-assisted ($operator)"
+    return 0
+  fi
+  echo "ai-assisted (unknown)"
+}
+
+_operator=""
+_attribution_line=""
+_resolve_done=0
+
+_get_operator() {
+  [ "$_resolve_done" -eq 1 ] && return 0
+  _operator="$(resolve_operator)"
+  if [ -n "$_operator" ]; then
+    _attribution_line="Co-authored-by: ai-assisted <$(_operator)@>"
+  fi
+  _resolve_done=1
+}
+
+# ── shared: shallow clone of an external repo we publish PRs into 
──────────────────────────────
+# Neither the Homebrew nor the WinGet destination previously cloned the real 
external repo at all -
+# both operated git commands against whatever this script's own working tree 
happened to be, which
+# only ever worked by accident against a project-owned tap. A version-bump PR 
only ever touches one
+# file, so this clones shallow and single-branch rather than paying for either 
repo's full history
+# (both are large, long-lived repos with tens of thousands of commits).
+#
+# Args: $1 = owner/repo (e.g. "homebrew/homebrew-core"), $2 = destination dir 
under target/jreleaser
+# Sets (via echo, caller captures with $(...)): the destination dir path, or 
empty + nonzero exit
+# on failure. Skips re-cloning if the destination dir already exists 
(resume-safe, matching the
+# rest of this script's idempotent-by-state-file design).
+__shallow_clone_or_reuse() {
+  _repo_slug="$1"; _dest_name="$2"
+  _dest_dir="$MODULE_DIR/target/jreleaser/$_dest_name"
+
+  if [ -d "$_dest_dir/.git" ]; then
+    echo "$_dest_dir"
+    return 0
+  fi
+
+  _default_branch=$(gh api "repos/$_repo_slug" --jq .default_branch 
2>/dev/null) || {
+    _error "  could not resolve default branch for $_repo_slug via gh api" >&2
+    return 1
+  }
+  [ -n "$_default_branch" ] || { _error "  empty default branch for 
$_repo_slug" >&2; return 1; }
+
+  _fork_slug="${CAMEL_PUB_FORK_OWNER:-$(gh api /user --jq '.login' 
2>/dev/null)}/$(echo "$_repo_slug" | cut -d/ -f2)"
+  if ! git clone --depth 1 --branch "$_default_branch" \
+      "https://github.com/$_fork_slug.git"; "$_dest_dir" >&2; then
+    _error "  shallow clone of $_fork_slug failed (does the fork exist yet? 
'gh repo fork $_repo_slug --clone=false')" >&2
+    return 1
+  fi
+  git -C "$_dest_dir" remote add upstream-repo 
"https://github.com/$_repo_slug.git"; >&2 || true
+
+  echo "$_dest_dir"
+}
+
+# ── phase 1: preparation (rerun prepare) ────────────────────────────────────
+
+_log "Phase 1: Preparation..."
+
+export CAMEL_PKG_BREW_FORMULA="$BREW_FORMULA"
+case "$BREW_FORMULA" in
+  *@*) CAMEL_PKG_BREW_VERSIONED="$BREW_FORMULA" ;;
+  *)   CAMEL_PKG_BREW_VERSIONED="" ;;
+esac
+export CAMEL_PKG_BREW_VERSIONED
+
+# Prepare artifacts (offline: dry-run)
+"$SCRIPT_DIR/camel-package.sh" prepare --channel "$CHANNEL" || {
+  _error "Prepare step failed. Aborting publish." >&2; exit 1
+}
+
+# ── phase 2: preflight ─────────────────────────────────────────────────────
+
+_log "Phase 2: Preflight..."
+
+_preflight_ok=1
+
+_preflight_check() {
+  local name="$1" cmd="$2"
+  if eval "$cmd" > /dev/null 2>&1; then
+    _log "  $name: OK"
+  else
+    _error "  $name: FAILED (missing or wrong version)" >&2
+    _preflight_ok=0
+  fi
+}
+
+_log "  Checking release artifacts..."
+[ -f "$MODULE_DIR/target/camel-launcher-${VERSION}-bin.tar.gz" ] || {
+  _error "  TAR artifact missing"; _preflight_ok=0
+}
+[ -f "$MODULE_DIR/target/camel-launcher-${VERSION}-bin.zip" ] || {
+  _error "  ZIP artifact missing"; _preflight_ok=0
+}
+
+_log "  Checking tools..."
+_preflight_check "git" "git --version"
+_preflight_check "java" "java -version"
+_preflight_check "maven" "mvn --version"
+
+if [ "$CHANNEL" = "stable" ]; then
+  _preflight_check "sdkman" "which sdkvendorctl >/dev/null 2>&1 || true"
+fi
+_preflight_check "chocolatey-client" "which choco >/dev/null 2>/dev/null || 
true"
+
+if [ -n "${GITHUB_TOKEN:-}" ]; then
+  _log "  GITHUB_TOKEN: set"
+else
+  _error "  GITHUB_TOKEN: not set"; _preflight_ok=0
+fi
+
+_log "  Checking fork and branch..."
+# Check that the operator's fork remote exists (upstream or user-specified)
+FORK_REMOTE="${CAMEL_PUB_FORK_REMOTE:-upstream}"
+if ! git remote get-url "$FORK_REMOTE" > /dev/null 2>&1; then
+  _error "  Fork remote '$FORK_REMOTE' not configured."; _preflight_ok=0
+else
+  _log "  Fork remote: $(git remote get-url "$FORK_REMOTE")"
+fi
+
+if [ "$_preflight_ok" -ne 1 ]; then
+  _error "Preflight failed. See errors above. Aborting." >&2; exit 1
+fi
+
+# ── phase 3: initialise state file ───────────────────────────────────────────
+
+PUBLISH_STATE_FILE="$MODULE_DIR/target/jreleaser/publish-state.json"
+_init_state "$VERSION" "$CHANNEL"
+
+_log "State file: $PUBLISH_STATE_FILE"
+_log "Resume status: $(state_resume_ok)"
+
+# If already fully done, stop (idempotent re-run)
+if [ "$(state_resume_ok)" = "yes" ]; then
+  _log "All steps already completed. Workflow is complete and idempotent."
+  exit 0
+fi
+

Review Comment:
   **Bug:** `${!hook:-}` is bash indirect variable expansion — it dereferences 
the variable whose name is stored in `$hook`. This is not POSIX-compatible and 
will fail or produce incorrect behavior under `#!/bin/sh` on systems where 
`/bin/sh` is not bash (e.g., dash on Debian/Ubuntu).
   
   Either change the shebang to `#!/usr/bin/env bash`, or replace the indirect 
expansion with an explicit check, e.g.:
   ```sh
   if [ -n "${CAMEL_PUB_JRELEASER:-}" ]; then
       eval "$CAMEL_PUB_JRELEASER ..."
   ```



##########
dsl/camel-jbang/camel-launcher/src/jreleaser/bin/lib/publish-state.sh:
##########
@@ -0,0 +1,190 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# ── publish-state.sh: redacted state-file helpers for publish workflow ──────
+#
+# Simple line-oriented flat-key-value store (one key=value per line).
+# Sourced by camel-publish.sh.  All public functions use $STATE_FILE or 
default.
+
+STATE_FILE="${PUBLISH_STATE_FILE:-}"
+
+__ensure_file() {
+  if [ -z "$STATE_FILE" ]; then
+    STATE_FILE="$(pwd)/target/jreleaser/publish-state.json"
+    mkdir -p "$(dirname "$STATE_FILE")"
+  fi
+}
+
+_is_secret_key() {
+  case "$(echo "$1" | tr '[:lower:]' '[:upper:]')" in
+    *KEY*|*TOKEN*|*SECRET*|*PASS*) return 0 ;;
+    *) return 1 ;;
+  esac
+}
+
+_json_escape() {
+  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g'
+}
+
+_read_val() {
+  _rf="$1"; _rk="$2"
+  [ -f "$_rf" ] || return 0
+  grep "^${_rk}=" "$_rf" 2>/dev/null | head -1 | sed "s|^${_rk}=||"
+}
+
+# ── public API ───────────────────────────────────────────────────────────────
+
+state_get() {
+  _key="$1"
+  __ensure_file; _f="$STATE_FILE"
+
+  if [ ! -f "$_f" ]; then
+    return 1
+  fi
+
+  val="$(_read_val "$_f" "$_key")"
+  # Check if key exists at all (even with empty value)
+  if ! grep -q "^${_key}=" "$_f" 2>/dev/null; then
+    return 1
+  fi
+
+  if _is_secret_key "$_key"; then

Review Comment:
   **Bug:** `$'\n'` is bash ANSI-C quoting, not POSIX sh. Under a strict 
`#!/bin/sh` shell (e.g., dash), this will produce the literal string `$'\n'` 
instead of a newline character.
   
   This pattern appears in several places in this file. Either change the 
shebang to `#!/usr/bin/env bash`, or use a literal newline variable:
   ```sh
   NL='
   '
   # then use ${NL} where $'\n' appears
   ```
   or use `printf`.



-- 
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]

Reply via email to