diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e95e4789..7ef61506a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,10 @@ on: permissions: contents: read +concurrency: + group: release-${{ github.event.inputs.version || github.ref_name }} + cancel-in-progress: true + jobs: # ───────────────────────────────────────────────────────────────────────────── # Step 1: Determine the release version diff --git a/.github/workflows/update-codeql.yml b/.github/workflows/update-codeql.yml index 40f3686a1..28ebc605e 100644 --- a/.github/workflows/update-codeql.yml +++ b/.github/workflows/update-codeql.yml @@ -15,8 +15,7 @@ jobs: # # Compares the current CodeQL CLI version in qlt.conf.json against the latest # release from github/codeql-cli-binaries. If a newer version is available, - # downstream jobs orchestrate a full release using the same child workflows - # as release.yml, guarded by environment approval gates. + # downstream jobs orchestrate the update and PR creation. # ───────────────────────────────────────────────────────────────────────────── detect-update: name: Detect CodeQL CLI Update @@ -38,8 +37,21 @@ jobs: GH_TOKEN: ${{ github.token }} run: | echo "Checking latest CodeQL CLI version..." + + # Read current version from qlt.conf.json current_version=$(jq -r .CodeQLCLI qlt.conf.json) + + # Get latest release from codeql-cli-binaries latest_tag=$(gh release list --repo github/codeql-cli-binaries --json 'tagName,isLatest' --jq '.[] | select(.isLatest == true) | .tagName') + + # Validate that we found a latest release + if [ -z "${latest_tag}" ]; then + echo "❌ Error: Could not determine latest CodeQL CLI version from github/codeql-cli-binaries" >&2 + echo "No release marked as 'latest' was found. This may indicate an API issue or repository change." >&2 + echo "update_needed=false" >> $GITHUB_OUTPUT + exit 1 + fi + latest_clean="${latest_tag#v}" echo "Current CodeQL CLI version: ${current_version}" @@ -63,91 +75,115 @@ jobs: if [ "${{ steps.check-version.outputs.update_needed }}" == "true" ]; then echo "✅ Update available: ${{ steps.check-version.outputs.current_version }} → ${{ steps.check-version.outputs.latest_version }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Initiating release pipeline for \`v${{ steps.check-version.outputs.latest_version }}\`..." >> $GITHUB_STEP_SUMMARY + echo "Initiating update pipeline for \`${{ steps.check-version.outputs.version }}\`..." >> $GITHUB_STEP_SUMMARY else - echo "ℹ️ CodeQL CLI is already up-to-date. No release needed." >> $GITHUB_STEP_SUMMARY + echo "ℹ️ CodeQL CLI is already up-to-date. No changes needed." >> $GITHUB_STEP_SUMMARY fi # ───────────────────────────────────────────────────────────────────────────── - # Step 2: Create release tag + # Step 2: Update version, test, and create PR # - # Calls the same release-tag workflow used by release.yml. This ensures the - # version update, CodeQL installation, pack lock upgrade, unit tests, and tag - # creation all follow the same validated process. + # Updates all version-bearing files (qlt.conf.json, qlpack.yml files), + # installs CodeQL, upgrades pack lock files, compiles CDS files, runs unit + # tests, and creates a pull request with the changes. # - # The release-tag environment approval gate provides human-in-the-loop review - # before any changes are committed. + # This does NOT trigger the release pipeline. Merging the PR and creating a + # release tag is a separate, human-initiated step via release.yml. # ───────────────────────────────────────────────────────────────────────────── - ensure-tag: - name: Ensure Release Tag + create-pr: + name: Create Update Pull Request needs: detect-update if: needs.detect-update.outputs.update_needed == 'true' - permissions: - contents: write - uses: ./.github/workflows/release-tag.yml - with: - version: ${{ needs.detect-update.outputs.version }} - - # ───────────────────────────────────────────────────────────────────────────── - # Step 3: Publish and bundle CodeQL packs - # - # Calls the same release-codeql workflow used by release.yml. Publishes packs - # to GHCR and bundles them as artifacts for the GitHub Release. - # ───────────────────────────────────────────────────────────────────────────── - publish-codeql: - name: Publish CodeQL Packs - needs: [detect-update, ensure-tag] - if: needs.detect-update.outputs.update_needed == 'true' - permissions: - contents: read - packages: write - uses: ./.github/workflows/release-codeql.yml - with: - publish_codeql_packs: true - version: ${{ needs.detect-update.outputs.version }} - - # ───────────────────────────────────────────────────────────────────────────── - # Step 4: Create GitHub Release - # - # Downloads the CodeQL pack bundles and creates the GitHub Release with - # auto-generated release notes and attached pack artifacts. - # ───────────────────────────────────────────────────────────────────────────── - create-release: - name: Create GitHub Release - needs: [detect-update, ensure-tag, publish-codeql] - if: >- - always() && !failure() && !cancelled() - && needs.detect-update.outputs.update_needed == 'true' runs-on: ubuntu-latest permissions: contents: write + pull-requests: write steps: - - name: Release - Download CodeQL pack artifacts - uses: actions/download-artifact@v7 - with: - name: codeql-pack-bundles-${{ needs.detect-update.outputs.version }} - path: dist-packs + - name: Update - Checkout repository + uses: actions/checkout@v6 + + - name: Update - Update version in all files + run: | + LATEST="${{ needs.detect-update.outputs.latest_version }}" + echo "Updating all version-bearing files to ${LATEST}..." + ./scripts/update-release-version.sh "${LATEST}" - - name: Release - Create GitHub Release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + - name: Update - Install CodeQL via GitHub CLI + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + CODEQL_VERSION="${{ needs.detect-update.outputs.latest_version }}" + echo "Installing CodeQL CLI ${CODEQL_VERSION} via gh-codeql..." + gh extension install github/gh-codeql + gh codeql set-version "${CODEQL_VERSION}" + STUB_DIR="$HOME/.local/bin" + mkdir -p "${STUB_DIR}" + gh codeql install-stub "${STUB_DIR}/" + echo "${STUB_DIR}" >> "$GITHUB_PATH" + export PATH="${STUB_DIR}:${PATH}" + echo "CodeQL version: $(codeql version --format=terse)" + + - name: Update - Upgrade CodeQL pack lock files + run: ./scripts/upgrade-packs.sh + + - name: Update - Setup Node.js for CDS compilation + uses: actions/setup-node@v6 with: - files: | - dist-packs/*.tar.gz - generate_release_notes: true - tag_name: ${{ needs.detect-update.outputs.version }} + node-version: '20' + cache: 'npm' + cache-dependency-path: 'extractors/cds/tools/package-lock.json' - - name: Release - Summary + - name: Update - Compile CAP CDS files + run: ./extractors/cds/tools/workflow/cds-compilation-for-actions.sh + + - name: Update - Run CodeQL unit tests + env: + LGTM_INDEX_XML_MODE: all + LGTM_INDEX_FILETYPES: ".json:JSON\n.cds:JSON" + shell: bash + run: | + echo "Running CodeQL unit tests to validate update..." + codeql test run \ + --threads=0 \ + --strict-test-discovery \ + --additional-packs="${GITHUB_WORKSPACE}" \ + -- javascript/ + + - name: Update - Create Pull Request + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 + with: + title: 'Upgrade CodeQL CLI dependency to ${{ needs.detect-update.outputs.version }}' + body: | + This PR upgrades the CodeQL CLI version to ${{ needs.detect-update.outputs.version }}. + + **Changes made:** + - Updated `qlt.conf.json` (CodeQLCLI, CodeQLStandardLibrary, CodeQLCLIBundle) to `${{ needs.detect-update.outputs.latest_version }}` + - Updated all version-bearing qlpack.yml files to `${{ needs.detect-update.outputs.latest_version }}` + - Upgraded CodeQL pack lock files + - Compiled CAP CDS files + - CodeQL unit tests passed ✅ + + **To complete the release**, merge this PR and then trigger the release workflow + via `workflow_dispatch` on `release.yml` with version `${{ needs.detect-update.outputs.version }}`. + commit-message: 'Upgrade CodeQL CLI dependency to ${{ needs.detect-update.outputs.version }}' + delete-branch: true + branch: 'codeql/upgrade-to-${{ needs.detect-update.outputs.version }}' + + - name: Update - Summary run: | VERSION="${{ needs.detect-update.outputs.version }}" - RELEASE_NAME="${{ needs.detect-update.outputs.latest_version }}" - echo "## Automated Release Summary" >> $GITHUB_STEP_SUMMARY + CURRENT="${{ needs.detect-update.outputs.current_version }}" + LATEST="${{ needs.detect-update.outputs.latest_version }}" + echo "## CodeQL CLI Update Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Triggered by CodeQL CLI update: ${CURRENT} → ${LATEST}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Triggered by CodeQL CLI update: ${{ needs.detect-update.outputs.current_version }} → ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY + echo "| Property | Old Value | New Value |" >> $GITHUB_STEP_SUMMARY + echo "| -------- | --------- | --------- |" >> $GITHUB_STEP_SUMMARY + echo "| qlt.conf.json CodeQLCLI | ${CURRENT} | ${LATEST} |" >> $GITHUB_STEP_SUMMARY + echo "| qlpack.yml versions | ${CURRENT} | ${LATEST} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY - echo "| ---- | ------ |" >> $GITHUB_STEP_SUMMARY - echo "| Tag | ✅ ${VERSION} |" >> $GITHUB_STEP_SUMMARY - echo "| CodeQL pack publish | ✅ Published to GHCR |" >> $GITHUB_STEP_SUMMARY - echo "| GitHub Release | ✅ Created |" >> $GITHUB_STEP_SUMMARY + echo "A pull request has been created with these changes." >> $GITHUB_STEP_SUMMARY diff --git a/scripts/upgrade-packs.sh b/scripts/upgrade-packs.sh new file mode 100755 index 000000000..cf2db99a6 --- /dev/null +++ b/scripts/upgrade-packs.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -euo pipefail + +## upgrade-packs.sh +## Upgrade CodeQL pack dependencies for packs in the codeql-sap-js repository. +## +## This script upgrades lock files for both source and test packs, installing +## the latest compatible version of each dependency (ignoring existing lock files). +## +## Usage: +## ./scripts/upgrade-packs.sh +## ./scripts/upgrade-packs.sh --framework cap +## ./scripts/upgrade-packs.sh --framework ui5 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +FRAMEWORK="" + +usage() { + cat < Upgrade packs only for the specified framework + Valid values: cap, heuristic-models, ui5, ui5-webcomponents, xsjs + -h, --help Show this help message + +By default, the script upgrades packs for all frameworks. +EOF +} + +while [[ $# -gt 0 ]]; do + case $1 in + --framework) + if [[ $# -lt 2 || "${2-}" == -* ]]; then + echo "Error: --framework requires a value" >&2 + usage >&2 + exit 1 + fi + FRAMEWORK="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: Unknown option $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +## Validate framework if provided +VALID_FRAMEWORKS=("cap" "heuristic-models" "ui5" "ui5-webcomponents" "xsjs") +if [[ -n "${FRAMEWORK}" ]]; then + FRAMEWORK_VALID=false + for valid_fw in "${VALID_FRAMEWORKS[@]}"; do + if [[ "${FRAMEWORK}" == "${valid_fw}" ]]; then + FRAMEWORK_VALID=true + break + fi + done + + if [[ "${FRAMEWORK_VALID}" == false ]]; then + echo "Error: Invalid framework '${FRAMEWORK}'" >&2 + echo "Valid frameworks: ${VALID_FRAMEWORKS[*]}" >&2 + exit 1 + fi +fi + +cd "${REPO_ROOT}" + +## Upgrade a single pack given its qlpack.yml directory +upgrade_pack() { + local pack_dir="$1" + if [[ -d "${pack_dir}" ]]; then + echo "INFO: Running 'codeql pack upgrade' for '${pack_dir}'..." + codeql pack upgrade -- "${pack_dir}" + else + echo "WARNING: Directory '${pack_dir}' not found, skipping" >&2 + fi +} + +## Upgrade packs for a framework (all subdirectories that contain qlpack.yml) +upgrade_framework() { + local framework_path="$1" + echo "Upgrading packs for: ${framework_path}" + + # Find all qlpack.yml files under this framework and upgrade their packs + find "${REPO_ROOT}/${framework_path}" -name "qlpack.yml" -type f | sort | while read -r qlpack_file; do + local pack_dir + pack_dir=$(dirname "${qlpack_file}") + # Use relative path for cleaner output + local rel_path="${pack_dir#${REPO_ROOT}/}" + upgrade_pack "${rel_path}" + done +} + +if [[ -n "${FRAMEWORK}" ]]; then + case "${FRAMEWORK}" in + heuristic-models) + upgrade_framework "javascript/heuristic-models" + ;; + ui5-webcomponents) + upgrade_framework "javascript/frameworks/ui5-webcomponents" + ;; + *) + upgrade_framework "javascript/frameworks/${FRAMEWORK}" + ;; + esac +else + echo "Upgrading packs for all frameworks..." + upgrade_framework "javascript/frameworks/cap" + upgrade_framework "javascript/frameworks/ui5" + upgrade_framework "javascript/frameworks/ui5-webcomponents" + upgrade_framework "javascript/frameworks/xsjs" + upgrade_framework "javascript/heuristic-models" +fi + +echo "" +echo "✅ All CodeQL pack lock files upgraded successfully."