Skip to content

Commit dd2357d

Browse files
felickzCopilot
andauthored
feat(publish): gate auto-publish on releases + publish summary table + docs (#160)
* feat(publish): add publish-summary table + GitHub Release upsert publish.yml now records a per-pack JSON result fragment (published / up-to-date / failed) from each of the 4 jobs' matrix entries, uploads them as artifacts, and a new summary job downloads and merges them into a unified markdown table (languages x pack types) that is: 1. written to the Actions run summary ($GITHUB_STEP_SUMMARY) 2. upserted into the matching GitHub Release body (.release.yml's version -> vX.Y.Z tag), inside marker comments so "What's Changed" and other release content is preserved across runs New scripts: - .github/scripts/build-publish-summary.sh - .github/scripts/upsert-release-table.sh Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(publish): avoid tee+pipe fragility in Build summary table step The previous form piped the script's stdout through tee and redirected tee's own stdout (not the file write) into GITHUB_STEP_SUMMARY. Combined with 'bash -e' (no pipefail) at the step level, this masked failures and produced an empty summary.md in a real run, upserting empty publish-summary markers into the v0.2.2 release. Switch to plain sequential redirects and add debug output (fragment listing, line/byte counts) to make future failures visible in the step log. * feat(publish): gate auto-publish on .release.yml changes; document release process - publish.yml's push trigger now only fires when .release.yml changes (paths filter), not on every push to main. workflow_dispatch remains as the manual escape hatch for one-off hotfix publishes. - Rewrite CONTRIBUTING.md's Releases & publishing section to match: shipping a pack change now just stages it until the next release cut or a manual dispatch; add a Cutting a release walkthrough for update-release.yml/patch-release-me now that .release.yml's CodeQL Pack Versions location (#158) actually drives every pack's version; document the -alpha.N + workflow_dispatch hotfix convention and note #155 as an accepted one-off exception; note #159 as the recovery path if .release.yml's version is ever hand-edited directly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: address Copilot review feedback on PR #160 - Anchor CURRENT_VERSION extraction to '^version:' (with head -1) in all four publish.yml jobs so a stray 'version' substring elsewhere in a qlpack.yml (e.g. a comment) can't be matched instead of the real field. - upsert-release-table.sh: only take the in-place marker-replacement path when BOTH the start and end marker are present in the existing release body; otherwise append, so a corrupted/partial marker pair can't cause the awk script to silently truncate the rest of the release notes. - CONTRIBUTING.md: grammar fix (find/replaces -> finds and replaces). Verified gh release create --notes-file + --generate-notes together is NOT mutually exclusive in gh 2.96.0 (contrary to a review comment) -- confirmed empirically: the v0.2.3 release this script created has both the publish-summary table (from --notes-file) and the auto-generated 'What's Changed' changelog (from --generate-notes) in its body. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * feat(publish): add CodeQL standard library versions cross-check table Every publish.yml run now builds and upserts a second table into the matching GitHub Release: for each language, the locked codeql/<lang>-all version (from lib/codeql-pack.lock.yml) versus the version github/codeql itself bundles with the pinned CodeQL CLI (.codeqlversion), read from its own <lang>/ql/lib/qlpack.yml at tag codeql-cli/v<version>. Mismatches are flagged with an actions warning annotation and a table marker, giving a machine-checkable tripwire for the drift risk documented in CONTRIBUTING.md (CI's 'codeql pack install' step is non-resolving, so a forgotten 'codeql pack upgrade <dir>' after a .codeqlversion bump silently pins a language to a stale library version forever). - Add .github/scripts/build-codeql-lib-versions-table.sh - Parameterize upsert-release-table.sh with an optional block-name arg so multiple independent marker-comment blocks can coexist in one release body without clobbering each other - Wire both into publish.yml's summary job - Document the new table and cross-check in CONTRIBUTING.md Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix: mark build-codeql-lib-versions-table.sh executable git-created file lost the exec bit, causing publish.yml's new step to fail with 'Permission denied' (exit 126). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(build-codeql-lib-versions-table): check src lock file, not lib CONTRIBUTING.md's 'Supported CodeQL versions' table already documents <language>/src/codeql-pack.lock.yml as the source-of-truth lock file (the queries pack CI actually compiles/tests against), not lib. Reading from lib/codeql-pack.lock.yml instead meant the tripwire could miss drift in the src lock, or flag drift that didn't actually affect the compiled queries. Switch the check (and its remediation guidance) to src, per Copilot review feedback on PR #160. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * feat(publish): commit/tag-anchored links + queries pack in lib-versions table - Discover direct codeql/* dependencies dynamically from each language's src/qlpack.yml instead of hardcoding codeql/<lang>-all, so this also covers codeql/<lang>-queries for C++/C# (which additionally depend on the upstream standard queries pack). - Link every "our locked" version to the exact commit/file/line in this repo (blob/<GITHUB_SHA>/...#L<n>) and every "upstream" version to the exact file/line in github/codeql at the matching codeql-cli/vX tag, so each row can be verified with one click instead of trusting the table. - Rename the section/table to "CodeQL standard library & query pack versions" and update CONTRIBUTING.md references accordingly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * feat: automate CodeQL CLI version bump detection and dependency refresh Adds the two workflows needed to close the gap identified while reviewing PR #118 (codeql pack upgrade weekly cron): - detect-codeql-release.yml: weekly (+ workflow_dispatch) tripwire that compares .codeqlversion against github/codeql-cli-binaries' latest release and keeps a single tracking issue open/updated while behind, auto-closing it once caught up. Never opens a PR itself. - update-codeql-version.yml: workflow_dispatch with a codeql_version input. Bumps .codeqlversion, runs codeql pack upgrade <dir> for every pack, and opens a PR via the same GitHub App token pattern as update-release.yml so CI actually runs on it (a GITHUB_TOKEN-authored PR would not trigger downstream workflows). Together these cover detection + mechanical dependency refresh; fixing compile/test breakage from upstream API changes and bumping each pack's own version: still needs a human (or delegated Copilot coding agent) - documented as a 3-step process in CONTRIBUTING.md. Rewrites CONTRIBUTING.md's "Updating the pinned CodeQL CLI/library version" section to describe this new process and adds reference-style links for both new workflows and codeql-cli-binaries. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(publish): gate release-table upsert to push trigger, clarify failed-check cell Two fixes from Copilot review on PR #160: - publish.yml: only run the "Upsert release table" step on the push (release-cut) trigger, not workflow_dispatch. A manual one-off hotfix publish (see CONTRIBUTING.md) still points .release.yml at the last cut release, so upserting that run's tables into it would overwrite an already-published release's notes with a misleading summary of an unrelated hotfix run. - build-publish-summary.sh: when a pack's version-check step itself fails, previous_version can be empty/null, which rendered as the confusing "publish failed (still ``)". Now shows an explicit "previous version unknown" message in that case. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(publish): check both src and lib lock files for CodeQL version drift src/qlpack.yml and lib/qlpack.yml each declare their own codeql/<lang>-all dependency and resolve it into their own independent codeql-pack.lock.yml. Nothing keeps these two lock files in sync automatically - codeql pack upgrade resolves each pack directory separately, so running it against one and not the other silently lets them drift. The standard-library-versions table previously only checked src as a 'coarse signal', which meant a stale lib lock could go undetected while the table showed all green. Now checks both src and lib for every language (ext/ext-library-sources are still out of scope: they pin via extensionTargets, not dependencies, so CodeQL never resolves a locked version for them). Adds a Pack column to the table to distinguish rows. Also updates CONTRIBUTING.md's Supported CodeQL versions section, which similarly implied a single lock file was the source of truth per language. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs: fix publish.yml job count in CONTRIBUTING.md (five jobs, not four) CONTRIBUTING.md said publish.yml is organized as four jobs, but it also has a fifth 'summary' job (needs: [queries, library, extensions, library_sources_extensions], if: always()) that aggregates results into the publish-summary and CodeQL lib-versions tables and upserts them into the Release notes. Clarify the doc to describe all five. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * fix(publish): don't fail check_version when a pack's GHCR container doesn't exist yet All four publish jobs (queries/library/extensions/library_sources_extensions) read the currently-published version via 'gh api .../versions'. run: steps default to bash -e, so a 404 (package never published, e.g. a brand-new language/pack) aborted the step and failed the job before it could ever publish that pack for the first time. Move '|| true' inside the command substitution so a fetch failure just leaves PUBLISHED_VERSION empty (correctly differing from CURRENT_VERSION, triggering a first publish) instead of killing the step under set -e. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 16a0390 commit dd2357d

7 files changed

Lines changed: 934 additions & 60 deletions
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/env bash
2+
# Builds a markdown section documenting the pinned CodeQL CLI version and
3+
# comparing every direct `codeql/*` dependency declared in each language's
4+
# `src/qlpack.yml` AND `lib/qlpack.yml` (e.g. `codeql/<lang>-all`, and
5+
# `codeql/<lang>-queries` for the languages that also depend on the standard
6+
# queries pack) against the canonical version github/codeql itself bundles
7+
# for that CLI release.
8+
#
9+
# Both `src` and `lib` are checked independently, not just `src`. Each pack
10+
# directory has its own `codeql-pack.lock.yml`, resolved separately by
11+
# `codeql pack upgrade <dir>` - the `codeql/<lang>-all: '*'` dependency each
12+
# of them declares can therefore drift out of sync with each other (e.g. if
13+
# someone runs `codeql pack upgrade <lang>/src` alone to fix a compile error
14+
# after a CLI bump, and forgets `<lang>/lib`). Nothing in this repo enforces
15+
# they stay identical, so both are treated as independent sources of truth
16+
# here rather than assuming one implies the other.
17+
# `ext`/`ext-library-sources` packs are deliberately NOT checked: they pin
18+
# their target via `extensionTargets:`, not `dependencies:`, which CodeQL
19+
# does not resolve into a version-locked `codeql-pack.lock.yml` entry (their
20+
# lock files have an empty `dependencies: {}`), so there is no locked version
21+
# to compare there.
22+
#
23+
# Dependencies are discovered dynamically from each qlpack.yml (not
24+
# hardcoded) so this stays correct if a language adds/drops a direct
25+
# `codeql/*` dependency.
26+
#
27+
# Both the "our locked" and "upstream" version cells link directly to the
28+
# exact file+line that pins that version, at the exact commit (ours) or CLI
29+
# tag (upstream) being compared - so a maintainer can verify the claim with
30+
# one click instead of trusting the table.
31+
#
32+
# This exists because CI's "codeql pack install" step is non-resolving: it
33+
# only installs whatever is already pinned in the checked-in lock file, it
34+
# never re-resolves/upgrades versions. If a maintainer bumps .codeqlversion
35+
# but forgets to run `codeql pack upgrade` for one of a language's pack
36+
# directories, that directory's lock file silently stays on a stale version
37+
# forever, and CI stays green. This table is a machine-checkable tripwire for
38+
# that drift - see CONTRIBUTING.md's "Updating the pinned CodeQL CLI/library
39+
# version" section.
40+
#
41+
# Usage: build-codeql-lib-versions-table.sh
42+
# Requires: gh (authenticated), git, awk, base64
43+
set -euo pipefail
44+
45+
REPO="${GITHUB_REPOSITORY:-GitHubSecurityLab/CodeQL-Community-Packs}"
46+
REPO_SHA="${GITHUB_SHA:-$(git rev-parse HEAD)}"
47+
48+
CODEQL_VERSION=$(tr -d '[:space:]' < .codeqlversion)
49+
if [ -z "$CODEQL_VERSION" ]; then
50+
echo "::error::Could not read .codeqlversion" >&2
51+
exit 1
52+
fi
53+
54+
LANGUAGES=(cpp csharp go java javascript python ruby)
55+
PACKTYPES=(src lib)
56+
57+
lang_label() {
58+
case "$1" in
59+
cpp) echo "C++" ;;
60+
csharp) echo "C#" ;;
61+
go) echo "Go" ;;
62+
java) echo "Java" ;;
63+
javascript) echo "JavaScript" ;;
64+
python) echo "Python" ;;
65+
ruby) echo "Ruby" ;;
66+
*) echo "$1" ;;
67+
esac
68+
}
69+
70+
# Lists the direct `codeql/*` dependencies declared in a qlpack.yml's
71+
# `dependencies:` block (skips our own `githubsecuritylab/*` packages).
72+
qlpack_codeql_deps() {
73+
local qlpackfile="$1"
74+
awk '
75+
{ sub(/\r$/, "") }
76+
/^dependencies:/ { found=1; next }
77+
found && /^[[:space:]]+codeql\// {
78+
line=$0
79+
sub(/^[[:space:]]+/, "", line)
80+
sub(/:.*/, "", line)
81+
print line
82+
next
83+
}
84+
found && /^[^[:space:]]/ { exit }
85+
' "$qlpackfile"
86+
}
87+
88+
# Reads the version pinned to a package in a codeql-pack.lock.yml, plus the
89+
# line numbers of the package key and its version line (for permalinks).
90+
# Prints: "<version> <key_line> <version_line>"
91+
locked_version_and_lines() {
92+
local lockfile="$1" pkg="$2"
93+
awk -v pkg=" ${pkg}:" '
94+
{ sub(/\r$/, "") }
95+
$0 == pkg { found=1; keyline=NR; next }
96+
found && /^[[:space:]]+version:/ {
97+
gsub(/\r/, "")
98+
print $2, keyline, NR
99+
exit
100+
}
101+
found && /^[^[:space:]]/ { exit }
102+
' "$lockfile"
103+
}
104+
105+
# Maps a direct dependency name to the path github/codeql uses for the
106+
# corresponding pack's qlpack.yml. Returns empty if unmapped (unknown dep
107+
# shape) so the caller can surface that instead of guessing.
108+
upstream_path_for_pkg() {
109+
local lang="$1" pkg="$2"
110+
case "$pkg" in
111+
"codeql/${lang}-all") echo "${lang}/ql/lib/qlpack.yml" ;;
112+
"codeql/${lang}-queries") echo "${lang}/ql/src/qlpack.yml" ;;
113+
*) echo "" ;;
114+
esac
115+
}
116+
117+
# Fetches a file from github/codeql at the tag for our pinned CLI version,
118+
# and prints "<version> <version_line>" from its top-level `version:` field.
119+
upstream_version_and_line() {
120+
local path="$1"
121+
local content
122+
content=$(gh api "repos/github/codeql/contents/${path}?ref=refs%2Ftags%2Fcodeql-cli%2Fv${CODEQL_VERSION}" \
123+
--jq '.content' 2>/dev/null | base64 -d 2>/dev/null) || return 1
124+
[ -n "$content" ] || return 1
125+
echo "$content" | awk '/^version:/ { print $2, NR; exit }'
126+
}
127+
128+
MISMATCH=0
129+
130+
echo "## CodeQL standard library & query pack versions"
131+
echo
132+
echo "Pinned CodeQL CLI/library version ([\`.codeqlversion\`](https://github.com/${REPO}/blob/${REPO_SHA}/.codeqlversion)): [\`v${CODEQL_VERSION}\`](https://github.com/github/codeql-cli-binaries/releases/tag/v${CODEQL_VERSION})"
133+
echo
134+
echo "_Comparing each language's direct \`codeql/*\` dependencies (from both \`src/qlpack.yml\` and \`lib/qlpack.yml\`, each resolved in its own \`codeql-pack.lock.yml\`) against the versions [github/codeql](https://github.com/github/codeql) itself ships for CLI \`v${CODEQL_VERSION}\` (tag [\`codeql-cli/v${CODEQL_VERSION}\`](https://github.com/github/codeql/tree/codeql-cli/v${CODEQL_VERSION})). \`src\` and \`lib\` are checked independently because they're separately-resolved lock files that can drift from each other. Each cell links to the exact file/line backing it - our side at the commit this table was generated from, upstream at the CLI tag - so the claim can be verified with one click. A mismatch means \`codeql pack upgrade <lang>/<pack>\` hasn't been run for that specific directory since the last \`.codeqlversion\` bump - see [CONTRIBUTING.md: Updating the pinned CodeQL CLI/library version](https://github.com/${REPO}/blob/${REPO_SHA}/CONTRIBUTING.md#updating-the-pinned-codeql-clilibrary-version)._"
135+
echo
136+
echo "| Language | Pack | Dependency | Our locked version | Upstream (CLI v${CODEQL_VERSION} ships) | Status |"
137+
echo "| --- | --- | --- | --- | --- | --- |"
138+
139+
for lang in "${LANGUAGES[@]}"; do
140+
label=$(lang_label "$lang")
141+
142+
for packtype in "${PACKTYPES[@]}"; do
143+
qlpackfile="${lang}/${packtype}/qlpack.yml"
144+
lockfile="${lang}/${packtype}/codeql-pack.lock.yml"
145+
146+
if [ ! -f "$qlpackfile" ] || [ ! -f "$lockfile" ]; then
147+
echo "| $label | \`$packtype\` | - | ❓ missing qlpack.yml or lock file | - | ❓ |"
148+
MISMATCH=1
149+
continue
150+
fi
151+
152+
deps=$(qlpack_codeql_deps "$qlpackfile")
153+
if [ -z "$deps" ]; then
154+
echo "| $label | \`$packtype\` | - | ❓ no direct \`codeql/*\` dependency found | - | ❓ |"
155+
MISMATCH=1
156+
continue
157+
fi
158+
159+
while IFS= read -r pkg; do
160+
[ -n "$pkg" ] || continue
161+
162+
read -r locked keyline verline <<< "$(locked_version_and_lines "$lockfile" "$pkg")"
163+
if [ -z "${locked:-}" ]; then
164+
echo "| $label | \`$packtype\` | \`$pkg\` | ❓ not found in lock file | - | ❓ |"
165+
MISMATCH=1
166+
continue
167+
fi
168+
locked_link="https://github.com/${REPO}/blob/${REPO_SHA}/${lockfile}#L${keyline}-L${verline}"
169+
locked_cell="[\`$locked\`]($locked_link)"
170+
171+
upath=$(upstream_path_for_pkg "$lang" "$pkg")
172+
if [ -z "$upath" ]; then
173+
echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | ❓ no known upstream mapping | ❓ |"
174+
MISMATCH=1
175+
continue
176+
fi
177+
178+
read -r upstream uverline <<< "$(upstream_version_and_line "$upath" || true)"
179+
if [ -z "${upstream:-}" ]; then
180+
echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | ❓ fetch failed | ❓ |"
181+
MISMATCH=1
182+
continue
183+
fi
184+
upstream_link="https://github.com/github/codeql/blob/codeql-cli/v${CODEQL_VERSION}/${upath}#L${uverline}"
185+
upstream_cell="[\`$upstream\`]($upstream_link)"
186+
187+
if [ "$locked" == "$upstream" ]; then
188+
echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | $upstream_cell | ✅ |"
189+
else
190+
echo "| $label | \`$packtype\` | \`$pkg\` | $locked_cell | $upstream_cell | ⚠️ drift |"
191+
MISMATCH=1
192+
fi
193+
done <<< "$deps"
194+
done
195+
done
196+
197+
if [ "$MISMATCH" -eq 1 ]; then
198+
echo "::warning::One or more CodeQL standard library/query pack versions are out of sync with CLI v${CODEQL_VERSION}. Run 'codeql pack upgrade <lang>/<pack>' for the affected language/pack directory(ies)." >&2
199+
fi
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/usr/bin/env bash
2+
# Builds a markdown table summarizing the package versions produced by a
3+
# publish.yml run, from the per-job JSON result fragments it wrote.
4+
#
5+
# Usage: build-publish-summary.sh <results-dir>
6+
#
7+
# Each fragment in <results-dir> looks like:
8+
# {
9+
# "language": "cpp",
10+
# "type": "src",
11+
# "package": "codeql-cpp-queries",
12+
# "previous_version": "0.2.1",
13+
# "target_version": "0.2.2",
14+
# "status": "published" # published | up-to-date | failed
15+
# }
16+
set -euo pipefail
17+
18+
RESULTS_DIR="${1:?usage: build-publish-summary.sh <results-dir>}"
19+
REPO="${GITHUB_REPOSITORY:-GitHubSecurityLab/CodeQL-Community-Packs}"
20+
RUN_URL="${GITHUB_SERVER_URL:-https://github.com}/${REPO}/actions/runs/${GITHUB_RUN_ID:-}"
21+
22+
shopt -s nullglob
23+
FRAGMENTS=("$RESULTS_DIR"/*.json)
24+
if [ "${#FRAGMENTS[@]}" -eq 0 ]; then
25+
MERGED='[]'
26+
else
27+
MERGED=$(jq -s '.' "${FRAGMENTS[@]}")
28+
fi
29+
30+
LANGUAGES=(cpp csharp go java javascript python ruby)
31+
TYPES=(src lib ext ext-library-sources)
32+
EXT_LANGUAGES=(csharp java)
33+
34+
lang_label() {
35+
case "$1" in
36+
cpp) echo "C++" ;;
37+
csharp) echo "C#" ;;
38+
go) echo "Go" ;;
39+
java) echo "Java" ;;
40+
javascript) echo "JavaScript" ;;
41+
python) echo "Python" ;;
42+
ruby) echo "Ruby" ;;
43+
*) echo "$1" ;;
44+
esac
45+
}
46+
47+
is_ext_language() {
48+
local lang="$1"
49+
for l in "${EXT_LANGUAGES[@]}"; do
50+
[ "$l" == "$lang" ] && return 0
51+
done
52+
return 1
53+
}
54+
55+
cell() {
56+
local lang="$1" type="$2"
57+
local entry
58+
entry=$(echo "$MERGED" | jq -c --arg l "$lang" --arg t "$type" \
59+
'[.[] | select(.language == $l and .type == $t)] | first // empty')
60+
61+
if [ -z "$entry" ] || [ "$entry" == "null" ]; then
62+
# This combo is expected to publish (unlike the "n/a" combos filtered out
63+
# by the caller) but no result fragment was found for it.
64+
echo "❓ no result"
65+
return
66+
fi
67+
68+
local package version status previous
69+
package=$(echo "$entry" | jq -r '.package')
70+
version=$(echo "$entry" | jq -r '.target_version')
71+
status=$(echo "$entry" | jq -r '.status')
72+
previous=$(echo "$entry" | jq -r '.previous_version')
73+
local url="https://github.com/${REPO}/pkgs/container/${package}?tag=${version}"
74+
75+
case "$status" in
76+
published) echo "[${version}](${url}) 🆕" ;;
77+
up-to-date) echo "[${version}](${url})" ;;
78+
failed)
79+
if [ -n "$previous" ] && [ "$previous" != "null" ]; then
80+
echo "⚠️ publish failed (still \`${previous}\`)"
81+
else
82+
echo "⚠️ publish failed (previous version unknown - version-check step itself failed)"
83+
fi
84+
;;
85+
*) echo "❓ unknown" ;;
86+
esac
87+
}
88+
89+
echo "## Publish summary"
90+
echo
91+
echo "_Generated by [publish.yml run #${GITHUB_RUN_NUMBER:-}](${RUN_URL}) — 🆕 marks a package republished by this run._"
92+
echo
93+
echo "| Language | Queries (\`src\`) | Library (\`lib\`) | Extensions (\`ext\`) | Library sources (\`ext-library-sources\`) |"
94+
echo "| --- | --- | --- | --- | --- |"
95+
for lang in "${LANGUAGES[@]}"; do
96+
row="| $(lang_label "$lang") |"
97+
for type in "${TYPES[@]}"; do
98+
if { [ "$type" == "ext" ] || [ "$type" == "ext-library-sources" ]; } && ! is_ext_language "$lang"; then
99+
row="$row n/a |"
100+
continue
101+
fi
102+
row="$row $(cell "$lang" "$type") |"
103+
done
104+
echo "$row"
105+
done
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
# Upserts a generated markdown block (e.g. the publish-summary table from
3+
# build-publish-summary.sh, or the CodeQL library-versions table from
4+
# build-codeql-lib-versions-table.sh) into the GitHub Release matching
5+
# .release.yml's current version, creating that release as a pre-release if
6+
# it doesn't exist yet.
7+
#
8+
# Usage: upsert-release-table.sh <table-markdown-file> [block-name]
9+
#
10+
# block-name defaults to "publish-summary" for backwards compatibility. Each
11+
# distinct block-name gets its own marker-comment pair, so multiple blocks
12+
# (e.g. "publish-summary" and "codeql-lib-versions") can be upserted
13+
# independently into the same release body without clobbering each other.
14+
# Re-runs only replace the matching block and leave the rest of the release
15+
# body (e.g. "What's Changed") alone.
16+
set -euo pipefail
17+
18+
TABLE_FILE="${1:?usage: upsert-release-table.sh <table-markdown-file> [block-name]}"
19+
BLOCK_NAME="${2:-publish-summary}"
20+
START_MARKER="<!-- ${BLOCK_NAME}:start -->"
21+
END_MARKER="<!-- ${BLOCK_NAME}:end -->"
22+
23+
VERSION=$(grep -E '^version:' .release.yml | head -1 | sed -E 's/^version:[[:space:]]*"?([^"[:space:]]*)"?/\1/')
24+
if [ -z "$VERSION" ]; then
25+
echo "::error::Could not read version from .release.yml" >&2
26+
exit 1
27+
fi
28+
TAG="v${VERSION}"
29+
30+
BLOCK_FILE=$(mktemp)
31+
{
32+
echo "$START_MARKER"
33+
cat "$TABLE_FILE"
34+
echo "$END_MARKER"
35+
} > "$BLOCK_FILE"
36+
37+
if gh release view "$TAG" >/dev/null 2>&1; then
38+
echo "Release $TAG exists; refreshing its $BLOCK_NAME block."
39+
CURRENT_BODY=$(mktemp)
40+
gh release view "$TAG" --json body -q .body > "$CURRENT_BODY"
41+
42+
NEW_BODY=$(mktemp)
43+
if grep -qF "$START_MARKER" "$CURRENT_BODY" && grep -qF "$END_MARKER" "$CURRENT_BODY"; then
44+
awk -v start="$START_MARKER" -v end="$END_MARKER" -v blockfile="$BLOCK_FILE" '
45+
$0 == start {
46+
while ((getline line < blockfile) > 0) print line
47+
skip = 1
48+
next
49+
}
50+
$0 == end {
51+
skip = 0
52+
next
53+
}
54+
skip { next }
55+
{ print }
56+
' "$CURRENT_BODY" > "$NEW_BODY"
57+
else
58+
# Older release predating this automation, or a corrupted/partial marker
59+
# pair: append the block once rather than risk truncating the body.
60+
cat "$CURRENT_BODY" > "$NEW_BODY"
61+
echo "" >> "$NEW_BODY"
62+
cat "$BLOCK_FILE" >> "$NEW_BODY"
63+
fi
64+
65+
gh release edit "$TAG" --notes-file "$NEW_BODY"
66+
else
67+
echo "Release $TAG does not exist yet; creating it as a pre-release."
68+
gh release create "$TAG" \
69+
--title "$TAG" \
70+
--notes-file "$BLOCK_FILE" \
71+
--generate-notes \
72+
--prerelease \
73+
--target "${GITHUB_SHA:-main}"
74+
fi

0 commit comments

Comments
 (0)