Add CHANGELOG.md management and update CI release workflow#1331
Add CHANGELOG.md management and update CI release workflow#1331rohan-pandeyy wants to merge 2 commits into
Conversation
WalkthroughIntroduces a Release Prep GitHub Actions workflow that bumps versions, drafts CHANGELOG sections from merged PRs, and opens a release PR. Adds a ChangesRelease Automation Pipeline
Sequence Diagram(s)sequenceDiagram
actor Developer
participant release-prep.yml as Release Prep Workflow
participant VersionBump as npm run version:bump
participant GitHubAPI as gh CLI / GitHub API
participant CHANGELOG as CHANGELOG.md
participant MainBranch as main branch
Developer->>release-prep.yml: workflow_dispatch(version=X.Y.Z)
release-prep.yml->>VersionBump: bump manifests to X.Y.Z
release-prep.yml->>GitHubAPI: gh pr list (merged since last tag)
GitHubAPI-->>release-prep.yml: PR list with labels
release-prep.yml->>CHANGELOG: insert ## [X.Y.Z] section after Unreleased
release-prep.yml->>GitHubAPI: git push release/prep-X.Y.Z
release-prep.yml->>GitHubAPI: gh pr create → main
GitHubAPI-->>Developer: PR opened for review
Developer->>MainBranch: merge release prep PR
Developer->>GitHubAPI: create GitHub Release (tag vX.Y.Z)
GitHubAPI->>release-prep.yml: triggers build-and-release.yml
Note over GitHubAPI: validate-changelog checks CHANGELOG.md
GitHubAPI-->>MainBranch: publish-tauri proceeds if entry found
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
.github/workflows/build-and-release.yml (1)
18-19: ⚡ Quick winAdd
persist-credentials: falseto the checkout action for better security posture.Although this job only reads
CHANGELOG.mdand doesn't push changes, it's a security best practice to avoid persisting credentials when not needed. This reduces the surface area for potential token leakage.🛡️ Proposed addition
- name: Checkout code uses: actions/checkout@v4 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/build-and-release.yml around lines 18 - 19, The checkout action in the workflow is currently persisting credentials unnecessarily. Add the `persist-credentials: false` parameter to the `actions/checkout@v4` action to follow security best practices and reduce the risk of credential leakage, since this job only needs to read files and does not push any changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build-and-release.yml:
- Line 25: The grep command in the build-and-release workflow uses the VERSION
variable as a regex pattern, which can cause unintended matches if the version
string contains regex metacharacters. Add the -F flag to the grep command to
force literal string matching instead of regex interpretation. This ensures that
the pattern "## [${VERSION}]" is matched exactly as a literal string rather than
being interpreted as a regex pattern.
In @.github/workflows/release-prep.yml:
- Around line 30-31: The workflow expands the raw user-provided `inputs.version`
directly into shell commands at lines 30, 117-120, and 126-141, which creates a
shell injection vulnerability. Validate the version input early in the workflow
before it is used in any run scripts, then either pass the validated version as
an environment variable to the npm run version:bump command or have the Node
script validate and read the input directly rather than interpolating it raw
into the shell command. This ensures validation happens before any shell
interpretation occurs and prevents malicious shell syntax from being executed.
- Around line 97-109: The Python script that inserts changelog content after the
`## [Unreleased]` marker does not check whether a release section header (such
as `## [X.Y.Z]`) already exists in the changelog, causing duplicate sections on
workflow reruns. Extract the version header from the section variable being
inserted, search the existing content for that same header, and only proceed
with the insertion if the header is not already present. This ensures
idempotency when the release-prep workflow runs multiple times for the same
version.
- Line 20: The GitHub Actions workflow is using mutable version tags (v4) for
both actions/checkout and actions/setup-node, which compromises supply-chain
security since tags can be moved to point to different commits without
visibility. Replace the mutable tags `@v4` with full immutable commit SHAs for
both the actions/checkout action on line 20 and the actions/setup-node action on
line 25. This ensures the workflow always uses the exact intended version
regardless of any tag changes.
---
Nitpick comments:
In @.github/workflows/build-and-release.yml:
- Around line 18-19: The checkout action in the workflow is currently persisting
credentials unnecessarily. Add the `persist-credentials: false` parameter to the
`actions/checkout@v4` action to follow security best practices and reduce the
risk of credential leakage, since this job only needs to read files and does not
push any changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 45bb691a-6399-458c-b141-bedb7490c4af
📒 Files selected for processing (4)
.github/workflows/build-and-release.yml.github/workflows/release-prep.ymlCHANGELOG.mdCONTRIBUTING.md
| run: | | ||
| RAW_TAG="${{ github.event.release.tag_name || github.event.inputs.tag }}" | ||
| VERSION="${RAW_TAG#v}" | ||
| if ! grep -q "## \[${VERSION}\]" CHANGELOG.md; then |
There was a problem hiding this comment.
Use grep -F for literal string matching to prevent regex injection.
The grep pattern on line 25 treats VERSION as a regex pattern. If a release tag contains regex metacharacters (e.g., v1.2.0.*, which Git allows), the pattern could match unintended CHANGELOG entries, bypassing the validation. Use the -F flag to match the version string literally.
🔒 Proposed fix for literal string matching
- if ! grep -q "## \[${VERSION}\]" CHANGELOG.md; then
+ if ! grep -qF "## [${VERSION}]" CHANGELOG.md; then📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ! grep -q "## \[${VERSION}\]" CHANGELOG.md; then | |
| if ! grep -qF "## [${VERSION}]" CHANGELOG.md; then |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/build-and-release.yml at line 25, The grep command in the
build-and-release workflow uses the VERSION variable as a regex pattern, which
can cause unintended matches if the version string contains regex
metacharacters. Add the -F flag to the grep command to force literal string
matching instead of regex interpretation. This ensures that the pattern "##
[${VERSION}]" is matched exactly as a literal string rather than being
interpreted as a regex pattern.
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all mutable action refs in workflows
rg -nP 'uses:\s*[^@]+@v\d+(\.\d+)?(\.\d+)?\b' .github/workflowsRepository: AOSSIE-Org/PictoPy
Length of output: 4338
🏁 Script executed:
cat -n .github/workflows/release-prep.yml | sed -n '15,30p'Repository: AOSSIE-Org/PictoPy
Length of output: 548
Pin GitHub Actions to immutable commit SHAs
Lines 20 and 25 use mutable tags (@v4). This weakens workflow supply-chain integrity because tag movement can change behavior without PR visibility.
Affected lines
20: uses: actions/checkout@v4
25: uses: actions/setup-node@v4
Replace with pinned commit SHAs, for example: actions/checkout@<full-commit-hash>
🧰 Tools
🪛 zizmor (1.25.2)
[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-prep.yml at line 20, The GitHub Actions workflow
is using mutable version tags (v4) for both actions/checkout and
actions/setup-node, which compromises supply-chain security since tags can be
moved to point to different commits without visibility. Replace the mutable tags
`@v4` with full immutable commit SHAs for both the actions/checkout action on line
20 and the actions/setup-node action on line 25. This ensures the workflow
always uses the exact intended version regardless of any tag changes.
Source: Linters/SAST tools
| run: npm run version:bump -- ${{ inputs.version }} | ||
|
|
There was a problem hiding this comment.
Validate version once and stop interpolating raw ${{ inputs.version }} into shell commands.
Line 30 (and downstream lines in git/gh commands) expands user-provided workflow input directly inside run scripts. That enables shell injection before your Node script validation executes.
Suggested fix
+ - name: Validate release version
+ env:
+ RAW_VERSION: ${{ inputs.version }}
+ run: |
+ if [[ ! "$RAW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "Error: version must be X.Y.Z (no v prefix)"
+ exit 1
+ fi
+ echo "VERSION=$RAW_VERSION" >> "$GITHUB_ENV"
+
- name: Bump version across manifests
- run: npm run version:bump -- ${{ inputs.version }}
+ run: npm run version:bump -- "$VERSION"
...
- name: Commit and push prep branch
run: |
...
- git checkout -b release/prep-${{ inputs.version }}
+ git checkout -b "release/prep-$VERSION"
...
- git commit -m "chore: prepare release ${{ inputs.version }}"
- git push origin release/prep-${{ inputs.version }}
+ git commit -m "chore: prepare release $VERSION"
+ git push origin "release/prep-$VERSION"
...
- name: Open pull request
...
run: |
gh pr create \
- --title "chore: release ${{ inputs.version }}" \
- --body "## Release Prep: \`${{ inputs.version }}\`
+ --title "chore: release $VERSION" \
+ --body "## Release Prep: \`$VERSION\`
...
- - Version bumped to \`${{ inputs.version }}\` across all manifest files
- - CHANGELOG.md updated with a draft \`[${{ inputs.version }}]\` entry
+ - Version bumped to \`$VERSION\` across all manifest files
+ - CHANGELOG.md updated with a draft \`[$VERSION]\` entry
...
- After merging, create the GitHub Release with tag \`v${{ inputs.version }}\` to trigger the build pipeline." \
+ After merging, create the GitHub Release with tag \`v$VERSION\` to trigger the build pipeline." \
--base main \
- --head release/prep-${{ inputs.version }} \
+ --head "release/prep-$VERSION" \
--assignee ${{ github.actor }}Also applies to: 117-120, 126-141
🧰 Tools
🪛 zizmor (1.25.2)
[error] 30-30: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-prep.yml around lines 30 - 31, The workflow
expands the raw user-provided `inputs.version` directly into shell commands at
lines 30, 117-120, and 126-141, which creates a shell injection vulnerability.
Validate the version input early in the workflow before it is used in any run
scripts, then either pass the validated version as an environment variable to
the npm run version:bump command or have the Node script validate and read the
input directly rather than interpolating it raw into the shell command. This
ensures validation happens before any shell interpretation occurs and prevents
malicious shell syntax from being executed.
Source: Linters/SAST tools
| python3 << 'PYEOF' | ||
| with open('CHANGELOG.md', 'r') as f: | ||
| content = f.read() | ||
| with open('/tmp/section.md', 'r') as f: | ||
| section = f.read().strip() | ||
| marker = '## [Unreleased]' | ||
| idx = content.find(marker) | ||
| if idx == -1: | ||
| raise SystemExit('Error: [Unreleased] header not found in CHANGELOG.md') | ||
| insert_pos = idx + len(marker) | ||
| new_content = content[:insert_pos] + '\n\n' + section + '\n' + content[insert_pos:] | ||
| with open('CHANGELOG.md', 'w') as f: | ||
| f.write(new_content) |
There was a problem hiding this comment.
Guard against duplicate ## [X.Y.Z] sections on reruns.
The insertion logic always appends after ## [Unreleased]. Re-running the same version creates duplicate headers, which degrades changelog consistency and review clarity.
Suggested fix
- name: Generate CHANGELOG section
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
run: |
...
# Insert section after ## [Unreleased] in CHANGELOG.md
python3 << 'PYEOF'
+ import os
with open('CHANGELOG.md', 'r') as f:
content = f.read()
with open('/tmp/section.md', 'r') as f:
section = f.read().strip()
+ version = os.environ['VERSION']
+ header = f'## [{version}]'
+ if header in content:
+ raise SystemExit(f'Error: {header} already exists in CHANGELOG.md')
marker = '## [Unreleased]'
idx = content.find(marker)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| python3 << 'PYEOF' | |
| with open('CHANGELOG.md', 'r') as f: | |
| content = f.read() | |
| with open('/tmp/section.md', 'r') as f: | |
| section = f.read().strip() | |
| marker = '## [Unreleased]' | |
| idx = content.find(marker) | |
| if idx == -1: | |
| raise SystemExit('Error: [Unreleased] header not found in CHANGELOG.md') | |
| insert_pos = idx + len(marker) | |
| new_content = content[:insert_pos] + '\n\n' + section + '\n' + content[insert_pos:] | |
| with open('CHANGELOG.md', 'w') as f: | |
| f.write(new_content) | |
| python3 << 'PYEOF' | |
| import os | |
| with open('CHANGELOG.md', 'r') as f: | |
| content = f.read() | |
| with open('/tmp/section.md', 'r') as f: | |
| section = f.read().strip() | |
| version = os.environ['VERSION'] | |
| header = f'## [{version}]' | |
| if header in content: | |
| raise SystemExit(f'Error: {header} already exists in CHANGELOG.md') | |
| marker = '## [Unreleased]' | |
| idx = content.find(marker) | |
| if idx == -1: | |
| raise SystemExit('Error: [Unreleased] header not found in CHANGELOG.md') | |
| insert_pos = idx + len(marker) | |
| new_content = content[:insert_pos] + '\n\n' + section + '\n' + content[insert_pos:] | |
| with open('CHANGELOG.md', 'w') as f: | |
| f.write(new_content) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-prep.yml around lines 97 - 109, The Python script
that inserts changelog content after the `## [Unreleased]` marker does not check
whether a release section header (such as `## [X.Y.Z]`) already exists in the
changelog, causing duplicate sections on workflow reruns. Extract the version
header from the section variable being inserted, search the existing content for
that same header, and only proceed with the insertion if the header is not
already present. This ensures idempotency when the release-prep workflow runs
multiple times for the same version.
Closes #1311
Description
PictoPy has no
CHANGELOG.mdand no mechanism to verify a changelog entry exists before a release is published. This PR adds a Keep a Changelog file backfilled from real release history, a CI gate that blocks releases without a matching entry, and a manually-triggered workflow that scaffolds version bumps + changelog drafts into a reviewable PR.This builds on the version bump script introduced in #1306.
What's included
1.
CHANGELOG.mdAdded at the repository root, following Keep a Changelog. Backfilled
[1.1.0]entry derived from actual merged PRs (cross-checked against Release Drafter output, grouped into Added / Changed / Fixed / Documentation, linked back to each PR).2. CI release gate (
build-and-release.yml)validate-changelogjob, runs first on every release triggerCHANGELOG.mdfor a## [X.Y.Z]entry matching the release tag${{ github.event.release.tag_name || github.event.inputs.tag }}— notgithub.ref_name, which resolves to the branch name on manual triggers and would always fail the checkpublish-tauri'sneedsarray updated to includevalidate-changelog3. Release prep workflow (
release-prep.yml)workflow_dispatch), takes aversioninput — no scheduled/automatic cadencenpm run version:bumpscriptrelease/prep-x.y.z) with a checklist for the reviewer — does not touchmaindirectly or publish anything4. Documentation (
CONTRIBUTING.md)Two new subsections under
## Release Management:validate-changelogchecks and why the prep PR must be merged before creating the GitHub ReleaseHow it works
Release prep flow (manual trigger → version bump + changelog draft → PR → review/merge → create release):
Build & release CI gate (changelog validation runs in parallel with platform builds; publish only proceeds if both succeed):
Testing
bump-version.mjsround-trips correctly (bump → diff → revert → empty diff)release-prep.ymlwhere multi-line bash string continuations broke the workflow's block scalar parsingLAST_TAGdetection or PR grouping, since GitHub forks don't copy tags or merged-PR history by default. The workflow falls back gracefully (empty draft) when no tag/PR data is available — confirmed this fallback path doesn't crashvalidate-changeloggrep logic locally against both a matching and non-matching version stringNote
mainor publishes a release[1.1.0]backfill was manually curated against actual merged PRs rather than generated, to avoid inaccurate entriesAI Usage Disclosure:
We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.
Check one of the checkboxes below:
I have used the following AI models and tools: Claude, Gemini
Checklist
Summary by CodeRabbit
New Features
Documentation