name: Check Node.js Version Drift on: workflow_dispatch: schedule: # Runs weekly on Monday at 06:00 UTC - cron: "0 6 * * 1" permissions: contents: read issues: write env: REPORT_FILE: node-version-report.md jobs: check-node-versions: if: github.repository == 'community-scripts/ProxmoxVE' runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 with: ref: main - name: Check upstream Node.js versions env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail echo "================================================" echo " Checking Node.js version drift in install scripts" echo "================================================" # Collect results declare -a mismatches=() declare -a results=() total=0 skipped=0 for script in install/*-install.sh; do [[ ! -f "$script" ]] && continue # Check if script uses setup_nodejs if ! grep -q 'setup_nodejs' "$script"; then continue fi total=$((total + 1)) slug=$(basename "$script" | sed 's/-install\.sh$//') # Extract Source URL from header (lines 1-10) source_url=$(head -20 "$script" | grep -oP '(?<=# Source: )https://github\.com/[^ ]+' | head -1) if [[ -z "$source_url" ]]; then skipped=$((skipped + 1)) results+=("| $slug | - | - | - | - | ⏭️ No GitHub source |") continue fi # Extract owner/repo from URL repo=$(echo "$source_url" | sed -E 's|https://github\.com/||; s|/$||; s|\.git$||') if [[ -z "$repo" || "$repo" != */* ]]; then skipped=$((skipped + 1)) results+=("| $slug | - | - | - | - | ⏭️ Invalid repo: $repo |") continue fi # Extract current NODE_VERSION from script # Handles: NODE_VERSION="22", NODE_VERSION="24", or dynamic (curl) current_version=$(grep -oP 'NODE_VERSION="(\d+)"' "$script" | head -1 | grep -oP '\d+' || echo "") if [[ -z "$current_version" ]]; then # Check for dynamic version (e.g. curl-based) if grep -q 'NODE_VERSION=\$(' "$script"; then current_version="dynamic" else current_version="unset" fi fi # Fetch upstream package.json engines.node engines_node="" pkg_raw=$(curl -sf "https://raw.githubusercontent.com/${repo}/HEAD/package.json" 2>/dev/null || echo "") if [[ -n "$pkg_raw" ]]; then engines_node=$(echo "$pkg_raw" | jq -r '.engines.node // empty' 2>/dev/null || echo "") fi # Fetch upstream Dockerfile FROM node:XX dockerfile_node="" for branch in main master; do df_raw=$(curl -sf "https://raw.githubusercontent.com/${repo}/${branch}/Dockerfile" 2>/dev/null || echo "") if [[ -n "$df_raw" ]]; then # Extract node version from FROM node:XX or nodesource/setup_XX.x dockerfile_node=$(echo "$df_raw" | grep -oP '(?<=FROM node:)\d+' | head -1 || echo "") if [[ -z "$dockerfile_node" ]]; then dockerfile_node=$(echo "$df_raw" | grep -oP '(?<=nodesource/setup_)\d+' | head -1 || echo "") fi break fi done # Determine upstream recommended version upstream_version="" if [[ -n "$dockerfile_node" ]]; then upstream_version="$dockerfile_node" fi # Parse engines.node for minimum major version engines_min="" if [[ -n "$engines_node" ]]; then engines_min=$(echo "$engines_node" | grep -oP '\d+' | head -1 || echo "") fi # Determine status status="✅" if [[ "$current_version" == "dynamic" ]]; then status="🔄 Dynamic" elif [[ "$current_version" == "unset" ]]; then status="⚠️ No NODE_VERSION set" mismatches+=("$slug") elif [[ -n "$upstream_version" && "$current_version" != "$upstream_version" ]]; then status="🔸 Drift (upstream: $upstream_version)" mismatches+=("$slug") elif [[ -n "$engines_min" && "$engines_node" == ">="* ]]; then # Check if engines requires a higher version than what we use if [[ "$engines_min" -gt "$current_version" ]]; then status="🔴 Below minimum (engines: $engines_node)" mismatches+=("$slug") fi fi engines_display="${engines_node:-—}" dockerfile_display="${dockerfile_node:-—}" results+=("| $slug | $current_version | $engines_display | $dockerfile_display | $repo | $status |") done # Build report { echo "## Node.js Version Drift Report" echo "" echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "Scripts checked: $total | Skipped: $skipped | Mismatches: ${#mismatches[@]}" echo "" echo "| Script | Our Version | engines.node | Dockerfile | Upstream Repo | Status |" echo "|--------|-------------|-------------|------------|---------------|--------|" printf '%s\n' "${results[@]}" | sort echo "" if [[ ${#mismatches[@]} -gt 0 ]]; then echo "### Scripts requiring attention" echo "" for m in "${mismatches[@]}"; do echo "- \`$m\`" done else echo "All Node.js versions are in sync with upstream. ✅" fi } > "$REPORT_FILE" cat "$REPORT_FILE" # Export for next steps echo "mismatch_count=${#mismatches[@]}" >> "$GITHUB_OUTPUT" echo "total=$total" >> "$GITHUB_OUTPUT" id: check - name: Create or update issue on drift if: steps.check.outputs.mismatch_count != '0' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TITLE="[Automated] Node.js version drift detected" BODY=$(cat "$REPORT_FILE") # Check if issue already exists EXISTING=$(gh issue list --label "automated,dependencies" --search "$TITLE" --state open --json number --jq '.[0].number // empty') if [[ -n "$EXISTING" ]]; then gh issue edit "$EXISTING" --body "$BODY" echo "Updated existing issue #$EXISTING" else gh issue create \ --title "$TITLE" \ --body "$BODY" \ --label "automated,dependencies" echo "Created new issue" fi - name: Close issue if no drift if: steps.check.outputs.mismatch_count == '0' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TITLE="[Automated] Node.js version drift detected" EXISTING=$(gh issue list --label "automated,dependencies" --search "$TITLE" --state open --json number --jq '.[0].number // empty') if [[ -n "$EXISTING" ]]; then gh issue close "$EXISTING" --comment "All Node.js versions are now in sync with upstream. Closing automatically." echo "Closed issue #$EXISTING" fi