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 new file mode 100644 index 0000000..017bf1a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,185 @@ +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 + pull-requests: write # For protected branch compatibility + metadata: read + +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: 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 + run: | + git tag -a "v${{ inputs.version }}" -m "Release v${{ inputs.version }}" + git push origin "v${{ inputs.version }}" + + - 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 + + ${{ steps.changelog.outputs.changelog }} + + --- + *Changelog automatically generated from conventional commits using git-cliff* \ 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..9c865d5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,161 @@ +# 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 a hybrid approach: manual release workflow with automated changelog generation. + +### 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 + - **Automatically generate and update CHANGELOG.md using git-cliff** + - Create a git tag + - 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 + +### Why This Approach? + +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 + +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