From 3f9ca2250f62122de26df76926c2e225da3d1f93 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Fri, 13 Feb 2026 14:48:26 -0700 Subject: [PATCH 1/6] Refactor release mgmt workflows to use environments New workflows: - release.yml: orchestrates tag creation, CodeQL pack publish, and GitHub Release creation (supports tag push and workflow_dispatch) - release-tag.yml: validates, version-bumps, tests, and tags releases - release-codeql.yml: publishes and bundles CodeQL packs to GHCR New scripts: - scripts/update-release-version.sh: deterministic version updates across all 15 qlpack.yml files with --check and --dry-run modes - scripts/install-packs.sh: installs CodeQL pack dependencies with optional --framework filtering Modified: - update-codeql.yml: reformatted indentation, added job summary step, preserved current_version output for summary reporting --- .github/workflows/release-codeql.yml | 191 ++++++++++++++++ .github/workflows/release-tag.yml | 224 +++++++++++++++++++ .github/workflows/release.yml | 171 +++++++++++++++ .github/workflows/update-codeql.yml | 222 ++++++++++--------- scripts/install-packs.sh | 121 +++++++++++ scripts/update-release-version.sh | 313 +++++++++++++++++++++++++++ 6 files changed, 1137 insertions(+), 105 deletions(-) create mode 100644 .github/workflows/release-codeql.yml create mode 100644 .github/workflows/release-tag.yml create mode 100644 .github/workflows/release.yml create mode 100755 scripts/install-packs.sh create mode 100755 scripts/update-release-version.sh diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml new file mode 100644 index 000000000..957348874 --- /dev/null +++ b/.github/workflows/release-codeql.yml @@ -0,0 +1,191 @@ +name: Release CodeQL - Publish and Bundle CodeQL Packs + +on: + workflow_call: + inputs: + publish_codeql_packs: + default: true + description: 'Publish CodeQL packs to GHCR. Disable for pre-release or re-run scenarios where packs already exist.' + required: false + type: boolean + version: + description: 'Release version tag (e.g., vX.Y.Z). Must start with "v".' + required: true + type: string + outputs: + release_name: + description: 'The release name without "v" prefix (e.g., X.Y.Z)' + value: ${{ jobs.publish-codeql-packs.outputs.release_name }} + version: + description: 'The full version string with "v" prefix (e.g., vX.Y.Z)' + value: ${{ jobs.publish-codeql-packs.outputs.version }} + +# Note: This workflow is called exclusively via workflow_call from release.yml. +# It does NOT have a workflow_dispatch trigger to keep release.yml as the single +# entry point for all release operations. To re-publish CodeQL packs standalone, +# use workflow_dispatch on release.yml with create_github_release=false. + +permissions: + contents: read + +jobs: + publish-codeql-packs: + name: Publish and Bundle CodeQL Packs + runs-on: ubuntu-latest + + environment: release-codeql + + permissions: + contents: read + packages: write + + outputs: + release_name: ${{ steps.version.outputs.release_name }} + version: ${{ steps.version.outputs.version }} + + steps: + - name: CodeQL - Validate and parse version + id: version + run: | + VERSION="${{ inputs.version }}" + if [[ ! "${VERSION}" =~ ^v ]]; then + echo "::error::Version '${VERSION}' must start with 'v'" + exit 1 + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + + - name: CodeQL - Checkout tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ steps.version.outputs.version }} + + - name: CodeQL - Install QLT + id: install-qlt + uses: advanced-security/codeql-development-toolkit/.github/actions/install-qlt@main + with: + qlt-version: 'latest' + add-to-path: true + + - name: CodeQL - Install CodeQL + shell: bash + run: | + echo "Installing CodeQL" + qlt codeql run install + echo "-----------------------------" + echo "CodeQL Home: $QLT_CODEQL_HOME" + echo "CodeQL Binary: $QLT_CODEQL_PATH" + + - name: CodeQL - Install pack dependencies + shell: bash + run: | + qlt query run install-packs + + - name: CodeQL - Validate version consistency + run: | + RELEASE_NAME="${{ steps.version.outputs.release_name }}" + echo "Validating all version-bearing files match ${RELEASE_NAME}..." + chmod +x ./scripts/update-release-version.sh + ./scripts/update-release-version.sh --check "${RELEASE_NAME}" + + - name: CodeQL - Publish CodeQL packs + if: inputs.publish_codeql_packs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Publishable packs: queries, models (ext), and libraries (lib) + PUBLISHABLE_PACKS=( + "javascript/frameworks/cap/src" + "javascript/frameworks/cap/ext" + "javascript/frameworks/cap/lib" + "javascript/frameworks/ui5/src" + "javascript/frameworks/ui5/ext" + "javascript/frameworks/ui5/lib" + "javascript/frameworks/xsjs/src" + "javascript/frameworks/xsjs/ext" + "javascript/frameworks/xsjs/lib" + "javascript/heuristic-models/ext" + ) + + echo "Publishing CodeQL packs..." + for pack_dir in "${PUBLISHABLE_PACKS[@]}"; do + if [ -d "${pack_dir}" ]; then + pack_name=$(grep -m1 "^name:" "${pack_dir}/qlpack.yml" | awk '{print $2}') + echo "đŸ“Ļ Publishing ${pack_name} from ${pack_dir}..." + $QLT_CODEQL_PATH pack publish --threads=-1 -- "${pack_dir}" + echo "✅ Published ${pack_name}" + else + echo "âš ī¸ Skipping: ${pack_dir} not found" + fi + done + + - name: CodeQL - Skip pack publishing + if: '!inputs.publish_codeql_packs' + run: echo "â­ī¸ CodeQL pack publishing disabled via workflow input" + + - name: CodeQL - Bundle CodeQL packs + run: | + mkdir -p dist-packs + + # Bundle all publishable packs + PUBLISHABLE_PACKS=( + "javascript/frameworks/cap/src" + "javascript/frameworks/cap/ext" + "javascript/frameworks/cap/lib" + "javascript/frameworks/ui5/src" + "javascript/frameworks/ui5/ext" + "javascript/frameworks/ui5/lib" + "javascript/frameworks/xsjs/src" + "javascript/frameworks/xsjs/ext" + "javascript/frameworks/xsjs/lib" + "javascript/heuristic-models/ext" + ) + + echo "Bundling CodeQL packs..." + for pack_dir in "${PUBLISHABLE_PACKS[@]}"; do + if [ -d "${pack_dir}" ]; then + pack_name=$(grep -m1 "^name:" "${pack_dir}/qlpack.yml" | awk '{print $2}') + # Convert pack name to filename: advanced-security/foo -> foo + bundle_name="${pack_name#advanced-security/}" + output="dist-packs/${bundle_name}.tar.gz" + echo "đŸ“Ļ Bundling ${pack_name} -> ${output}..." + $QLT_CODEQL_PATH pack bundle --threads=-1 --output="${output}" -- "${pack_dir}" + echo "✅ Bundled ${bundle_name}" + fi + done + echo "" + echo "Bundled packs:" + ls -lh dist-packs/ + + - name: CodeQL - Upload pack artifacts + uses: actions/upload-artifact@v6 + with: + name: codeql-pack-bundles-${{ steps.version.outputs.version }} + path: dist-packs/*.tar.gz + + - name: CodeQL - Summary + run: | + VERSION="${{ steps.version.outputs.version }}" + RELEASE_NAME="${{ steps.version.outputs.release_name }}" + echo "## CodeQL Packs Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.publish_codeql_packs }}" == "true" ]; then + echo "✅ Published CodeQL packs to GHCR" >> $GITHUB_STEP_SUMMARY + else + echo "â­ī¸ CodeQL pack publishing was disabled" >> $GITHUB_STEP_SUMMARY + fi + echo "✅ Bundled CodeQL packs as artifacts" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### CodeQL Packs" >> $GITHUB_STEP_SUMMARY + echo "| Pack | Version |" >> $GITHUB_STEP_SUMMARY + echo "| ---- | ------- |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-heuristic-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml new file mode 100644 index 000000000..0da82a02c --- /dev/null +++ b/.github/workflows/release-tag.yml @@ -0,0 +1,224 @@ +name: Release Tag - Create Version Tag + +on: + workflow_call: + inputs: + version: + description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + required: true + type: string + outputs: + release_name: + description: 'The release name without "v" prefix (e.g., X.Y.Z)' + value: ${{ jobs.create-tag.outputs.release_name }} + tag_sha: + description: 'The commit SHA that the tag points to' + value: ${{ jobs.create-tag.outputs.tag_sha }} + version: + description: 'The full version string with "v" prefix (e.g., vX.Y.Z)' + value: ${{ jobs.create-tag.outputs.version }} + +# Note: This workflow is called exclusively via workflow_call from release.yml. +# It does NOT have a workflow_dispatch trigger to keep release.yml as the single +# entry point for all release operations. + +permissions: + contents: read + +jobs: + create-tag: + name: Create Version Tag + runs-on: ubuntu-latest + + environment: release-tag + + permissions: + contents: write + + outputs: + release_name: ${{ steps.version.outputs.release_name }} + tag_sha: ${{ steps.final-sha.outputs.tag_sha }} + version: ${{ steps.version.outputs.version }} + + steps: + - name: Tag - Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Tag - Validate and parse version + id: version + run: | + VERSION="${{ inputs.version }}" + # Validate version starts with 'v' + if [[ ! "${VERSION}" =~ ^v ]]; then + echo "::error::Version '${VERSION}' must start with 'v'" + exit 1 + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + + - name: Tag - Check if tag already exists + id: check-tag + run: | + TAG="${{ steps.version.outputs.version }}" + if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then + TAG_SHA=$(git rev-parse "refs/tags/${TAG}^{commit}" 2>/dev/null || git rev-parse "refs/tags/${TAG}") + echo "tag_exists=true" >> $GITHUB_OUTPUT + echo "tag_sha=${TAG_SHA}" >> $GITHUB_OUTPUT + echo "â„šī¸ Tag ${TAG} already exists at commit ${TAG_SHA:0:8}" + else + echo "tag_exists=false" >> $GITHUB_OUTPUT + echo "â„šī¸ Tag ${TAG} does not exist yet" + fi + + - name: Tag - Install QLT + if: steps.check-tag.outputs.tag_exists != 'true' + id: install-qlt + uses: advanced-security/codeql-development-toolkit/.github/actions/install-qlt@main + with: + qlt-version: 'latest' + add-to-path: true + + - name: Tag - Install CodeQL + if: steps.check-tag.outputs.tag_exists != 'true' + shell: bash + run: | + echo "Installing CodeQL" + qlt codeql run install + echo "-----------------------------" + echo "CodeQL Home: $QLT_CODEQL_HOME" + echo "CodeQL Binary: $QLT_CODEQL_PATH" + + - name: Tag - Update release version + if: steps.check-tag.outputs.tag_exists != 'true' + run: | + TAG_VERSION="${{ steps.version.outputs.release_name }}" + echo "Updating all version-bearing files to '${TAG_VERSION}'..." + chmod +x ./scripts/update-release-version.sh + ./scripts/update-release-version.sh "${TAG_VERSION}" + + - name: Tag - Upgrade CodeQL pack lock files + if: steps.check-tag.outputs.tag_exists != 'true' + shell: bash + run: | + echo "Upgrading CodeQL pack lock files" + find . -name "qlpack.yml" -type f | while read -r qlpack_file; do + pack_dir=$(dirname "$qlpack_file") + echo "Upgrading pack in directory: $pack_dir" + cd "$pack_dir" + $QLT_CODEQL_PATH pack upgrade + cd - > /dev/null + done + echo "Finished upgrading all CodeQL pack lock files" + + - name: Tag - Install QL packs + if: steps.check-tag.outputs.tag_exists != 'true' + shell: bash + run: | + qlt query run install-packs + + - name: Tag - Setup Node.js for CDS compilation + if: steps.check-tag.outputs.tag_exists != 'true' + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'extractors/cds/tools/package-lock.json' + + - name: Tag - Compile CAP CDS files + if: steps.check-tag.outputs.tag_exists != 'true' + run: | + chmod +x ./extractors/cds/tools/workflow/cds-compilation-for-actions.sh + ./extractors/cds/tools/workflow/cds-compilation-for-actions.sh + + - name: Tag - Run CodeQL unit tests + if: steps.check-tag.outputs.tag_exists != 'true' + env: + LGTM_INDEX_XML_MODE: all + LGTM_INDEX_FILETYPES: ".json:JSON\n.cds:JSON" + shell: bash + run: | + echo "Running CodeQL unit tests to validate release..." + $QLT_CODEQL_PATH test run \ + --threads=0 \ + --strict-test-discovery \ + --additional-packs="${GITHUB_WORKSPACE}" \ + -- javascript/ + + - name: Tag - Validate version consistency + if: steps.check-tag.outputs.tag_exists != 'true' + run: | + RELEASE_NAME="${{ steps.version.outputs.release_name }}" + echo "Validating all version-bearing files match ${RELEASE_NAME}..." + ./scripts/update-release-version.sh --check "${RELEASE_NAME}" + + - name: Tag - Commit version changes and create tag + id: create-tag + if: steps.check-tag.outputs.tag_exists != 'true' + run: | + TAG="${{ steps.version.outputs.version }}" + RELEASE_NAME="${{ steps.version.outputs.release_name }}" + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Stage version-bearing files and lockfile changes + git add -A + # Ensure CodeQL-generated artifacts are not staged for commit + git restore --staged .codeql || true + git restore --staged '*.qlx' || true + + # Check if there are changes to commit + if git diff --cached --quiet; then + echo "â„šī¸ No changes to commit (versions already up to date)" + CURRENT_SHA=$(git rev-parse HEAD) + else + git commit -m "Release ${TAG}: update versions to ${RELEASE_NAME}" + CURRENT_SHA=$(git rev-parse HEAD) + git push origin HEAD + echo "✅ Committed version changes at ${CURRENT_SHA:0:8}" + fi + + # Create and push the tag + git tag -a "${TAG}" -m "Release ${TAG}" "${CURRENT_SHA}" + git push origin "${TAG}" + echo "✅ Created and pushed tag ${TAG} at commit ${CURRENT_SHA:0:8}" + echo "tag_sha=${CURRENT_SHA}" >> $GITHUB_OUTPUT + + - name: Tag - Output existing tag SHA + id: existing-tag + if: steps.check-tag.outputs.tag_exists == 'true' + run: | + echo "tag_sha=${{ steps.check-tag.outputs.tag_sha }}" >> $GITHUB_OUTPUT + + - name: Tag - Set final tag SHA output + id: final-sha + run: | + if [ "${{ steps.check-tag.outputs.tag_exists }}" == "true" ]; then + SHA="${{ steps.check-tag.outputs.tag_sha }}" + else + SHA="${{ steps.create-tag.outputs.tag_sha }}" + fi + echo "tag_sha=${SHA}" >> $GITHUB_OUTPUT + + - name: Tag - Summary + run: | + TAG="${{ steps.version.outputs.version }}" + echo "## Release Tag Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check-tag.outputs.tag_exists }}" == "true" ]; then + echo "â„šī¸ Tag \`${TAG}\` already existed at \`${{ steps.check-tag.outputs.tag_sha }}\`" >> $GITHUB_STEP_SUMMARY + else + echo "✅ Created tag \`${TAG}\` at \`${{ steps.create-tag.outputs.tag_sha }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY + echo "| ---- | ------ |" >> $GITHUB_STEP_SUMMARY + echo "| Version update | ✅ All files updated to ${{ steps.version.outputs.release_name }} |" >> $GITHUB_STEP_SUMMARY + echo "| Pack lock upgrade | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + echo "| CodeQL unit tests | ✅ Passed |" >> $GITHUB_STEP_SUMMARY + echo "| Version consistency | ✅ All files match |" >> $GITHUB_STEP_SUMMARY + echo "| Tag creation | ✅ ${TAG} |" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..c217c2e8e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,171 @@ +name: Release - CodeQL SAP JavaScript + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + create_github_release: + default: true + description: 'Create GitHub Release with CodeQL pack bundles. Disable to only publish packs without creating a release.' + required: false + type: boolean + publish_codeql_packs: + default: true + description: 'Publish CodeQL packs to GHCR. Disable for pre-release or re-run scenarios where packs already exist. Packs are always bundled as release artifacts regardless of this setting.' + required: false + type: boolean + version: + description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + required: true + type: string + +permissions: + contents: read + +jobs: + # ───────────────────────────────────────────────────────────────────────────── + # Step 1: Determine the release version + # + # Resolves the version from either the tag push event or the workflow_dispatch + # input, and validates the format. This output is consumed by all downstream + # jobs. + # ───────────────────────────────────────────────────────────────────────────── + resolve-version: + name: Resolve Release Version + runs-on: ubuntu-latest + + outputs: + create_github_release: ${{ steps.resolve.outputs.create_github_release }} + publish_codeql_packs: ${{ steps.resolve.outputs.publish_codeql_packs }} + release_name: ${{ steps.resolve.outputs.release_name }} + version: ${{ steps.resolve.outputs.version }} + + steps: + - name: Version - Resolve and validate + id: resolve + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION="${{ github.ref_name }}" + fi + + # Validate version starts with 'v' + if [[ ! "${VERSION}" =~ ^v ]]; then + echo "::error::Version '${VERSION}' must start with 'v'" + exit 1 + fi + + # Resolve publish flags (default true for tag pushes) + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + CREATE_RELEASE="${{ github.event.inputs.create_github_release }}" + PUBLISH_PACKS="${{ github.event.inputs.publish_codeql_packs }}" + else + CREATE_RELEASE="true" + PUBLISH_PACKS="true" + fi + + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "release_name=${VERSION#v}" >> $GITHUB_OUTPUT + echo "create_github_release=${CREATE_RELEASE}" >> $GITHUB_OUTPUT + echo "publish_codeql_packs=${PUBLISH_PACKS}" >> $GITHUB_OUTPUT + + # ───────────────────────────────────────────────────────────────────────────── + # Step 2: Ensure the release tag exists + # + # For workflow_dispatch, ensures a properly validated tag exists. For tag push + # events, this is a no-op (tag already exists). The release-tag workflow + # handles version updates, pack lock upgrades, test validation, and tag + # creation. + # ───────────────────────────────────────────────────────────────────────────── + ensure-tag: + name: Ensure Release Tag + needs: resolve-version + permissions: + contents: write + uses: ./.github/workflows/release-tag.yml + with: + version: ${{ needs.resolve-version.outputs.version }} + + # ───────────────────────────────────────────────────────────────────────────── + # Step 3: Bundle and optionally publish CodeQL packs + # + # Checks out the clean tag, installs CodeQL, and bundles packs for release. + # Publishing to GHCR is controlled by the publish_codeql_packs flag; bundling + # always runs so that pack artifacts are available for the GitHub Release. + # ───────────────────────────────────────────────────────────────────────────── + publish-codeql: + name: Publish CodeQL Packs + needs: [resolve-version, ensure-tag] + permissions: + contents: read + packages: write + uses: ./.github/workflows/release-codeql.yml + with: + publish_codeql_packs: ${{ needs.resolve-version.outputs.publish_codeql_packs == 'true' }} + version: ${{ needs.resolve-version.outputs.version }} + + # ───────────────────────────────────────────────────────────────────────────── + # Step 4: Create GitHub Release + # + # Downloads the CodeQL pack bundles and creates the GitHub Release with + # release notes and attached artifacts. + # ───────────────────────────────────────────────────────────────────────────── + create-release: + name: Create GitHub Release + if: >- + always() && !failure() && !cancelled() + && needs.resolve-version.outputs.create_github_release == 'true' + needs: [resolve-version, ensure-tag, publish-codeql] + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Release - Download CodeQL pack artifacts + uses: actions/download-artifact@v7 + with: + name: codeql-pack-bundles-${{ needs.resolve-version.outputs.version }} + path: dist-packs + + - name: Release - Create GitHub Release + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + with: + files: | + dist-packs/*.tar.gz + generate_release_notes: true + tag_name: ${{ needs.resolve-version.outputs.version }} + + - name: Release - Summary + run: | + VERSION="${{ needs.resolve-version.outputs.version }}" + RELEASE_NAME="${{ needs.resolve-version.outputs.release_name }}" + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY + echo "| ---- | ------ |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | ✅ ${VERSION} |" >> $GITHUB_STEP_SUMMARY + echo "| Version validation | ✅ All files match ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.resolve-version.outputs.publish_codeql_packs }}" == "true" ]; then + echo "| CodeQL pack publish | ✅ Published to GHCR |" >> $GITHUB_STEP_SUMMARY + else + echo "| CodeQL pack publish | â­ī¸ Skipped (packs bundled only) |" >> $GITHUB_STEP_SUMMARY + fi + echo "| GitHub Release | ✅ Created |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Published CodeQL Packs" >> $GITHUB_STEP_SUMMARY + echo "| Pack | Version |" >> $GITHUB_STEP_SUMMARY + echo "| ---- | ------- |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-cap-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-ui5-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-queries\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-sap-xsjs-all\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY + echo "| \`advanced-security/javascript-heuristic-models\` | ${RELEASE_NAME} |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/update-codeql.yml b/.github/workflows/update-codeql.yml index e68907f7b..72d409409 100644 --- a/.github/workflows/update-codeql.yml +++ b/.github/workflows/update-codeql.yml @@ -1,111 +1,123 @@ -name: "Update the CodeQL CLI dependencies" +name: Update CodeQL CLI Dependencies on: - workflow_dispatch: - # nightly runs to update the CodeQL CLI dependencies - schedule: - - cron: '30 0 * * *' + workflow_dispatch: + # Nightly check for new CodeQL CLI releases + schedule: + - cron: '30 0 * * *' permissions: - contents: write - pull-requests: write + contents: write + pull-requests: write jobs: - update-codeql: - name: Update CodeQL CLI dependencies - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Check latest CodeQL CLI version and update qlt.conf.json - id: check-version - env: - GH_TOKEN: ${{ github.token }} - run: | - echo "Checking latest CodeQL CLI version" - current_version=$(jq .CodeQLCLI qlt.conf.json -r) - latest_version=$(gh release list --repo github/codeql-cli-binaries --json 'tagName,isLatest' --jq '.[] | select(.isLatest == true) | .tagName') - echo "Current CodeQL CLI version: $current_version" - echo "Latest CodeQL CLI version: $latest_version" - - # Remove 'v' prefix if present for comparison with current version - latest_clean=$(echo "$latest_version" | sed 's/^v//') - - if [ "$latest_clean" != "$current_version" ]; then - echo "Updating CodeQL CLI from $current_version to $latest_clean" - echo "update_needed=true" >> $GITHUB_OUTPUT - echo "latest_version=$latest_clean" >> $GITHUB_OUTPUT - echo "latest_version_tag=$latest_version" >> $GITHUB_OUTPUT - - # Update qlt.conf.json with all properties - echo "Updating qlt.conf.json with all properties for version $latest_clean" - jq --arg cli_version "$latest_clean" \ - --arg std_lib "codeql-cli/$latest_version" \ - --arg bundle "codeql-bundle-$latest_version" \ - '.CodeQLCLI = $cli_version | .CodeQLStandardLibrary = $std_lib | .CodeQLCLIBundle = $bundle' \ - qlt.conf.json > qlt.conf.json.tmp && mv qlt.conf.json.tmp qlt.conf.json - - echo "Updated qlt.conf.json contents:" - cat qlt.conf.json - else - echo "CodeQL CLI is already up-to-date at version $current_version." - echo "update_needed=false" >> $GITHUB_OUTPUT - fi - - - name: Install QLT - if: steps.check-version.outputs.update_needed == 'true' - id: install-qlt - uses: advanced-security/codeql-development-toolkit/.github/actions/install-qlt@main - with: - qlt-version: 'latest' - add-to-path: true - - - name: Install CodeQL - if: steps.check-version.outputs.update_needed == 'true' - id: install-codeql - shell: bash - run: | - echo "Installing CodeQL" - qlt codeql run install - echo "-----------------------------" - echo "CodeQL Home: $QLT_CODEQL_HOME" - echo "CodeQL Binary: $QLT_CODEQL_PATH" - - - name: Upgrade CodeQL pack lock files - if: steps.check-version.outputs.update_needed == 'true' - shell: bash - run: | - echo "Upgrading CodeQL pack lock files" - echo "Finding all directories with qlpack.yml files..." - - # Find all directories containing qlpack.yml files - find . -name "qlpack.yml" -type f | while read -r qlpack_file; do - pack_dir=$(dirname "$qlpack_file") - echo "Upgrading pack in directory: $pack_dir" - - # Change to the directory and run codeql pack upgrade - cd "$pack_dir" - $QLT_CODEQL_PATH pack upgrade - cd - > /dev/null - done - - echo "Finished upgrading all CodeQL pack lock files" - - - name: Create Pull Request - if: steps.check-version.outputs.update_needed == 'true' - uses: peter-evans/create-pull-request@v8 - with: - title: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" - body: | - This PR upgrades the CodeQL CLI version to ${{ steps.check-version.outputs.latest_version_tag }}. - - **Changes made:** - - Updated `CodeQLCLI` to `${{ steps.check-version.outputs.latest_version }}` - - Updated `CodeQLStandardLibrary` to `codeql-cli/${{ steps.check-version.outputs.latest_version_tag }}` - - Updated `CodeQLCLIBundle` to `codeql-bundle-${{ steps.check-version.outputs.latest_version_tag }}` - - Upgraded all CodeQL pack lock files using `codeql pack upgrade` - commit-message: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" - delete-branch: true - branch: "codeql/upgrade-to-${{ steps.check-version.outputs.latest_version_tag }}" + update-codeql: + name: Update CodeQL CLI Dependencies + runs-on: ubuntu-latest + + steps: + - name: Update - Checkout repository + uses: actions/checkout@v6 + + - name: Update - Check latest CodeQL CLI version + id: check-version + env: + GH_TOKEN: ${{ github.token }} + run: | + echo "Checking latest CodeQL CLI version..." + current_version=$(jq .CodeQLCLI qlt.conf.json -r) + latest_version=$(gh release list --repo github/codeql-cli-binaries --json 'tagName,isLatest' --jq '.[] | select(.isLatest == true) | .tagName') + echo "Current CodeQL CLI version: $current_version" + echo "Latest CodeQL CLI version: $latest_version" + + # Remove 'v' prefix if present for comparison with current version + latest_clean=$(echo "$latest_version" | sed 's/^v//') + + if [ "$latest_clean" != "$current_version" ]; then + echo "Updating CodeQL CLI from $current_version to $latest_clean" + echo "update_needed=true" >> $GITHUB_OUTPUT + echo "current_version=$current_version" >> $GITHUB_OUTPUT + echo "latest_version=$latest_clean" >> $GITHUB_OUTPUT + echo "latest_version_tag=$latest_version" >> $GITHUB_OUTPUT + + # Update qlt.conf.json with all properties + echo "Updating qlt.conf.json with all properties for version $latest_clean" + jq --arg cli_version "$latest_clean" \ + --arg std_lib "codeql-cli/$latest_version" \ + --arg bundle "codeql-bundle-$latest_version" \ + '.CodeQLCLI = $cli_version | .CodeQLStandardLibrary = $std_lib | .CodeQLCLIBundle = $bundle' \ + qlt.conf.json > qlt.conf.json.tmp && mv qlt.conf.json.tmp qlt.conf.json + + echo "Updated qlt.conf.json contents:" + cat qlt.conf.json + else + echo "CodeQL CLI is already up-to-date at version $current_version." + echo "update_needed=false" >> $GITHUB_OUTPUT + fi + + - name: Update - Install QLT + if: steps.check-version.outputs.update_needed == 'true' + id: install-qlt + uses: advanced-security/codeql-development-toolkit/.github/actions/install-qlt@main + with: + qlt-version: 'latest' + add-to-path: true + + - name: Update - Install CodeQL + if: steps.check-version.outputs.update_needed == 'true' + shell: bash + run: | + echo "Installing CodeQL" + qlt codeql run install + echo "-----------------------------" + echo "CodeQL Home: $QLT_CODEQL_HOME" + echo "CodeQL Binary: $QLT_CODEQL_PATH" + + - name: Update - Upgrade CodeQL pack lock files + if: steps.check-version.outputs.update_needed == 'true' + shell: bash + run: | + echo "Upgrading CodeQL pack lock files..." + find . -name "qlpack.yml" -type f | sort | while read -r qlpack_file; do + pack_dir=$(dirname "$qlpack_file") + echo "Upgrading pack in directory: $pack_dir" + cd "$pack_dir" + $QLT_CODEQL_PATH pack upgrade + cd - > /dev/null + done + echo "Finished upgrading all CodeQL pack lock files" + + - name: Update - Create Pull Request + if: steps.check-version.outputs.update_needed == 'true' + uses: peter-evans/create-pull-request@v8 + with: + title: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" + body: | + This PR upgrades the CodeQL CLI version to ${{ steps.check-version.outputs.latest_version_tag }}. + + **Changes made:** + - Updated `CodeQLCLI` to `${{ steps.check-version.outputs.latest_version }}` + - Updated `CodeQLStandardLibrary` to `codeql-cli/${{ steps.check-version.outputs.latest_version_tag }}` + - Updated `CodeQLCLIBundle` to `codeql-bundle-${{ steps.check-version.outputs.latest_version_tag }}` + - Upgraded all CodeQL pack lock files using `codeql pack upgrade` + commit-message: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" + delete-branch: true + branch: "codeql/upgrade-to-${{ steps.check-version.outputs.latest_version_tag }}" + + - name: Update - Summary + run: | + echo "## CodeQL CLI Update Check" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + 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 "| Property | Old Value | New Value |" >> $GITHUB_STEP_SUMMARY + echo "| -------- | --------- | --------- |" >> $GITHUB_STEP_SUMMARY + echo "| CodeQLCLI | ${{ steps.check-version.outputs.current_version }} | ${{ steps.check-version.outputs.latest_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| CodeQLStandardLibrary | — | codeql-cli/${{ steps.check-version.outputs.latest_version_tag }} |" >> $GITHUB_STEP_SUMMARY + echo "| CodeQLCLIBundle | — | codeql-bundle-${{ steps.check-version.outputs.latest_version_tag }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "A pull request has been created with these changes." >> $GITHUB_STEP_SUMMARY + else + echo "â„šī¸ CodeQL CLI is already up-to-date. No changes needed." >> $GITHUB_STEP_SUMMARY + fi diff --git a/scripts/install-packs.sh b/scripts/install-packs.sh new file mode 100755 index 000000000..d5c6e0ab6 --- /dev/null +++ b/scripts/install-packs.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +set -euo pipefail + +## install-packs.sh +## Install CodeQL pack dependencies for all packs in the codeql-sap-js repository. +## +## This script installs dependencies for both source and test packs, using +## --additional-packs for workspace-local resolution of internal pack references. +## +## Usage: +## ./scripts/install-packs.sh +## ./scripts/install-packs.sh --framework cap +## ./scripts/install-packs.sh --framework ui5 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +FRAMEWORK="" + +usage() { + cat < Install packs only for the specified framework + Valid values: cap, ui5, ui5-webcomponents, xsjs, heuristic-models + -h, --help Show this help message + +By default, the script installs packs for all frameworks. +EOF +} + +while [[ $# -gt 0 ]]; do + case $1 in + --framework) + 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" "ui5" "ui5-webcomponents" "xsjs" "heuristic-models") +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}" + +## Install packs for a given qlpack.yml directory +install_pack() { + local pack_dir="$1" + if [[ -d "${pack_dir}" ]]; then + echo "INFO: Running 'codeql pack install' for '${pack_dir}'..." + codeql pack install --no-strict-mode --additional-packs="${REPO_ROOT}/javascript" -- "${pack_dir}" + else + echo "WARNING: Directory '${pack_dir}' not found, skipping" >&2 + fi +} + +## Install packs for a framework (all subdirectories that contain qlpack.yml) +install_framework() { + local framework_path="$1" + echo "Installing packs for: ${framework_path}" + + # Find all qlpack.yml files under this framework and install 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}/}" + install_pack "${rel_path}" + done +} + +if [[ -n "${FRAMEWORK}" ]]; then + case "${FRAMEWORK}" in + heuristic-models) + install_framework "javascript/heuristic-models" + ;; + ui5-webcomponents) + install_framework "javascript/frameworks/ui5-webcomponents" + ;; + *) + install_framework "javascript/frameworks/${FRAMEWORK}" + ;; + esac +else + echo "Installing packs for all frameworks..." + install_framework "javascript/frameworks/cap" + install_framework "javascript/frameworks/ui5" + install_framework "javascript/frameworks/ui5-webcomponents" + install_framework "javascript/frameworks/xsjs" + install_framework "javascript/heuristic-models" +fi + +echo "" +echo "✅ All CodeQL pack dependencies installed successfully." diff --git a/scripts/update-release-version.sh b/scripts/update-release-version.sh new file mode 100755 index 000000000..d89b6f8cf --- /dev/null +++ b/scripts/update-release-version.sh @@ -0,0 +1,313 @@ +#!/usr/bin/env bash +set -euo pipefail + +## update-release-version.sh +## Deterministically updates the release version across all version-bearing files +## in the codeql-sap-js repository. +## +## Version-bearing files (15 qlpack.yml files): +## javascript/frameworks/cap/ext/qlpack.yml +## javascript/frameworks/cap/lib/qlpack.yml +## javascript/frameworks/cap/src/qlpack.yml +## javascript/frameworks/cap/test/qlpack.yml +## javascript/frameworks/ui5/ext/qlpack.yml +## javascript/frameworks/ui5/lib/qlpack.yml +## javascript/frameworks/ui5/src/qlpack.yml +## javascript/frameworks/ui5/test/qlpack.yml +## javascript/frameworks/ui5-webcomponents/test/qlpack.yml +## javascript/frameworks/xsjs/ext/qlpack.yml +## javascript/frameworks/xsjs/lib/qlpack.yml +## javascript/frameworks/xsjs/src/qlpack.yml +## javascript/frameworks/xsjs/test/qlpack.yml +## javascript/heuristic-models/ext/qlpack.yml +## javascript/heuristic-models/tests/qlpack.yml +## +## Additionally updates internal dependency references within qlpack.yml files +## that reference other packs in this repository (e.g., ^X.Y.Z constraints). +## +## Usage: +## ./scripts/update-release-version.sh +## ./scripts/update-release-version.sh --check [] +## +## Examples: +## ./scripts/update-release-version.sh 2.4.0 +## ./scripts/update-release-version.sh --check +## ./scripts/update-release-version.sh --check 2.4.0 + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +## All qlpack.yml file paths relative to repo root +QLPACK_FILES=( + "javascript/frameworks/cap/ext/qlpack.yml" + "javascript/frameworks/cap/lib/qlpack.yml" + "javascript/frameworks/cap/src/qlpack.yml" + "javascript/frameworks/cap/test/qlpack.yml" + "javascript/frameworks/ui5/ext/qlpack.yml" + "javascript/frameworks/ui5/lib/qlpack.yml" + "javascript/frameworks/ui5/src/qlpack.yml" + "javascript/frameworks/ui5/test/qlpack.yml" + "javascript/frameworks/ui5-webcomponents/test/qlpack.yml" + "javascript/frameworks/xsjs/ext/qlpack.yml" + "javascript/frameworks/xsjs/lib/qlpack.yml" + "javascript/frameworks/xsjs/src/qlpack.yml" + "javascript/frameworks/xsjs/test/qlpack.yml" + "javascript/heuristic-models/ext/qlpack.yml" + "javascript/heuristic-models/tests/qlpack.yml" +) + +## Pack names that belong to this repository (for updating internal dependency refs) +INTERNAL_PACKS=( + "advanced-security/javascript-sap-cap-models" + "advanced-security/javascript-sap-cap-all" + "advanced-security/javascript-sap-cap-queries" + "advanced-security/javascript-sap-ui5-models" + "advanced-security/javascript-sap-ui5-all" + "advanced-security/javascript-sap-ui5-queries" + "advanced-security/javascript-sap-xsjs-models" + "advanced-security/javascript-sap-xsjs-all" + "advanced-security/javascript-sap-xsjs-queries" + "advanced-security/javascript-heuristic-models" +) + +usage() { + cat < + $0 --check [] + +Deterministically updates the release version across all version-bearing files. + +ARGUMENTS: + The new version to set (e.g., 2.4.0). + The 'v' prefix is optional and will be normalized. + +OPTIONS: + --check [] Check version consistency across all files. + If is provided, also validates that all + files match the expected version. + --dry-run Show what would be changed without modifying files. + -h, --help Show this help message. + +EXAMPLES: + $0 2.4.0 Update all files to version 2.4.0 + $0 v2.4.0 Same as above (v prefix is stripped automatically) + $0 --check Verify all version-bearing files are consistent + $0 --check 2.4.0 Verify all files contain version 2.4.0 + $0 --dry-run 2.4.0 Preview changes without writing files +EOF +} + +## Collect all version-bearing files and their current versions +collect_versions() { + local versions=() + + for qlpack_file in "${QLPACK_FILES[@]}"; do + local full_path="${REPO_ROOT}/${qlpack_file}" + if [[ -f "${full_path}" ]]; then + local pack_version + pack_version=$(grep -m1 "^version:" "${full_path}" | awk '{print $2}') + versions+=("${qlpack_file}|${pack_version}") + else + echo "WARNING: ${qlpack_file} not found" >&2 + fi + done + + printf '%s\n' "${versions[@]}" +} + +## Check version consistency +check_versions() { + local expected_version="${1:-}" + local all_consistent=true + local first_version="" + local file_count=0 + + echo "=== Version Consistency Check ===" + echo "" + + while IFS='|' read -r file version; do + file_count=$((file_count + 1)) + + if [[ -z "${first_version}" ]]; then + first_version="${version}" + fi + + if [[ -n "${expected_version}" ]]; then + if [[ "${version}" == "${expected_version}" ]]; then + echo " ✅ ${file}: ${version}" + else + echo " ❌ ${file}: ${version} (expected ${expected_version})" + all_consistent=false + fi + else + if [[ "${version}" == "${first_version}" ]]; then + echo " ✅ ${file}: ${version}" + else + echo " ❌ ${file}: ${version} (differs from ${first_version})" + all_consistent=false + fi + fi + done < <(collect_versions) + + echo "" + echo "Checked ${file_count} version-bearing files." + + if [[ "${all_consistent}" == true ]]; then + if [[ -n "${expected_version}" ]]; then + echo "✅ All files match expected version: ${expected_version}" + else + echo "✅ All files are consistent at version: ${first_version}" + fi + return 0 + else + echo "❌ Version inconsistency detected!" + return 1 + fi +} + +## Validate version format (X.Y.Z) +validate_version() { + local version="$1" + if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: Invalid version format '${version}'" >&2 + echo "Expected format: X.Y.Z (e.g., 2.4.0)" >&2 + return 1 + fi +} + +## Update a qlpack.yml file's version field using sed +update_pack_version() { + local file="$1" + local new_version="$2" + sed -i.bak "s/^version:[[:space:]]*.*/version: ${new_version}/" "${file}" + rm -f "${file}.bak" +} + +## Update internal dependency references in a qlpack.yml file +## e.g., advanced-security/javascript-sap-cap-models: "^2.3.0" -> "^2.4.0" +## and advanced-security/javascript-heuristic-models: 2.3.0 -> 2.4.0 +update_internal_deps() { + local file="$1" + local old_version="$2" + local new_version="$3" + + for pack_name in "${INTERNAL_PACKS[@]}"; do + # Update quoted caret-prefixed versions: "^X.Y.Z" + sed -i.bak "s|${pack_name}: \"\\^${old_version}\"|${pack_name}: \"^${new_version}\"|g" "${file}" + rm -f "${file}.bak" + # Update unquoted exact versions: X.Y.Z + sed -i.bak "s|${pack_name}: ${old_version}$|${pack_name}: ${new_version}|g" "${file}" + rm -f "${file}.bak" + done +} + +## Update all version-bearing files +update_versions() { + local new_version="$1" + local dry_run="${2:-false}" + local updated_count=0 + + echo "=== Updating Release Version to ${new_version} ===" + echo "" + + # Determine the current version from the first qlpack.yml file + local current_version="" + local first_file="${REPO_ROOT}/${QLPACK_FILES[0]}" + if [[ -f "${first_file}" ]]; then + current_version=$(grep -m1 "^version:" "${first_file}" | awk '{print $2}') + fi + + if [[ -z "${current_version}" ]]; then + echo "ERROR: Could not determine current version from ${QLPACK_FILES[0]}" >&2 + return 1 + fi + + echo " Current version: ${current_version}" + echo " New version: ${new_version}" + echo "" + + if [[ "${current_version}" == "${new_version}" ]]; then + echo "â„šī¸ Version is already ${new_version}. Nothing to update." + return 0 + fi + + ## Update all qlpack.yml files + for qlpack_file in "${QLPACK_FILES[@]}"; do + local full_path="${REPO_ROOT}/${qlpack_file}" + if [[ -f "${full_path}" ]]; then + local old_version + old_version=$(grep -m1 "^version:" "${full_path}" | awk '{print $2}') + if [[ "${dry_run}" == true ]]; then + echo " [DRY RUN] ${qlpack_file}: ${old_version} -> ${new_version}" + else + update_pack_version "${full_path}" "${new_version}" + update_internal_deps "${full_path}" "${current_version}" "${new_version}" + echo " ✅ ${qlpack_file}: ${old_version} -> ${new_version}" + fi + updated_count=$((updated_count + 1)) + fi + done + + echo "" + if [[ "${dry_run}" == true ]]; then + echo "Would update ${updated_count} files. (Dry run — no files modified)" + else + echo "Updated ${updated_count} files to version ${new_version}." + echo "" + echo "Next steps:" + echo " 1. Run 'codeql pack upgrade' on all packs to update lock files" + echo " 2. Run CodeQL unit tests to validate the changes" + echo " 3. Commit the changes and tag with 'v${new_version}'" + fi +} + +## Parse arguments +CHECK_MODE=false +DRY_RUN=false +NEW_VERSION="" + +while [[ $# -gt 0 ]]; do + case $1 in + --check) + CHECK_MODE=true + shift + ## Optional expected version argument + if [[ $# -gt 0 && ! "$1" =~ ^-- ]]; then + NEW_VERSION="${1#v}" + shift + fi + ;; + --dry-run) + DRY_RUN=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + -*) + echo "Error: Unknown option $1" >&2 + usage >&2 + exit 1 + ;; + *) + NEW_VERSION="${1#v}" ## Strip optional v prefix + shift + ;; + esac +done + +if [[ "${CHECK_MODE}" == true ]]; then + check_versions "${NEW_VERSION}" + exit $? +fi + +if [[ -z "${NEW_VERSION}" ]]; then + echo "Error: No version specified" >&2 + echo "" >&2 + usage >&2 + exit 1 +fi + +validate_version "${NEW_VERSION}" +update_versions "${NEW_VERSION}" "${DRY_RUN}" From b700d79085e03b3010ed3bce17ec1440e679efa3 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Fri, 13 Feb 2026 17:40:10 -0700 Subject: [PATCH 2/6] Address PR review comments --- .github/workflows/code_scanning.yml | 4 ++-- .github/workflows/release-tag.yml | 3 ++- .gitignore | 4 ++++ scripts/update-release-version.sh | 12 ++++++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code_scanning.yml b/.github/workflows/code_scanning.yml index b714c141b..377858661 100644 --- a/.github/workflows/code_scanning.yml +++ b/.github/workflows/code_scanning.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Prepare local CodeQL model packs run: | @@ -92,7 +92,7 @@ jobs: - name: Upload sarif change if: steps.validate.outcome != 'success' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: sarif path: | diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 0da82a02c..cfd4ef331 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -167,9 +167,10 @@ jobs: # Stage version-bearing files and lockfile changes git add -A - # Ensure CodeQL-generated artifacts are not staged for commit + # Ensure generated artifacts (CodeQL, CAP compilation) are not staged for commit git restore --staged .codeql || true git restore --staged '*.qlx' || true + git restore --staged 'javascript/frameworks/cap/test/**/model.cds.json' || true # Check if there are changes to commit if git diff --cached --quiet; then diff --git a/.gitignore b/.gitignore index b3593c113..9f3d55a64 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,7 @@ dbs *.cds.json .cds-extractor-cache +# CodeQL-generated artifacts +.codeql/ +*.qlx + diff --git a/scripts/update-release-version.sh b/scripts/update-release-version.sh index d89b6f8cf..cfb3fe44d 100755 --- a/scripts/update-release-version.sh +++ b/scripts/update-release-version.sh @@ -106,6 +106,10 @@ collect_versions() { if [[ -f "${full_path}" ]]; then local pack_version pack_version=$(grep -m1 "^version:" "${full_path}" | awk '{print $2}') + if [[ -z "${pack_version}" ]]; then + echo "ERROR: ${qlpack_file} is missing a 'version:' field" >&2 + return 1 + fi versions+=("${qlpack_file}|${pack_version}") else echo "WARNING: ${qlpack_file} not found" >&2 @@ -191,12 +195,16 @@ update_internal_deps() { local old_version="$2" local new_version="$3" + # Escape regex metacharacters in the old version (e.g., '.' -> '\.') + local escaped_old_version + escaped_old_version=$(printf '%s' "${old_version}" | sed 's/[.\*\[\^\$]/\\&/g') + for pack_name in "${INTERNAL_PACKS[@]}"; do # Update quoted caret-prefixed versions: "^X.Y.Z" - sed -i.bak "s|${pack_name}: \"\\^${old_version}\"|${pack_name}: \"^${new_version}\"|g" "${file}" + sed -i.bak "s|${pack_name}: \"\\^${escaped_old_version}\"|${pack_name}: \"^${new_version}\"|g" "${file}" rm -f "${file}.bak" # Update unquoted exact versions: X.Y.Z - sed -i.bak "s|${pack_name}: ${old_version}$|${pack_name}: ${new_version}|g" "${file}" + sed -i.bak "s|${pack_name}: ${escaped_old_version}$|${pack_name}: ${new_version}|g" "${file}" rm -f "${file}.bak" done } From 9ef096cbc57d94ef215b5814d9bdca6dbb7bed44 Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:03:18 -0700 Subject: [PATCH 3/6] Update .github/workflows/release-tag.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release-tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index cfd4ef331..d0f05c2ff 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -104,7 +104,7 @@ jobs: shell: bash run: | echo "Upgrading CodeQL pack lock files" - find . -name "qlpack.yml" -type f | while read -r qlpack_file; do + find . -name "qlpack.yml" -type f | sort | while read -r qlpack_file; do pack_dir=$(dirname "$qlpack_file") echo "Upgrading pack in directory: $pack_dir" cd "$pack_dir" From b8c3f177ca002a9cddc70313ef34361add46a252 Mon Sep 17 00:00:00 2001 From: Nathan Randall <70299490+data-douser@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:14:21 -0700 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release-codeql.yml | 27 +++++++++++++++------------ scripts/update-release-version.sh | 4 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index 957348874..0600573a2 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -124,22 +124,25 @@ jobs: run: echo "â­ī¸ CodeQL pack publishing disabled via workflow input" - name: CodeQL - Bundle CodeQL packs + env: + PUBLISHABLE_PACKS_LIST: | + javascript/frameworks/cap/src + javascript/frameworks/cap/ext + javascript/frameworks/cap/lib + javascript/frameworks/ui5/src + javascript/frameworks/ui5/ext + javascript/frameworks/ui5/lib + javascript/frameworks/xsjs/src + javascript/frameworks/xsjs/ext + javascript/frameworks/xsjs/lib + javascript/heuristic-models/ext run: | mkdir -p dist-packs # Bundle all publishable packs - PUBLISHABLE_PACKS=( - "javascript/frameworks/cap/src" - "javascript/frameworks/cap/ext" - "javascript/frameworks/cap/lib" - "javascript/frameworks/ui5/src" - "javascript/frameworks/ui5/ext" - "javascript/frameworks/ui5/lib" - "javascript/frameworks/xsjs/src" - "javascript/frameworks/xsjs/ext" - "javascript/frameworks/xsjs/lib" - "javascript/heuristic-models/ext" - ) + # Read the pack list from the environment into a Bash array. + # Each line in PUBLISHABLE_PACKS_LIST becomes one element. + mapfile -t PUBLISHABLE_PACKS <<< "${PUBLISHABLE_PACKS_LIST}" echo "Bundling CodeQL packs..." for pack_dir in "${PUBLISHABLE_PACKS[@]}"; do diff --git a/scripts/update-release-version.sh b/scripts/update-release-version.sh index cfb3fe44d..4e60634bb 100755 --- a/scripts/update-release-version.sh +++ b/scripts/update-release-version.sh @@ -195,9 +195,9 @@ update_internal_deps() { local old_version="$2" local new_version="$3" - # Escape regex metacharacters in the old version (e.g., '.' -> '\.') + # Escape dots in the old version (e.g., '2.3.0' -> '2\.3\.0') for use in sed regex local escaped_old_version - escaped_old_version=$(printf '%s' "${old_version}" | sed 's/[.\*\[\^\$]/\\&/g') + escaped_old_version=$(printf '%s' "${old_version}" | sed 's/\./\\./g') for pack_name in "${INTERNAL_PACKS[@]}"; do # Update quoted caret-prefixed versions: "^X.Y.Z" From 0cdb720a36508099f322b832d6d5776da0b9c755 Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 15 Feb 2026 15:54:29 -0700 Subject: [PATCH 5/6] Refactor release workflows to share child workflows - Rewrite update-codeql.yml to orchestrate via release-tag.yml and release-codeql.yml instead of inline logic and PR creation - Fix release-tag.yml step ordering: update version before installing CodeQL so qlt.conf.json is correct when QLT reads it - Use install-packs.sh in release-tag.yml and release-codeql.yml - Add qlt.conf.json support to update-release-version.sh (jq primary, sed fallback) with --check validation - Add pre-release suffix support (X.Y.Z-alpha, X.Y.Z-rc1) across version validation, workflow descriptions, and script documentation - Add --framework argument guard to install-packs.sh - Fix collect_versions error handling and check_versions error propagation --- .github/workflows/release-codeql.yml | 10 +- .github/workflows/release-tag.yml | 26 ++-- .github/workflows/release.yml | 2 +- .github/workflows/update-codeql.yml | 204 +++++++++++++++------------ .gitignore | 3 + scripts/install-packs.sh | 5 + scripts/update-release-version.sh | 96 +++++++++++-- 7 files changed, 231 insertions(+), 115 deletions(-) diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index 0600573a2..36bca6073 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -9,15 +9,15 @@ on: required: false type: boolean version: - description: 'Release version tag (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version tag (e.g., vX.Y.Z or vX.Y.Z-suffix). Must start with "v".' required: true type: string outputs: release_name: - description: 'The release name without "v" prefix (e.g., X.Y.Z)' + description: 'The release name without "v" prefix (e.g., X.Y.Z or X.Y.Z-alpha)' value: ${{ jobs.publish-codeql-packs.outputs.release_name }} version: - description: 'The full version string with "v" prefix (e.g., vX.Y.Z)' + description: 'The full version string with "v" prefix (e.g., vX.Y.Z or vX.Y.Z-alpha)' value: ${{ jobs.publish-codeql-packs.outputs.version }} # Note: This workflow is called exclusively via workflow_call from release.yml. @@ -79,7 +79,9 @@ jobs: - name: CodeQL - Install pack dependencies shell: bash run: | - qlt query run install-packs + export PATH="$(dirname "$QLT_CODEQL_PATH"):$PATH" + chmod +x ./scripts/install-packs.sh + ./scripts/install-packs.sh - name: CodeQL - Validate version consistency run: | diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index d0f05c2ff..0c0c7d6b0 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -4,18 +4,18 @@ on: workflow_call: inputs: version: - description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version (e.g., vX.Y.Z or vX.Y.Z-suffix). Must start with "v".' required: true type: string outputs: release_name: - description: 'The release name without "v" prefix (e.g., X.Y.Z)' + description: 'The release name without "v" prefix (e.g., X.Y.Z or X.Y.Z-alpha)' value: ${{ jobs.create-tag.outputs.release_name }} tag_sha: description: 'The commit SHA that the tag points to' value: ${{ jobs.create-tag.outputs.tag_sha }} version: - description: 'The full version string with "v" prefix (e.g., vX.Y.Z)' + description: 'The full version string with "v" prefix (e.g., vX.Y.Z or vX.Y.Z-alpha)' value: ${{ jobs.create-tag.outputs.version }} # Note: This workflow is called exclusively via workflow_call from release.yml. @@ -73,6 +73,14 @@ jobs: echo "â„šī¸ Tag ${TAG} does not exist yet" fi + - name: Tag - Update release version + if: steps.check-tag.outputs.tag_exists != 'true' + run: | + TAG_VERSION="${{ steps.version.outputs.release_name }}" + echo "Updating all version-bearing files to '${TAG_VERSION}'..." + chmod +x ./scripts/update-release-version.sh + ./scripts/update-release-version.sh "${TAG_VERSION}" + - name: Tag - Install QLT if: steps.check-tag.outputs.tag_exists != 'true' id: install-qlt @@ -91,14 +99,6 @@ jobs: echo "CodeQL Home: $QLT_CODEQL_HOME" echo "CodeQL Binary: $QLT_CODEQL_PATH" - - name: Tag - Update release version - if: steps.check-tag.outputs.tag_exists != 'true' - run: | - TAG_VERSION="${{ steps.version.outputs.release_name }}" - echo "Updating all version-bearing files to '${TAG_VERSION}'..." - chmod +x ./scripts/update-release-version.sh - ./scripts/update-release-version.sh "${TAG_VERSION}" - - name: Tag - Upgrade CodeQL pack lock files if: steps.check-tag.outputs.tag_exists != 'true' shell: bash @@ -117,7 +117,9 @@ jobs: if: steps.check-tag.outputs.tag_exists != 'true' shell: bash run: | - qlt query run install-packs + export PATH="$(dirname "$QLT_CODEQL_PATH"):$PATH" + chmod +x ./scripts/install-packs.sh + ./scripts/install-packs.sh - name: Tag - Setup Node.js for CDS compilation if: steps.check-tag.outputs.tag_exists != 'true' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c217c2e8e..2e95e4789 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ on: required: false type: boolean version: - description: 'Release version (e.g., vX.Y.Z). Must start with "v".' + description: 'Release version (e.g., vX.Y.Z or vX.Y.Z-suffix). Must start with "v". Supports pre-release suffixes like -alpha, -beta, -rc1.' required: true type: string diff --git a/.github/workflows/update-codeql.yml b/.github/workflows/update-codeql.yml index 72d409409..40f3686a1 100644 --- a/.github/workflows/update-codeql.yml +++ b/.github/workflows/update-codeql.yml @@ -7,117 +7,147 @@ on: - cron: '30 0 * * *' permissions: - contents: write - pull-requests: write + contents: read jobs: - update-codeql: - name: Update CodeQL CLI Dependencies + # ───────────────────────────────────────────────────────────────────────────── + # Step 1: Detect new CodeQL CLI version + # + # 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. + # ───────────────────────────────────────────────────────────────────────────── + detect-update: + name: Detect CodeQL CLI Update runs-on: ubuntu-latest + outputs: + current_version: ${{ steps.check-version.outputs.current_version }} + latest_version: ${{ steps.check-version.outputs.latest_version }} + update_needed: ${{ steps.check-version.outputs.update_needed }} + version: ${{ steps.check-version.outputs.version }} + steps: - - name: Update - Checkout repository + - name: Detect - Checkout repository uses: actions/checkout@v6 - - name: Update - Check latest CodeQL CLI version + - name: Detect - Check latest CodeQL CLI version id: check-version env: GH_TOKEN: ${{ github.token }} run: | echo "Checking latest CodeQL CLI version..." - current_version=$(jq .CodeQLCLI qlt.conf.json -r) - latest_version=$(gh release list --repo github/codeql-cli-binaries --json 'tagName,isLatest' --jq '.[] | select(.isLatest == true) | .tagName') - echo "Current CodeQL CLI version: $current_version" - echo "Latest CodeQL CLI version: $latest_version" + current_version=$(jq -r .CodeQLCLI qlt.conf.json) + latest_tag=$(gh release list --repo github/codeql-cli-binaries --json 'tagName,isLatest' --jq '.[] | select(.isLatest == true) | .tagName') + latest_clean="${latest_tag#v}" - # Remove 'v' prefix if present for comparison with current version - latest_clean=$(echo "$latest_version" | sed 's/^v//') + echo "Current CodeQL CLI version: ${current_version}" + echo "Latest CodeQL CLI version: ${latest_clean}" - if [ "$latest_clean" != "$current_version" ]; then - echo "Updating CodeQL CLI from $current_version to $latest_clean" + if [ "${latest_clean}" != "${current_version}" ]; then + echo "✅ Update available: ${current_version} → ${latest_clean}" echo "update_needed=true" >> $GITHUB_OUTPUT - echo "current_version=$current_version" >> $GITHUB_OUTPUT - echo "latest_version=$latest_clean" >> $GITHUB_OUTPUT - echo "latest_version_tag=$latest_version" >> $GITHUB_OUTPUT - - # Update qlt.conf.json with all properties - echo "Updating qlt.conf.json with all properties for version $latest_clean" - jq --arg cli_version "$latest_clean" \ - --arg std_lib "codeql-cli/$latest_version" \ - --arg bundle "codeql-bundle-$latest_version" \ - '.CodeQLCLI = $cli_version | .CodeQLStandardLibrary = $std_lib | .CodeQLCLIBundle = $bundle' \ - qlt.conf.json > qlt.conf.json.tmp && mv qlt.conf.json.tmp qlt.conf.json - - echo "Updated qlt.conf.json contents:" - cat qlt.conf.json + echo "current_version=${current_version}" >> $GITHUB_OUTPUT + echo "latest_version=${latest_clean}" >> $GITHUB_OUTPUT + echo "version=v${latest_clean}" >> $GITHUB_OUTPUT else - echo "CodeQL CLI is already up-to-date at version $current_version." + echo "â„šī¸ CodeQL CLI is already up-to-date at version ${current_version}" echo "update_needed=false" >> $GITHUB_OUTPUT fi - - name: Update - Install QLT - if: steps.check-version.outputs.update_needed == 'true' - id: install-qlt - uses: advanced-security/codeql-development-toolkit/.github/actions/install-qlt@main - with: - qlt-version: 'latest' - add-to-path: true - - - name: Update - Install CodeQL - if: steps.check-version.outputs.update_needed == 'true' - shell: bash - run: | - echo "Installing CodeQL" - qlt codeql run install - echo "-----------------------------" - echo "CodeQL Home: $QLT_CODEQL_HOME" - echo "CodeQL Binary: $QLT_CODEQL_PATH" - - - name: Update - Upgrade CodeQL pack lock files - if: steps.check-version.outputs.update_needed == 'true' - shell: bash - run: | - echo "Upgrading CodeQL pack lock files..." - find . -name "qlpack.yml" -type f | sort | while read -r qlpack_file; do - pack_dir=$(dirname "$qlpack_file") - echo "Upgrading pack in directory: $pack_dir" - cd "$pack_dir" - $QLT_CODEQL_PATH pack upgrade - cd - > /dev/null - done - echo "Finished upgrading all CodeQL pack lock files" - - - name: Update - Create Pull Request - if: steps.check-version.outputs.update_needed == 'true' - uses: peter-evans/create-pull-request@v8 - with: - title: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" - body: | - This PR upgrades the CodeQL CLI version to ${{ steps.check-version.outputs.latest_version_tag }}. - - **Changes made:** - - Updated `CodeQLCLI` to `${{ steps.check-version.outputs.latest_version }}` - - Updated `CodeQLStandardLibrary` to `codeql-cli/${{ steps.check-version.outputs.latest_version_tag }}` - - Updated `CodeQLCLIBundle` to `codeql-bundle-${{ steps.check-version.outputs.latest_version_tag }}` - - Upgraded all CodeQL pack lock files using `codeql pack upgrade` - commit-message: "Upgrade CodeQL CLI dependency to ${{ steps.check-version.outputs.latest_version_tag }}" - delete-branch: true - branch: "codeql/upgrade-to-${{ steps.check-version.outputs.latest_version_tag }}" - - - name: Update - Summary + - name: Detect - Summary run: | echo "## CodeQL CLI Update Check" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY 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 "| Property | Old Value | New Value |" >> $GITHUB_STEP_SUMMARY - echo "| -------- | --------- | --------- |" >> $GITHUB_STEP_SUMMARY - echo "| CodeQLCLI | ${{ steps.check-version.outputs.current_version }} | ${{ steps.check-version.outputs.latest_version }} |" >> $GITHUB_STEP_SUMMARY - echo "| CodeQLStandardLibrary | — | codeql-cli/${{ steps.check-version.outputs.latest_version_tag }} |" >> $GITHUB_STEP_SUMMARY - echo "| CodeQLCLIBundle | — | codeql-bundle-${{ steps.check-version.outputs.latest_version_tag }} |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "A pull request has been created with these changes." >> $GITHUB_STEP_SUMMARY + echo "Initiating release pipeline for \`v${{ steps.check-version.outputs.latest_version }}\`..." >> $GITHUB_STEP_SUMMARY else - echo "â„šī¸ CodeQL CLI is already up-to-date. No changes needed." >> $GITHUB_STEP_SUMMARY + echo "â„šī¸ CodeQL CLI is already up-to-date. No release needed." >> $GITHUB_STEP_SUMMARY fi + + # ───────────────────────────────────────────────────────────────────────────── + # Step 2: Create release tag + # + # 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. + # + # The release-tag environment approval gate provides human-in-the-loop review + # before any changes are committed. + # ───────────────────────────────────────────────────────────────────────────── + ensure-tag: + name: Ensure Release Tag + 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 + + 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: Release - Create GitHub Release + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + with: + files: | + dist-packs/*.tar.gz + generate_release_notes: true + tag_name: ${{ needs.detect-update.outputs.version }} + + - name: Release - Summary + run: | + VERSION="${{ needs.detect-update.outputs.version }}" + RELEASE_NAME="${{ needs.detect-update.outputs.latest_version }}" + echo "## Automated Release Summary" >> $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 "" >> $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 diff --git a/.gitignore b/.gitignore index 9f3d55a64..533f3b6dc 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,6 @@ dbs .codeql/ *.qlx +# workspace customization file +codeql-sap-js.code-workspace + diff --git a/scripts/install-packs.sh b/scripts/install-packs.sh index d5c6e0ab6..0ebad75c1 100755 --- a/scripts/install-packs.sh +++ b/scripts/install-packs.sh @@ -35,6 +35,11 @@ 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 ;; diff --git a/scripts/update-release-version.sh b/scripts/update-release-version.sh index 4e60634bb..156ff7dd3 100755 --- a/scripts/update-release-version.sh +++ b/scripts/update-release-version.sh @@ -22,8 +22,11 @@ set -euo pipefail ## javascript/heuristic-models/ext/qlpack.yml ## javascript/heuristic-models/tests/qlpack.yml ## -## Additionally updates internal dependency references within qlpack.yml files -## that reference other packs in this repository (e.g., ^X.Y.Z constraints). +## Additionally updates: +## - Internal dependency references within qlpack.yml files +## that reference other packs in this repository (e.g., ^X.Y.Z constraints). +## - qlt.conf.json (CodeQLCLI, CodeQLStandardLibrary, CodeQLCLIBundle) +## using the base version (X.Y.Z) derived by stripping any pre-release suffix. ## ## Usage: ## ./scripts/update-release-version.sh @@ -31,6 +34,7 @@ set -euo pipefail ## ## Examples: ## ./scripts/update-release-version.sh 2.4.0 +## ./scripts/update-release-version.sh 2.4.0-rc1 ## ./scripts/update-release-version.sh --check ## ./scripts/update-release-version.sh --check 2.4.0 @@ -78,8 +82,9 @@ Usage: $0 Deterministically updates the release version across all version-bearing files. ARGUMENTS: - The new version to set (e.g., 2.4.0). + The new version to set (e.g., 2.4.0 or 2.4.0-alpha). The 'v' prefix is optional and will be normalized. + Supports pre-release suffixes: -alpha, -beta, -rc1, etc. OPTIONS: --check [] Check version consistency across all files. @@ -90,6 +95,7 @@ OPTIONS: EXAMPLES: $0 2.4.0 Update all files to version 2.4.0 + $0 2.4.0-alpha Update all files to pre-release version 2.4.0-alpha $0 v2.4.0 Same as above (v prefix is stripped automatically) $0 --check Verify all version-bearing files are consistent $0 --check 2.4.0 Verify all files contain version 2.4.0 @@ -112,7 +118,8 @@ collect_versions() { fi versions+=("${qlpack_file}|${pack_version}") else - echo "WARNING: ${qlpack_file} not found" >&2 + echo "ERROR: ${qlpack_file} not found" >&2 + return 1 fi done @@ -129,6 +136,12 @@ check_versions() { echo "=== Version Consistency Check ===" echo "" + local version_output + if ! version_output=$(collect_versions); then + echo "❌ Failed to collect versions" >&2 + return 1 + fi + while IFS='|' read -r file version; do file_count=$((file_count + 1)) @@ -151,10 +164,26 @@ check_versions() { all_consistent=false fi fi - done < <(collect_versions) + done <<< "${version_output}" + + ## Also check qlt.conf.json consistency + local qlt_config="${REPO_ROOT}/qlt.conf.json" + if [[ -f "${qlt_config}" ]]; then + local cli_version + cli_version=$(grep -o '"CodeQLCLI":[[:space:]]*"[^"]*"' "${qlt_config}" | grep -o '"[^"]*"$' | tr -d '"') + ## Derive expected base version: strip pre-release suffix from first_version or expected_version + local check_base="${expected_version:-${first_version}}" + check_base="${check_base%%-*}" + if [[ "${cli_version}" == "${check_base}" ]]; then + echo " ✅ qlt.conf.json: CodeQLCLI ${cli_version}" + else + echo " ❌ qlt.conf.json: CodeQLCLI ${cli_version} (expected ${check_base})" + all_consistent=false + fi + fi echo "" - echo "Checked ${file_count} version-bearing files." + echo "Checked ${file_count} version-bearing files + qlt.conf.json." if [[ "${all_consistent}" == true ]]; then if [[ -n "${expected_version}" ]]; then @@ -169,12 +198,12 @@ check_versions() { fi } -## Validate version format (X.Y.Z) +## Validate version format (X.Y.Z or X.Y.Z-suffix) validate_version() { local version="$1" - if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then echo "ERROR: Invalid version format '${version}'" >&2 - echo "Expected format: X.Y.Z (e.g., 2.4.0)" >&2 + echo "Expected format: X.Y.Z or X.Y.Z-suffix (e.g., 2.4.0, 2.4.0-alpha, 2.4.0-rc1)" >&2 return 1 fi } @@ -187,8 +216,50 @@ update_pack_version() { rm -f "${file}.bak" } +## Update qlt.conf.json with the base version (suffix stripped) +## e.g., 2.4.0-alpha -> CodeQLCLI: "2.4.0", CodeQLStandardLibrary: "codeql-cli/v2.4.0", etc. +update_qlt_config() { + local new_version="$1" + local dry_run="${2:-false}" + local qlt_config="${REPO_ROOT}/qlt.conf.json" + + # Derive the base version by stripping any pre-release suffix + local base_version="${new_version%%-*}" + + if [[ ! -f "${qlt_config}" ]]; then + echo "WARNING: qlt.conf.json not found, skipping" >&2 + return 0 + fi + + if [[ "${dry_run}" == true ]]; then + echo " [DRY RUN] qlt.conf.json: CodeQLCLI -> ${base_version}" + return 0 + fi + + # Use jq for reliable JSON manipulation (learned from update-codeql.yml) + if command -v jq &>/dev/null; then + jq --arg cli_version "${base_version}" \ + --arg std_lib "codeql-cli/v${base_version}" \ + --arg bundle "codeql-bundle-v${base_version}" \ + '.CodeQLCLI = $cli_version | .CodeQLStandardLibrary = $std_lib | .CodeQLCLIBundle = $bundle' \ + "${qlt_config}" > "${qlt_config}.tmp" && mv "${qlt_config}.tmp" "${qlt_config}" + else + # Fallback to sed if jq is not available + local tmp_file + tmp_file=$(mktemp) + sed \ + -e "s/\"CodeQLCLI\":[[:space:]]*\"[^\"]*\"/\"CodeQLCLI\": \"${base_version}\"/" \ + -e "s/\"CodeQLStandardLibrary\":[[:space:]]*\"[^\"]*\"/\"CodeQLStandardLibrary\": \"codeql-cli\/v${base_version}\"/" \ + -e "s/\"CodeQLCLIBundle\":[[:space:]]*\"[^\"]*\"/\"CodeQLCLIBundle\": \"codeql-bundle-v${base_version}\"/" \ + "${qlt_config}" > "${tmp_file}" + mv "${tmp_file}" "${qlt_config}" + fi + echo " ✅ qlt.conf.json: CodeQLCLI -> ${base_version}" +} + ## Update internal dependency references in a qlpack.yml file ## e.g., advanced-security/javascript-sap-cap-models: "^2.3.0" -> "^2.4.0" +## e.g., advanced-security/javascript-sap-cap-models: "^2.3.0" -> "^2.4.0-alpha" ## and advanced-security/javascript-heuristic-models: 2.3.0 -> 2.4.0 update_internal_deps() { local file="$1" @@ -256,11 +327,14 @@ update_versions() { fi done + ## Update qlt.conf.json + update_qlt_config "${new_version}" "${dry_run}" + echo "" if [[ "${dry_run}" == true ]]; then - echo "Would update ${updated_count} files. (Dry run — no files modified)" + echo "Would update ${updated_count} qlpack files + qlt.conf.json. (Dry run — no files modified)" else - echo "Updated ${updated_count} files to version ${new_version}." + echo "Updated ${updated_count} qlpack files + qlt.conf.json to version ${new_version}." echo "" echo "Next steps:" echo " 1. Run 'codeql pack upgrade' on all packs to update lock files" From 8c15f8a9b4cc41913d277372f506b9e12289f94f Mon Sep 17 00:00:00 2001 From: Nathan Randall Date: Sun, 15 Feb 2026 16:15:03 -0700 Subject: [PATCH 6/6] Deduplicate published packs info in workflows --- .github/workflows/release-codeql.yml | 40 +++++++++++----------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/.github/workflows/release-codeql.yml b/.github/workflows/release-codeql.yml index 36bca6073..4e09fef19 100644 --- a/.github/workflows/release-codeql.yml +++ b/.github/workflows/release-codeql.yml @@ -43,6 +43,19 @@ jobs: release_name: ${{ steps.version.outputs.release_name }} version: ${{ steps.version.outputs.version }} + env: + PUBLISHABLE_PACKS_LIST: | + javascript/frameworks/cap/src + javascript/frameworks/cap/ext + javascript/frameworks/cap/lib + javascript/frameworks/ui5/src + javascript/frameworks/ui5/ext + javascript/frameworks/ui5/lib + javascript/frameworks/xsjs/src + javascript/frameworks/xsjs/ext + javascript/frameworks/xsjs/lib + javascript/heuristic-models/ext + steps: - name: CodeQL - Validate and parse version id: version @@ -95,19 +108,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # Publishable packs: queries, models (ext), and libraries (lib) - PUBLISHABLE_PACKS=( - "javascript/frameworks/cap/src" - "javascript/frameworks/cap/ext" - "javascript/frameworks/cap/lib" - "javascript/frameworks/ui5/src" - "javascript/frameworks/ui5/ext" - "javascript/frameworks/ui5/lib" - "javascript/frameworks/xsjs/src" - "javascript/frameworks/xsjs/ext" - "javascript/frameworks/xsjs/lib" - "javascript/heuristic-models/ext" - ) + # Read the shared pack list from the job-level environment variable. + mapfile -t PUBLISHABLE_PACKS <<< "${PUBLISHABLE_PACKS_LIST}" echo "Publishing CodeQL packs..." for pack_dir in "${PUBLISHABLE_PACKS[@]}"; do @@ -126,18 +128,6 @@ jobs: run: echo "â­ī¸ CodeQL pack publishing disabled via workflow input" - name: CodeQL - Bundle CodeQL packs - env: - PUBLISHABLE_PACKS_LIST: | - javascript/frameworks/cap/src - javascript/frameworks/cap/ext - javascript/frameworks/cap/lib - javascript/frameworks/ui5/src - javascript/frameworks/ui5/ext - javascript/frameworks/ui5/lib - javascript/frameworks/xsjs/src - javascript/frameworks/xsjs/ext - javascript/frameworks/xsjs/lib - javascript/heuristic-models/ext run: | mkdir -p dist-packs