Add changelog validation workflow and update CHANGELOG with current changes
Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com>
This commit is contained in:
194
.github/workflows/changelog-validation.yml
vendored
Normal file
194
.github/workflows/changelog-validation.yml
vendored
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# This file is part of a Moko Consulting project.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Changelog Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize, reopened]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dev/**'
|
||||||
|
- 'rc/**'
|
||||||
|
- 'version/**'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-changelog-entry:
|
||||||
|
name: Validate Changelog Entry
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check for changelog entry in PR description
|
||||||
|
id: check_changelog
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const prBody = context.payload.pull_request.body || '';
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
|
// Skip for automated PRs (e.g., Dependabot)
|
||||||
|
const automatedAuthors = ['dependabot[bot]', 'github-actions[bot]'];
|
||||||
|
if (automatedAuthors.includes(prAuthor)) {
|
||||||
|
console.log('Skipping changelog check for automated PR');
|
||||||
|
core.setOutput('has_entry', 'true');
|
||||||
|
core.setOutput('skip', 'true');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if PR body contains changelog entry section
|
||||||
|
const changelogSection = prBody.match(/## Changelog Entry[\s\S]*?```markdown([\s\S]*?)```/);
|
||||||
|
|
||||||
|
if (!changelogSection) {
|
||||||
|
console.log('No changelog entry section found in PR template');
|
||||||
|
core.setOutput('has_entry', 'false');
|
||||||
|
core.setOutput('skip', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changelogContent = changelogSection[1].trim();
|
||||||
|
|
||||||
|
// Check if changelog entry is not just the template placeholder
|
||||||
|
const isPlaceholder = changelogContent.includes('[Category]') ||
|
||||||
|
changelogContent.includes('Your changelog entry here') ||
|
||||||
|
changelogContent.length < 20;
|
||||||
|
|
||||||
|
if (isPlaceholder) {
|
||||||
|
console.log('Changelog entry appears to be placeholder text');
|
||||||
|
core.setOutput('has_entry', 'false');
|
||||||
|
core.setOutput('skip', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that it contains a category
|
||||||
|
const validCategories = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];
|
||||||
|
const hasValidCategory = validCategories.some(cat => changelogContent.includes(`### ${cat}`));
|
||||||
|
|
||||||
|
if (!hasValidCategory) {
|
||||||
|
console.log('Changelog entry does not contain a valid category');
|
||||||
|
core.setOutput('has_entry', 'false');
|
||||||
|
core.setOutput('skip', 'false');
|
||||||
|
core.setOutput('reason', 'missing_category');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Valid changelog entry found');
|
||||||
|
core.setOutput('has_entry', 'true');
|
||||||
|
core.setOutput('skip', 'false');
|
||||||
|
core.setOutput('entry', changelogContent);
|
||||||
|
|
||||||
|
- name: Comment on PR if changelog entry is missing
|
||||||
|
if: steps.check_changelog.outputs.has_entry == 'false' && steps.check_changelog.outputs.skip == 'false'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const reason = '${{ steps.check_changelog.outputs.reason }}';
|
||||||
|
let message = '## ⚠️ Changelog Entry Required\n\n';
|
||||||
|
|
||||||
|
if (reason === 'missing_category') {
|
||||||
|
message += 'Your PR includes a changelog entry, but it does not contain a valid category.\n\n';
|
||||||
|
} else {
|
||||||
|
message += 'This PR is missing a changelog entry or contains only placeholder text.\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
message += 'Please add a changelog entry in the "Changelog Entry" section of the PR description.\n\n';
|
||||||
|
message += '### Valid Categories\n';
|
||||||
|
message += '- **Added** - New features or files\n';
|
||||||
|
message += '- **Changed** - Modifications to existing functionality\n';
|
||||||
|
message += '- **Deprecated** - Features marked for future removal\n';
|
||||||
|
message += '- **Removed** - Deleted features or files\n';
|
||||||
|
message += '- **Fixed** - Bug fixes\n';
|
||||||
|
message += '- **Security** - Security-related changes\n\n';
|
||||||
|
message += '### Example\n';
|
||||||
|
message += '```markdown\n';
|
||||||
|
message += '### Added\n';
|
||||||
|
message += '- New feature description (#' + context.payload.pull_request.number + ')\n\n';
|
||||||
|
message += '### Changed\n';
|
||||||
|
message += '- Modified behavior description (#' + context.payload.pull_request.number + ')\n';
|
||||||
|
message += '```\n\n';
|
||||||
|
message += 'See [CHANGELOG_PROCESS.md](https://github.com/mokoconsulting-tech/moko-cassiopeia/blob/main/docs/CHANGELOG_PROCESS.md) for detailed guidelines.';
|
||||||
|
|
||||||
|
// Check if we already commented
|
||||||
|
const comments = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number
|
||||||
|
});
|
||||||
|
|
||||||
|
const botComment = comments.data.find(comment =>
|
||||||
|
comment.user.login === 'github-actions[bot]' &&
|
||||||
|
comment.body.includes('Changelog Entry Required')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (botComment) {
|
||||||
|
// Update existing comment
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: botComment.id,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create new comment
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
body: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Add label if changelog is missing
|
||||||
|
if: steps.check_changelog.outputs.has_entry == 'false' && steps.check_changelog.outputs.skip == 'false'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
labels: ['needs-changelog']
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Remove label if changelog is present
|
||||||
|
if: steps.check_changelog.outputs.has_entry == 'true' && steps.check_changelog.outputs.skip == 'false'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
try {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
name: 'needs-changelog'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Label might not exist, that's okay
|
||||||
|
console.log('Label does not exist or already removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set status
|
||||||
|
if: steps.check_changelog.outputs.skip == 'false'
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.check_changelog.outputs.has_entry }}" == "true" ]; then
|
||||||
|
echo "✅ Changelog entry found and validated"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ Changelog entry is missing or invalid"
|
||||||
|
echo "Please add a changelog entry to your PR description"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -14,6 +14,18 @@
|
|||||||
|
|
||||||
# Changelog — Moko-Cassiopeia (VERSION: 03.06.00)
|
# Changelog — Moko-Cassiopeia (VERSION: 03.06.00)
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- PR-based changelog process with comprehensive documentation (#66)
|
||||||
|
- Created CHANGELOG_PROCESS.md guide with detailed workflow
|
||||||
|
- Added changelog entry section to PR template
|
||||||
|
- Integrated changelog guidance into CONTRIBUTING.md and WORKFLOW_GUIDE.md
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated roadmap documentation based on current open pull requests (#66)
|
||||||
|
- Added document generation system as planned feature (#66)
|
||||||
|
- Synchronized roadmap version timeline with active development branches (#66)
|
||||||
|
|
||||||
## [03.06.00] 2026-01-28
|
## [03.06.00] 2026-01-28
|
||||||
### Changed
|
### Changed
|
||||||
- Updated version to 03.06.00 across all files
|
- Updated version to 03.06.00 across all files
|
||||||
|
|||||||
Reference in New Issue
Block a user