diff --git a/.github/skills/upgrade-codeql-cli-and-packs/SKILL.md b/.github/skills/upgrade-codeql-cli-and-packs/SKILL.md index 7add189..28b73c1 100644 --- a/.github/skills/upgrade-codeql-cli-and-packs/SKILL.md +++ b/.github/skills/upgrade-codeql-cli-and-packs/SKILL.md @@ -69,17 +69,25 @@ gh codeql set-version vX.XX.Y codeql version # Verify installation ``` -#### 1.3 Update package.json Versions +#### 1.3 Update All Version-Bearing Files -All `package.json` files must have their `version` field set to match the CLI version (without the "v" prefix): +Use the `update-release-version.sh` script to deterministically update `.codeql-version`, all `package.json` files, and all `codeql-pack.yml` files in a single command: -| File | Field to Update | -| --------------------- | --------------- | -| `package.json` | `version` | -| `client/package.json` | `version` | -| `server/package.json` | `version` | +```bash +./server/scripts/update-release-version.sh X.XX.Y +``` + +This updates all 22 version-bearing files. Preview changes first with `--dry-run`: + +```bash +./server/scripts/update-release-version.sh --dry-run X.XX.Y +``` -Example: If `.codeql-version` is `v2.24.1`, set all `package.json` versions to `"version": "2.24.1"`. +Verify consistency with `--check`: + +```bash +./server/scripts/update-release-version.sh --check X.XX.Y +``` After updating, regenerate the lock file: @@ -125,6 +133,8 @@ Then re-verify the `cliVersion` is compatible. ### Phase 3: Update codeql-pack.yml Files +> **Note**: The `version` field in all `codeql-pack.yml` files is already updated by the `update-release-version.sh` script in Phase 1.3. This phase focuses on updating `codeql/*-all` **dependency versions** for compatibility. + #### 3.1 Files to Update All `codeql-pack.yml` files under `server/ql/*/tools/`: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a887200..8eee4e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,11 @@ on: - 'v*' workflow_dispatch: inputs: + publish_codeql_packs: + default: true + description: 'Publish CodeQL tool query packs to GHCR. Disable for pre-release or re-run scenarios where packs already exist.' + required: false + type: boolean version: description: 'Release version (e.g., vX.Y.Z). Must start with "v".' required: true @@ -90,23 +95,31 @@ jobs: echo "::warning::Tag ${TAG} not found, building from current checkout at ${CURRENT_SHA:0:8}" fi - - name: Release - Install dependencies - run: npm ci --include=optional - - - name: Release - Build server - run: npm run build -w server - - name: Release - Setup CodeQL environment uses: ./.github/actions/setup-codeql-environment with: add-to-path: true install-language-runtimes: false + - name: Release - Update release version + run: | + TAG_VERSION="${{ steps.version.outputs.release_name }}" + echo "Updating all version-bearing files to '${TAG_VERSION}'..." + ./server/scripts/update-release-version.sh "${TAG_VERSION}" + + - name: Release - Install dependencies + run: npm install --include=optional + + - name: Release - Build server + run: npm run build -w server + - name: Release - Install CodeQL pack dependencies run: server/scripts/install-packs.sh - name: Release - Publish CodeQL tool query packs - if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + if: | + (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') + && (github.event_name != 'workflow_dispatch' || inputs.publish_codeql_packs) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -123,6 +136,10 @@ jobs: fi done + - name: Release - Skip CodeQL tool query pack publishing + if: github.event_name == 'workflow_dispatch' && !inputs.publish_codeql_packs + run: echo "⏭️ CodeQL tool query pack publishing disabled via workflow input" + - name: Release - Bundle CodeQL tool query packs run: | mkdir -p dist-packs @@ -206,13 +223,19 @@ jobs: - name: Release - Summary run: | echo "## Release Summary" >> $GITHUB_STEP_SUMMARY - echo "✅ Server built successfully" >> $GITHUB_STEP_SUMMARY - echo "✅ npm package published to GitHub Packages" >> $GITHUB_STEP_SUMMARY - echo "✅ CodeQL tool query packs published to GHCR" >> $GITHUB_STEP_SUMMARY - echo "✅ Distribution package created" >> $GITHUB_STEP_SUMMARY - echo "✅ Production dependencies installed" >> $GITHUB_STEP_SUMMARY - echo "✅ Archive created: codeql-development-mcp-server-${{ steps.version.outputs.version }}.tar.gz" >> $GITHUB_STEP_SUMMARY - echo "✅ CodeQL tool query pack archives bundled" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY + echo "| ---- | ------ |" >> $GITHUB_STEP_SUMMARY + echo "| Server build | ✅ Success |" >> $GITHUB_STEP_SUMMARY + echo "| Version validation | ✅ All files match ${{ steps.version.outputs.release_name }} |" >> $GITHUB_STEP_SUMMARY + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.publish_codeql_packs }}" != "true" ]]; then + echo "| CodeQL pack publish | ⏭️ Skipped (disabled via input) |" >> $GITHUB_STEP_SUMMARY + else + echo "| CodeQL pack publish | ✅ Published to GHCR |" >> $GITHUB_STEP_SUMMARY + fi + echo "| npm package | ✅ Published to GitHub Packages |" >> $GITHUB_STEP_SUMMARY + echo "| Distribution archive | ✅ Created |" >> $GITHUB_STEP_SUMMARY + echo "| CodeQL pack bundles | ✅ Bundled |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Package Contents" >> $GITHUB_STEP_SUMMARY echo "- \`server/dist/\` - Bundled JavaScript output" >> $GITHUB_STEP_SUMMARY diff --git a/server/scripts/update-release-version.sh b/server/scripts/update-release-version.sh new file mode 100755 index 0000000..9b565c4 --- /dev/null +++ b/server/scripts/update-release-version.sh @@ -0,0 +1,389 @@ +#!/usr/bin/env bash +set -euo pipefail + +## update-release-version.sh +## Deterministically updates the release version across all version-bearing files +## in the codeql-development-mcp-server repository. +## +## Version-bearing files: +## .codeql-version (vX.Y.Z format) +## package.json (X.Y.Z format) +## client/package.json (X.Y.Z format) +## server/package.json (X.Y.Z format) +## server/ql/*/tools/src/codeql-pack.yml (X.Y.Z format) +## server/ql/*/tools/test/codeql-pack.yml (X.Y.Z format) +## +## The base version (X.Y.Z without any prerelease suffix) must correspond to +## an actual CodeQL CLI release. The script validates this by checking against +## the installed `codeql` CLI or the `.codeql-version` file. +## +## Usage: +## ./server/scripts/update-release-version.sh +## ./server/scripts/update-release-version.sh --check [] +## +## Examples: +## ./server/scripts/update-release-version.sh 2.24.1 +## ./server/scripts/update-release-version.sh 2.24.1-beta +## ./server/scripts/update-release-version.sh v2.24.1-beta +## ./server/scripts/update-release-version.sh --check +## ./server/scripts/update-release-version.sh --check 2.24.1-beta + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +## Supported languages for ql-mcp-* packs +LANGUAGES=("actions" "cpp" "csharp" "go" "java" "javascript" "python" "ruby" "swift") + +usage() { + cat < + $0 --check [] + +Deterministically updates the release version across all version-bearing files. +The base version (X.Y.Z, without prerelease suffixes) must correspond to an +actual CodeQL CLI release. + +ARGUMENTS: + The new version to set (e.g., 2.24.1 or 2.24.1-beta). + The 'v' prefix is optional and will be normalized. + The base version (X.Y.Z) is validated against the + installed CodeQL CLI or .codeql-version file. + +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. + --skip-cli-validation Skip CodeQL CLI version validation (not recommended). + -h, --help Show this help message. + +EXAMPLES: + $0 2.24.1 Update all files to version 2.24.1 + $0 2.24.1-beta Update all files to version 2.24.1-beta + $0 v2.24.1-beta Same as above (v prefix is stripped automatically) + $0 --check Verify all version-bearing files are consistent + $0 --check 2.24.1 Verify all files contain version 2.24.1 + $0 --dry-run 2.24.1 Preview changes without writing files +EOF +} + +## Collect all version-bearing files and their current versions +collect_versions() { + local versions=() + + ## .codeql-version (stores vX.Y.Z) + local codeql_version_file="${REPO_ROOT}/.codeql-version" + if [[ -f "${codeql_version_file}" ]]; then + local raw_version + raw_version=$(tr -d '[:space:]' < "${codeql_version_file}") + versions+=(".codeql-version|${raw_version#v}") + else + echo "WARNING: .codeql-version not found" >&2 + fi + + ## package.json files + local pkg_files=("package.json" "client/package.json" "server/package.json") + for pkg_file in "${pkg_files[@]}"; do + local full_path="${REPO_ROOT}/${pkg_file}" + if [[ -f "${full_path}" ]]; then + local pkg_version + pkg_version=$(grep -m1 '"version"' "${full_path}" | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + versions+=("${pkg_file}|${pkg_version}") + else + echo "WARNING: ${pkg_file} not found" >&2 + fi + done + + ## codeql-pack.yml files (src and test packs for each language) + for lang in "${LANGUAGES[@]}"; do + for pack_type in "src" "test"; do + local pack_file="server/ql/${lang}/tools/${pack_type}/codeql-pack.yml" + local full_path="${REPO_ROOT}/${pack_file}" + if [[ -f "${full_path}" ]]; then + local pack_version + pack_version=$(grep -m1 "^version:" "${full_path}" | awk '{print $2}') + versions+=("${pack_file}|${pack_version}") + fi + done + 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 or X.Y.Z-suffix) +validate_version() { + local version="$1" + 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 or X.Y.Z-suffix (e.g., 2.24.1 or 2.24.1-beta)" >&2 + return 1 + fi +} + +## Extract the base version (X.Y.Z) from a potentially suffixed version (X.Y.Z-beta) +extract_base_version() { + local version="$1" + echo "${version%%-*}" +} + +## Validate that the base version corresponds to an actual CodeQL CLI version. +## Checks in order: +## 1. If 'codeql' CLI is on PATH, compare its version +## 2. Fall back to the .codeql-version file in the repo +validate_cli_version() { + local base_version="$1" + + echo "=== CodeQL CLI Version Validation ===" + echo " Required base version: ${base_version}" + + ## Try the installed CLI first + if command -v codeql >/dev/null 2>&1; then + local installed_version + installed_version=$(codeql version --format=terse 2>/dev/null || echo "unknown") + echo " Installed CLI version: ${installed_version}" + if [[ "${installed_version}" == "${base_version}" ]]; then + echo " ✅ Base version ${base_version} matches installed CodeQL CLI" + return 0 + else + echo " ❌ Base version ${base_version} does not match installed CodeQL CLI (${installed_version})" >&2 + echo "" >&2 + echo " The base version (X.Y.Z, without prerelease suffix) must match an" >&2 + echo " available CodeQL CLI release. Either:" >&2 + echo " - Install the correct CLI: gh codeql set-version ${base_version}" >&2 + echo " - Use a version matching the installed CLI: ${installed_version}" >&2 + echo " - Skip validation with --skip-cli-validation (not recommended)" >&2 + return 1 + fi + fi + + ## Fall back to .codeql-version file + local codeql_version_file="${REPO_ROOT}/.codeql-version" + if [[ -f "${codeql_version_file}" ]]; then + local file_version + file_version=$(tr -d '[:space:]' < "${codeql_version_file}") + file_version="${file_version#v}" ## Strip v prefix + echo " .codeql-version file: ${file_version}" + if [[ "${file_version}" == "${base_version}" ]]; then + echo " ✅ Base version ${base_version} matches .codeql-version" + return 0 + else + echo " ❌ Base version ${base_version} does not match .codeql-version (${file_version})" >&2 + echo "" >&2 + echo " The base version (X.Y.Z, without prerelease suffix) must correspond" >&2 + echo " to the CodeQL CLI version in .codeql-version." >&2 + echo " Current .codeql-version: v${file_version}" >&2 + echo " Skip validation with --skip-cli-validation (not recommended)" >&2 + return 1 + fi + fi + + echo " WARNING: Cannot validate base version — no CodeQL CLI on PATH and no .codeql-version file" >&2 + return 1 +} + +## Update a JSON file's "version" field using sed +update_json_version() { + local file="$1" + local new_version="$2" + ## Match the "version": "..." line and replace the version value + sed -i.bak "s/\"version\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/\"version\": \"${new_version}\"/" "${file}" + rm -f "${file}.bak" +} + +## Update a codeql-pack.yml file's version field using sed +update_pack_version() { + local file="$1" + local new_version="$2" + ## Match the version: line at the start and replace the version value + sed -i.bak "s/^version:[[:space:]]*.*/version: ${new_version}/" "${file}" + rm -f "${file}.bak" +} + +## 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 "" + + ## 1. Update .codeql-version (uses v prefix) + local codeql_version_file="${REPO_ROOT}/.codeql-version" + if [[ -f "${codeql_version_file}" ]]; then + local old_version + old_version=$(tr -d '[:space:]' < "${codeql_version_file}") + if [[ "${dry_run}" == true ]]; then + echo " [DRY RUN] .codeql-version: ${old_version} -> v${new_version}" + else + printf "v%s\n" "${new_version}" > "${codeql_version_file}" + echo " ✅ .codeql-version: ${old_version} -> v${new_version}" + fi + updated_count=$((updated_count + 1)) + fi + + ## 2. Update package.json files + local pkg_files=("package.json" "client/package.json" "server/package.json") + for pkg_file in "${pkg_files[@]}"; do + local full_path="${REPO_ROOT}/${pkg_file}" + if [[ -f "${full_path}" ]]; then + local old_version + old_version=$(grep -m1 '"version"' "${full_path}" | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + if [[ "${dry_run}" == true ]]; then + echo " [DRY RUN] ${pkg_file}: ${old_version} -> ${new_version}" + else + update_json_version "${full_path}" "${new_version}" + echo " ✅ ${pkg_file}: ${old_version} -> ${new_version}" + fi + updated_count=$((updated_count + 1)) + fi + done + + ## 3. Update codeql-pack.yml files (src and test packs for each language) + for lang in "${LANGUAGES[@]}"; do + for pack_type in "src" "test"; do + local pack_file="server/ql/${lang}/tools/${pack_type}/codeql-pack.yml" + local full_path="${REPO_ROOT}/${pack_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] ${pack_file}: ${old_version} -> ${new_version}" + else + update_pack_version "${full_path}" "${new_version}" + echo " ✅ ${pack_file}: ${old_version} -> ${new_version}" + fi + updated_count=$((updated_count + 1)) + fi + done + 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 'npm install' to regenerate package-lock.json" + echo " 2. Run 'npm run build-and-test' to validate the changes" + echo " 3. Commit the changes and tag with 'v${new_version}'" + fi +} + +## Parse arguments +CHECK_MODE=false +DRY_RUN=false +SKIP_CLI_VALIDATION=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 + ;; + --skip-cli-validation) + SKIP_CLI_VALIDATION=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}" + +## Validate that the base version matches an actual CodeQL CLI release +if [[ "${SKIP_CLI_VALIDATION}" == false ]]; then + BASE_VERSION=$(extract_base_version "${NEW_VERSION}") + validate_cli_version "${BASE_VERSION}" + echo "" +else + echo "⚠️ CLI version validation skipped (--skip-cli-validation)" + echo "" +fi + +update_versions "${NEW_VERSION}" "${DRY_RUN}"