11name : release-post-comment
22
33on :
4- # Trigger after release workflow completes
4+ # Trigger after the release workflow completes
55 workflow_run :
66 workflows : ["release"]
77 types : [completed]
88
99 # Manual dispatch for debugging
1010 workflow_dispatch :
1111 inputs :
12- discussion_category_id :
13- description : " GitHub Discussions category ID for CI-CD notifications "
12+ discussion_category :
13+ description : " GitHub Discussions category name to post into (case-insensitive) "
1414 required : false
1515 type : string
16- # TODO: Update this with the actual CI-CD category ID for cfxdb repo
17- # You can find this by querying the GitHub GraphQL API or creating a category
18- # and inspecting the browser network tab
19- default : " "
16+ default : " ci-cd"
2017
2118permissions :
22- contents : read # Required for reading artifacts
23- discussions : write # Required for posting to discussions
19+ contents : read # Required for reading releases
20+ discussions : write # Required for posting to discussions
2421
2522jobs :
2623 check-release-exists :
2724 name : Check if release created (Early Exit Pattern)
2825 runs-on : ubuntu-latest
2926 outputs :
3027 should_process : ${{ steps.check.outputs.should_process }}
31- release_exists : ${{ steps.check.outputs.release_exists }}
3228 release_name : ${{ steps.check.outputs.release_name }}
3329 release_url : ${{ steps.check.outputs.release_url }}
34- is_tag_release : ${{ steps.check.outputs.is_tag_release }}
30+ release_type : ${{ steps.check.outputs.release_type }}
3531
3632 steps :
37- - name : Check if GitHub Release exists for this commit
33+ - name : Check if a fresh GitHub Release exists for this commit
3834 id : check
3935 env :
4036 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
4137 run : |
42- echo '--------------------------------------------------- '
43- echo 'Checking if GitHub Release exists (Early Exit Pattern)'
44- echo '--------------------------------------------------- '
38+ echo '───────────────────────────────────────────────── '
39+ echo '🔍 Checking if a fresh GitHub Release exists (Early Exit Pattern)'
40+ echo '───────────────────────────────────────────────── '
4541
4642 COMMIT_SHA="${{ github.event.workflow_run.head_sha || github.sha }}"
4743 echo "Commit SHA: $COMMIT_SHA"
4844
49- # Get all releases (sorted by created date , newest first)
45+ # All releases, newest first
5046 RELEASES_JSON=$(gh api repos/$GITHUB_REPOSITORY/releases --paginate 2>/dev/null || echo "[]")
5147
52- # Find most recent release (created within last hour)
5348 FOUND_RELEASE_NAME=""
5449 FOUND_RELEASE_URL=""
55- IS_TAG_RELEASE="false"
56-
57- # Check for tag-based releases (vX.Y.Z)
58- TAG_RELEASE=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name | startswith(\"v\")) | .tag_name" | head -1)
59- if [ -n "$TAG_RELEASE" ]; then
60- RELEASE_CREATED=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$TAG_RELEASE\") | .created_at")
61- CURRENT_TIME=$(date -u +%s)
62- RELEASE_TIME=$(date -u -d "$RELEASE_CREATED" +%s 2>/dev/null || echo "0")
63- TIME_DIFF=$((CURRENT_TIME - RELEASE_TIME))
64-
65- echo "Found tag release: $TAG_RELEASE"
66- echo "Created: $RELEASE_CREATED"
67- echo "Age: ${TIME_DIFF}s"
68-
69- if [ $TIME_DIFF -lt 3600 ]; then
70- echo "Tag release created within last hour - proceeding"
71- FOUND_RELEASE_NAME="$TAG_RELEASE"
72- FOUND_RELEASE_URL=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$TAG_RELEASE\") | .html_url")
73- IS_TAG_RELEASE="true"
74- else
75- echo "Tag release is too old (${TIME_DIFF}s > 3600s) - skipping"
76- fi
50+ RELEASE_TYPE=""
51+
52+ # Helper: is the named release younger than one hour?
53+ is_fresh() {
54+ local created current rel_time diff
55+ created=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$1\") | .created_at")
56+ current=$(date -u +%s)
57+ rel_time=$(date -u -d "$created" +%s 2>/dev/null || echo "0")
58+ diff=$((current - rel_time))
59+ echo " $1 created $created (age ${diff}s)"
60+ [ "$diff" -lt 3600 ]
61+ }
62+
63+ # Stable release first: tags like vX.Y.Z
64+ STABLE=$(echo "$RELEASES_JSON" | jq -r '.[] | select(.tag_name | startswith("v")) | .tag_name' | head -1)
65+ if [ -n "$STABLE" ] && is_fresh "$STABLE"; then
66+ FOUND_RELEASE_NAME="$STABLE"
67+ RELEASE_TYPE="stable"
7768 fi
7869
79- # Check for development releases ( master-* pattern for cfxdb)
70+ # Otherwise nightly: tags like master-YYYYMMDDHHMM
8071 if [ -z "$FOUND_RELEASE_NAME" ]; then
81- DEV_RELEASE=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name | startswith(\"master-\") or startswith(\"dev-\")) | .tag_name" | head -1)
82- if [ -n "$DEV_RELEASE" ]; then
83- RELEASE_CREATED=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$DEV_RELEASE\") | .created_at")
84- CURRENT_TIME=$(date -u +%s)
85- RELEASE_TIME=$(date -u -d "$RELEASE_CREATED" +%s 2>/dev/null || echo "0")
86- TIME_DIFF=$((CURRENT_TIME - RELEASE_TIME))
87-
88- echo "Found development release: $DEV_RELEASE"
89- echo "Created: $RELEASE_CREATED"
90- echo "Age: ${TIME_DIFF}s"
91-
92- if [ $TIME_DIFF -lt 3600 ]; then
93- echo "Development release created within last hour - proceeding"
94- FOUND_RELEASE_NAME="$DEV_RELEASE"
95- FOUND_RELEASE_URL=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$DEV_RELEASE\") | .html_url")
96- IS_TAG_RELEASE="false"
97- else
98- echo "Development release is too old (${TIME_DIFF}s > 3600s) - skipping"
99- fi
72+ NIGHTLY=$(echo "$RELEASES_JSON" | jq -r '.[] | select(.tag_name | startswith("master-")) | .tag_name' | head -1)
73+ if [ -n "$NIGHTLY" ] && is_fresh "$NIGHTLY"; then
74+ FOUND_RELEASE_NAME="$NIGHTLY"
75+ RELEASE_TYPE="nightly"
10076 fi
10177 fi
10278
10379 if [ -z "$FOUND_RELEASE_NAME" ]; then
104- echo "GitHub Release not found for commit $COMMIT_SHA"
105- echo " This is normal! Will post once release workflow creates the release."
106- echo " (This is an early exit - release.yml probably hasn't created the release yet)"
80+ echo "⏳ No fresh GitHub Release found - nothing to announce (early exit)."
10781 {
10882 echo "should_process=false"
109- echo "release_exists=false"
11083 echo "release_name="
11184 echo "release_url="
112- echo "is_tag_release=false "
85+ echo "release_type= "
11386 } >> $GITHUB_OUTPUT
11487 else
115- echo "GitHub Release found: $FOUND_RELEASE_NAME"
116- echo " Release URL: $FOUND_RELEASE_URL"
88+ FOUND_RELEASE_URL=$(echo "$RELEASES_JSON" | jq -r ".[] | select(.tag_name == \"$FOUND_RELEASE_NAME\") | .html_url")
89+ echo "✅ Release found: $FOUND_RELEASE_NAME ($RELEASE_TYPE)"
90+ echo " URL: $FOUND_RELEASE_URL"
11791 {
11892 echo "should_process=true"
119- echo "release_exists=true"
12093 echo "release_name=$FOUND_RELEASE_NAME"
12194 echo "release_url=$FOUND_RELEASE_URL"
122- echo "is_tag_release=$IS_TAG_RELEASE "
95+ echo "release_type=$RELEASE_TYPE "
12396 } >> $GITHUB_OUTPUT
12497 fi
12598
126- echo '--------------------------------------------------- '
99+ echo '───────────────────────────────────────────────── '
127100
128101 post-discussion :
129- name : Post to GitHub Discussions
102+ name : Post to GitHub Discussions (Nightly & Stable)
130103 needs : [check-release-exists]
131104 runs-on : ubuntu-latest
132105
133- # Only post for tag-based releases (not development builds)
134106 if : |
135107 needs.check-release-exists.outputs.should_process == 'true' &&
136- needs.check-release-exists.outputs.is_tag_release == 'true'
108+ (needs.check-release-exists.outputs.release_type == 'nightly' ||
109+ needs.check-release-exists.outputs.release_type == 'stable')
137110
138111 env :
139112 RELEASE_NAME : ${{ needs.check-release-exists.outputs.release_name }}
@@ -146,108 +119,77 @@ jobs:
146119 submodules : recursive
147120
148121 - name : Get release notes from GitHub Release
149- id : release-details
150122 env :
151123 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
152124 run : |
153125 echo "==> Fetching release notes for: $RELEASE_NAME"
154- echo " Release URL: $RELEASE_URL"
155-
156- # Get release body via GitHub API
157126 RELEASE_JSON=$(gh api repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_NAME 2>/dev/null || echo "{}")
158-
159127 if [ "$RELEASE_JSON" = "{}" ]; then
160- echo "Release not found: $RELEASE_NAME"
161- echo "This should not happen - check-release-exists should have prevented this!"
128+ echo "⚠️ Release not found: $RELEASE_NAME (should not happen after the check job)"
162129 exit 1
163130 fi
164-
165- # Save release body (release notes)
166131 echo "$RELEASE_JSON" | jq -r '.body' > release-notes.md
167- echo "Release notes retrieved"
132+ echo "✅ Release notes retrieved"
168133
169134 - name : Install jinja2-cli for template rendering
170- run : |
171- pip install jinja2-cli
135+ run : pip install jinja2-cli
172136
173137 - name : Render discussion post from Jinja2 template
174- id : render
175138 run : |
176- echo "==> Preparing GitHub Discussion post..."
177- echo "Release name: $RELEASE_NAME"
178-
179- # Read release notes (already fetched)
139+ echo "==> Rendering GitHub Discussion post for $RELEASE_NAME"
180140 RELEASE_NOTES=$(cat release-notes.md)
181-
182- # Render template using jinja2
183141 jinja2 .github/templates/discussion-post.md.j2 \
184142 -D release_name="$RELEASE_NAME" \
185143 -D release_url="$RELEASE_URL" \
186144 -D release_notes="$RELEASE_NOTES" \
187145 -o discussion-post.md
188-
189- echo ""
190146 echo "==> Generated discussion post:"
191147 cat discussion-post.md
192148
193149 - name : Post to GitHub Discussions
194150 uses : actions/github-script@v7
195151 env :
196- # TODO: Set the actual Discussion category ID for cfxdb CI-CD category
197- # This needs to be configured after the category is created in the repo
198- DISCUSSION_CATEGORY_ID : ${{ github.event.inputs.discussion_category_id || '' }}
152+ DISCUSSION_CATEGORY : ${{ github.event.inputs.discussion_category || 'ci-cd' }}
199153 with :
200154 script : |
201155 const fs = require('fs');
202- const discussionBody = fs.readFileSync('discussion-post.md', 'utf8');
203-
204- const releaseName = '${{ env.RELEASE_NAME }}';
156+ const body = fs.readFileSync('discussion-post.md', 'utf8');
157+ const releaseName = process.env.RELEASE_NAME;
205158 const title = `Release ${releaseName}`;
206- const categoryId = process.env.DISCUSSION_CATEGORY_ID;
207-
208- if (!categoryId) {
209- console.log('No Discussion category ID configured - skipping discussion post');
210- console.log('To enable discussion posts:');
211- console.log('1. Create a CI-CD category in GitHub Discussions');
212- console.log('2. Get the category ID from the GraphQL API');
213- console.log('3. Set it in the workflow file');
159+ const wantCategory = (process.env.DISCUSSION_CATEGORY || 'ci-cd').toLowerCase();
160+
161+ // Resolve the repository node id and the target discussion category id
162+ // by (case-insensitive) name, so we never hard-code a repo-specific
163+ // opaque category id and are immune to category-name casing mistakes.
164+ const repoInfo = await github.graphql(`
165+ query($owner: String!, $repo: String!) {
166+ repository(owner: $owner, name: $repo) {
167+ id
168+ discussionCategories(first: 50) { nodes { id name } }
169+ }
170+ }
171+ `, { owner: context.repo.owner, repo: context.repo.repo });
172+
173+ const categories = repoInfo.repository.discussionCategories.nodes;
174+ const category = categories.find(c => c.name.toLowerCase() === wantCategory);
175+ if (!category) {
176+ const names = categories.map(c => c.name).join(', ');
177+ core.setFailed(`Discussion category '${wantCategory}' not found. Available: ${names}`);
214178 return;
215179 }
216180
217- console.log(`Creating discussion: ${title}`);
218- console.log(`Category ID: ${categoryId}`);
219-
220- // Create discussion using GraphQL API
221- const mutation = `
181+ console.log(`Creating discussion '${title}' in category '${category.name}'`);
182+ const result = await github.graphql(`
222183 mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
223184 createDiscussion(input: {
224185 repositoryId: $repositoryId
225186 categoryId: $categoryId
226187 title: $title
227188 body: $body
228- }) {
229- discussion {
230- url
231- }
232- }
189+ }) { discussion { url } }
233190 }
234- `;
235-
236- // Get repository ID
237- const { data: repo } = await github.rest.repos.get({
238- owner: context.repo.owner,
239- repo: context.repo.repo
240- });
241- const repositoryId = repo.node_id;
242-
243- // Create the discussion
244- const result = await github.graphql(mutation, {
245- repositoryId: repositoryId,
246- categoryId: categoryId,
247- title: title,
248- body: discussionBody
249- });
250-
251- const discussionUrl = result.createDiscussion.discussion.url;
252- console.log(`Discussion created: ${discussionUrl}`);
253- core.setOutput('discussion_url', discussionUrl);
191+ `, { repositoryId: repoInfo.repository.id, categoryId: category.id, title, body });
192+
193+ const url = result.createDiscussion.discussion.url;
194+ console.log(`✅ Discussion created: ${url}`);
195+ core.setOutput('discussion_url', url);
0 commit comments