From 2541a035558de6b26f5e5c0a17e26f53b1654bac Mon Sep 17 00:00:00 2001 From: Yibei Chen Date: Wed, 30 Jul 2025 11:42:11 -0400 Subject: [PATCH 1/2] feat: add simple release workflow and documentation - Add manual release workflow using GitHub Actions - Create CHANGELOG.md to track version history - Add CONTRIBUTING.md with development and release guidelines - Use built-in GITHUB_TOKEN for security - Support semantic versioning with version validation - Automatically update version in pyproject.toml and cookiecutter.json - Generate release notes automatically This replaces the complex auto-based workflow from PR #7 with a simpler, more maintainable approach suitable for cookiecutter templates. --- .github/workflows/release.yml | 159 ++++++++++++++++++++++++++++++++++ CHANGELOG.md | 41 +++++++++ CONTRIBUTING.md | 149 +++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bfba10d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,159 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 1.0.1)' + required: true + type: string + release_type: + description: 'Release type' + required: true + type: choice + options: + - patch + - minor + - major + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install toml + + - name: Validate version format + run: | + if ! echo "${{ inputs.version }}" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Version must be in format X.Y.Z" + exit 1 + fi + + - name: Check if tag already exists + run: | + if git rev-parse "v${{ inputs.version }}" >/dev/null 2>&1; then + echo "Error: Tag v${{ inputs.version }} already exists" + exit 1 + fi + + - name: Wait for tests to complete + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.sha }} + check-name: 'Test template generation' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + + - name: Update version in pyproject.toml + run: | + python -c " +import toml +import sys + +# Read the current pyproject.toml +with open('pyproject.toml', 'r') as f: + data = toml.load(f) + +# Update the version +data['project']['version'] = '${{ inputs.version }}' + +# Write back +with open('pyproject.toml', 'w') as f: + toml.dump(data, f) + " + + - name: Update version in cookiecutter.json + run: | + python -c " +import json + +# Read cookiecutter.json +with open('cookiecutter.json', 'r') as f: + data = json.load(f) + +# Add version field if it doesn't exist +data['_template_version'] = '${{ inputs.version }}' + +# Write back with proper formatting +with open('cookiecutter.json', 'w') as f: + json.dump(data, f, indent=4) + f.write('\n') + " + + - name: Commit version updates + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add pyproject.toml cookiecutter.json + git commit -m "chore: bump version to ${{ inputs.version }}" + git push origin main + + - name: Create and push tag + run: | + git tag -a "v${{ inputs.version }}" -m "Release v${{ inputs.version }}" + git push origin "v${{ inputs.version }}" + + - name: Generate release notes + id: release_notes + run: | + # Get the previous tag + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + + if [ -z "$PREV_TAG" ]; then + echo "No previous tag found, this is the first release" + COMPARE_FROM=$(git rev-list --max-parents=0 HEAD) + else + COMPARE_FROM=$PREV_TAG + fi + + # Generate commit list + echo "commits<> $GITHUB_OUTPUT + git log --pretty=format:"- %s (%h)" $COMPARE_FROM..HEAD >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ inputs.version }} + name: Release v${{ inputs.version }} + draft: false + prerelease: false + generate_release_notes: true + body: | + ## 🎉 Release v${{ inputs.version }} + + **Release type**: ${{ inputs.release_type }} + + ### For Users of This Template + + To update your existing protocol generated from this template: + ```bash + cruft update + ``` + + Or to create a new protocol: + ```bash + cookiecutter gh:ReproNim/reproschema-protocol-cookiecutter + ``` + + ### What's Changed + + Auto-generated release notes are below. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..12e17b7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- GitHub Pages deployment workflow for generated protocols +- Comprehensive testing infrastructure with CI/CD +- Improved error handling in post-generation hooks +- Support for deploying specific versions/commits to GitHub Pages + +### Changed +- Updated all schemas to use stable ReproSchema version 1.0.0 +- Standardized schema filenames to lowercase convention +- Fixed context paths from `/contexts/generic` to `/contexts/reproschema` + +### Fixed +- Schema version inconsistencies +- Hardcoded activity references in protocol template +- Path mismatches in generated schemas + +## [1.0.0] - 2024-06-12 + +### Added +- Initial release of reproschema-protocol-cookiecutter +- Support for generating ReproSchema protocols +- Customizable number of activities (1-5) +- Pre-configured activity types: + - Basic activities with various input types + - Voice recording activities + - Selection activities +- Integration with reproschema-ui +- Cruft support for template updates +- Basic documentation and examples + +[Unreleased]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/releases/tag/v1.0.0 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fe62687 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,149 @@ +# Contributing to reproschema-protocol-cookiecutter + +Thank you for your interest in contributing to the ReproSchema Protocol Cookiecutter! This document provides guidelines and instructions for contributing. + +## Getting Started + +1. Fork the repository +2. Clone your fork: `git clone https://github.com/YOUR-USERNAME/reproschema-protocol-cookiecutter.git` +3. Create a new branch: `git checkout -b feature/your-feature-name` +4. Make your changes +5. Test your changes (see Testing section) +6. Commit your changes using conventional commits +7. Push to your fork and submit a pull request + +## Development Setup + +### Prerequisites + +- Python 3.8+ +- Node.js 20+ (for testing GitHub Pages deployment) +- Git + +### Installing Dependencies + +```bash +pip install -r requirements.txt +``` + +### Pre-commit Hooks + +We use pre-commit hooks to ensure code quality: + +```bash +pre-commit install +``` + +## Testing + +### Running Tests Locally + +```bash +# Test the cookiecutter template generation +python test_cookiecutter.py + +# Or use the micromamba environment +./run_in_env.sh python test_cookiecutter.py +``` + +### Testing Your Changes + +1. Generate a test protocol: + ```bash + cookiecutter . --no-input -o test-output + ``` + +2. Validate the generated schemas: + ```bash + cd test-output/my-reproschema-protocol + reproschema validate my_reproschema_protocol/my_reproschema_protocol_schema + ``` + +## Code Style + +- Python code follows PEP 8 +- Use meaningful variable and function names +- Add docstrings to functions +- Keep functions focused and small + +## Commit Messages + +We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +- `feat:` New features +- `fix:` Bug fixes +- `docs:` Documentation changes +- `chore:` Maintenance tasks +- `test:` Test additions or modifications + +Example: +``` +feat: add support for custom activity types + +- Allow users to define custom activity schemas +- Update documentation with examples +- Add tests for custom activities +``` + +## Pull Request Process + +1. Ensure all tests pass +2. Update documentation if needed +3. Update CHANGELOG.md with your changes under "Unreleased" +4. Fill out the PR template completely +5. Request review from maintainers + +## Release Process + +Releases are managed by maintainers using the GitHub Actions release workflow: + +### Creating a Release + +1. Go to Actions → Release workflow +2. Click "Run workflow" +3. Enter the new version (e.g., 1.0.1) +4. Select release type (patch/minor/major) +5. The workflow will: + - Update version in pyproject.toml and cookiecutter.json + - Create a git tag + - Generate release notes + - Create a GitHub release + +### Version Guidelines + +- **Major** (X.0.0): Breaking changes to template structure or output +- **Minor** (1.X.0): New features, activities, or capabilities +- **Patch** (1.0.X): Bug fixes, documentation updates + +### Post-Release + +After a release, update CHANGELOG.md: +1. Move items from "Unreleased" to the new version section +2. Add comparison link at the bottom + +## Schema Updates + +When updating ReproSchema versions or structures: + +1. Use `update_schema_version.py` to update all schemas consistently +2. Test that all schemas validate with the new version +3. Update documentation to reflect changes + +## Adding New Activities + +To add a new activity type: + +1. Create the activity folder in `{{cookiecutter.protocol_name}}/activities/` +2. Add the activity schema and item schemas +3. Update `hooks/pre_gen_project.py` to include the new activity option +4. Add tests for the new activity +5. Update documentation + +## Questions? + +Feel free to: +- Open an issue for bugs or feature requests +- Start a discussion for questions +- Join the ReproNim community channels + +Thank you for contributing! \ No newline at end of file From 1f963ee43e18c6e5f94200eaf96567e415929055 Mon Sep 17 00:00:00 2001 From: Yibei Chen Date: Tue, 12 Aug 2025 23:15:01 -0400 Subject: [PATCH 2/2] feat: implement hybrid release approach with automated changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add git-cliff configuration (.cliff.toml) for conventional commit parsing - Integrate git-cliff into release workflow for automated changelog generation - Update CONTRIBUTING.md to document hybrid approach and benefits - Fix dual push strategy to prevent race conditions - Add proper workflow permissions for protected branch compatibility - Add error handling for git-cliff operations Addresses Gemini's feedback about automating manual changelog updates while maintaining intentional release control appropriate for cookiecutter templates. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .cliff.toml | 90 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 68 ++++++++++++++++++-------- CONTRIBUTING.md | 24 +++++++--- 3 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 .cliff.toml diff --git a/.cliff.toml b/.cliff.toml new file mode 100644 index 0000000..97b3133 --- /dev/null +++ b/.cliff.toml @@ -0,0 +1,90 @@ +# git-cliff configuration for reproschema-protocol-cookiecutter +# https://git-cliff.org/docs/configuration + +[changelog] +# Changelog header +header = """ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +""" +# Template for the changelog body +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [Unreleased] +{% endif %}\ + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | upper_first | trim }}{% if commit.breaking %} (**BREAKING**){% endif %}\ + {% endfor %} +{% endfor %}\n +""" +# Remove the leading and trailing whitespace from the template +trim = true +# Changelog footer +footer = """ + +{% for release in releases -%} + {% if release.version -%} + {% if release.previous and release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/compare/{{ release.previous.version }}...{{ release.version }} + {% else -%} + [{{ release.version | trim_start_matches(pat="v") }}]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/releases/tag/{{ release.version }} + {% endif -%} + {% else -%} + {% if releases | length > 1 -%} + [Unreleased]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/compare/{{ releases | nth(n=1) | get(key="version") }}...HEAD + {% else -%} + [Unreleased]: https://github.com/ReproNim/reproschema-protocol-cookiecutter/compare/HEAD + {% endif -%} + {% endif -%} +{% endfor %} +""" + +[git] +# Parse the commits based on conventional commits +conventional_commits = true +# Filter out the commits that are not conventional +filter_unconventional = false +# Process each line of a commit as an individual commit +split_commits = false +# Regex for preprocessing the commit messages +commit_preprocessors = [ + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/ReproNim/reproschema-protocol-cookiecutter/pull/${2}))" }, +] +# Regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^doc", group = "Documentation" }, + { message = "^docs", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactored" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\):", skip = true }, + { message = "^chore", group = "Miscellaneous" }, + { message = "^build", group = "Build" }, + { message = "^ci", group = "CI" }, + { body = ".*security", group = "Security" }, +] +# Filter commits by regex +protect_breaking_commits = false +# Glob pattern for matching git tags +tag_pattern = "v[0-9]*" +# Regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# Regex for ignoring tags +ignore_tags = "" +# Sort the tags chronologically +date_order = false +# Sort the commits inside sections by oldest/newest order +sort_commits = "oldest" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfba10d..017bf1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,8 @@ on: permissions: contents: write + pull-requests: write # For protected branch compatibility + metadata: read jobs: release: @@ -97,12 +99,52 @@ with open('cookiecutter.json', 'w') as f: f.write('\n') " - - name: Commit version updates + - name: Install git-cliff + uses: kenji-miyake/setup-git-cliff@v2 + + - name: Generate changelog with git-cliff + id: changelog + run: | + # Get the previous tag + PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [ -z "$PREV_TAG" ]; then + echo "No previous tag found, this is the first release" + # Generate changelog for all commits + git cliff --tag v${{ inputs.version }} --output CHANGELOG_NEW.md || { echo "Error: Failed to generate changelog"; exit 1; } + else + echo "Generating changelog from $PREV_TAG to v${{ inputs.version }}" + # Generate changelog for commits since last tag + git cliff --tag v${{ inputs.version }} --unreleased --output CHANGELOG_NEW.md || { echo "Error: Failed to generate changelog"; exit 1; } + fi + + # Extract just the content for this release (without header/footer) + echo "changelog<> $GITHUB_OUTPUT + cat CHANGELOG_NEW.md >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Generate the complete updated changelog + git cliff --tag v${{ inputs.version }} --output CHANGELOG.md || { echo "Error: Failed to update CHANGELOG.md"; exit 1; } + + - name: Commit version updates and changelog run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + + # Stage version updates git add pyproject.toml cookiecutter.json git commit -m "chore: bump version to ${{ inputs.version }}" + + # Stage and commit changelog (amend if possible, otherwise separate commit) + git add CHANGELOG.md + if git diff --cached --quiet; then + echo "No changelog changes to commit" + else + git commit --amend --no-edit || git commit -m "docs: update CHANGELOG.md for v${{ inputs.version }}" + fi + + # Single push to main git push origin main - name: Create and push tag @@ -110,25 +152,6 @@ with open('cookiecutter.json', 'w') as f: git tag -a "v${{ inputs.version }}" -m "Release v${{ inputs.version }}" git push origin "v${{ inputs.version }}" - - name: Generate release notes - id: release_notes - run: | - # Get the previous tag - PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - - if [ -z "$PREV_TAG" ]; then - echo "No previous tag found, this is the first release" - COMPARE_FROM=$(git rev-list --max-parents=0 HEAD) - else - COMPARE_FROM=$PREV_TAG - fi - - # Generate commit list - echo "commits<> $GITHUB_OUTPUT - git log --pretty=format:"- %s (%h)" $COMPARE_FROM..HEAD >> $GITHUB_OUTPUT - echo "" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - name: Create Release uses: softprops/action-gh-release@v2 with: @@ -156,4 +179,7 @@ with open('cookiecutter.json', 'w') as f: ### What's Changed - Auto-generated release notes are below. \ No newline at end of file + ${{ steps.changelog.outputs.changelog }} + + --- + *Changelog automatically generated from conventional commits using git-cliff* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe62687..9c865d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,7 +95,7 @@ feat: add support for custom activity types ## Release Process -Releases are managed by maintainers using the GitHub Actions release workflow: +Releases are managed by maintainers using a hybrid approach: manual release workflow with automated changelog generation. ### Creating a Release @@ -105,21 +105,33 @@ Releases are managed by maintainers using the GitHub Actions release workflow: 4. Select release type (patch/minor/major) 5. The workflow will: - Update version in pyproject.toml and cookiecutter.json + - **Automatically generate and update CHANGELOG.md using git-cliff** - Create a git tag - - Generate release notes + - Generate comprehensive release notes from conventional commits - Create a GitHub release +### Automated Changelog + +The project uses [git-cliff](https://git-cliff.org/) to automatically generate changelog entries from conventional commits. This eliminates manual changelog maintenance and ensures consistency. + +- Changelog is generated from commit messages following the Conventional Commits specification +- The `.cliff.toml` configuration file defines how commits are grouped and formatted +- CHANGELOG.md is automatically updated during the release process +- No manual post-release changelog updates needed! + ### Version Guidelines - **Major** (X.0.0): Breaking changes to template structure or output - **Minor** (1.X.0): New features, activities, or capabilities - **Patch** (1.0.X): Bug fixes, documentation updates -### Post-Release +### Why This Approach? -After a release, update CHANGELOG.md: -1. Move items from "Unreleased" to the new version section -2. Add comparison link at the bottom +We use a hybrid approach (manual release + automated changelog) because: +- Cookiecutter templates have different release needs than packages +- Manual releases provide intentional version control +- Automated changelog prevents human error and ensures consistency +- Simpler than full release automation tools while addressing key pain points ## Schema Updates