diff --git a/.github/codejson/hooks/post_gen_project.py b/.github/codejson/hooks/post_gen_project.py new file mode 100644 index 0000000..211c1a0 --- /dev/null +++ b/.github/codejson/hooks/post_gen_project.py @@ -0,0 +1,132 @@ +import subprocess +import shutil +import json +import os +import shutil + +def get_date_fields(): + # Run git commands and capture as string + output = subprocess.run(['git', 'log', '--date=iso', '--pretty=%cI', '--max-parents=0', '-n', '1'], capture_output=True, text=True) + + # Store string and strip of leading / trailing whitespace + date = output.stdout.strip() + + # Create a dictionary for date information to be pushed to JSON + date_information = {"created": f"{date}", + "lastModified": "{% now 'utc', '%Y-%m-%dT%H:%M:%S%z' %}", + "metadataLastUpdated": "{% now 'utc', '%Y-%m-%dT%H:%M:%S%z' %}"} + + return date_information + +def get_scc_labor_hours(): + if shutil.which('scc') is not None: + try: + #Run scc and load results into a dictionary + #assuming we are in the .git directory of the repo + cmd = ['scc', '..', '--format', 'json2', '--exclude-file'] + + # Currently only supports specific files + files_to_exclude = [ + "checks.yml,auto-changelog.yml,contributors.yml,repoStructure.yml,code.json,checklist.md,checklist.pdf,README.md,CONTIRBUTING.md,LICENSE,MAINTAINERS.md,repolinter.json,SECURITY.md,CODE_OF_CONDUCT.md,CODEOWNERS.md,COMMUNITY_GUIDELINES.md,GOVERANCE.md" + ] + + cmd.extend(files_to_exclude) + + d = json.loads(subprocess.run(cmd,check=True, capture_output=True).stdout) + + l_hours = d['estimatedScheduleMonths'] * 730.001 + + return round(l_hours,2) + + except (subprocess.CalledProcessError, KeyError) as e: + print(e) + return None + else: + print("scc (https://github.com/boyter/scc) not found on system") + + #Otherwise just use previous value as a default value. + return None + +def prompt_exemption_text(exemptionType): + print(f"You have selected {exemptionType} for your Usage Type.") + return input("Please provide a one or two sentence justification for the exemption used: ") + +def format_multi_select_fields(text): + new_text = text.split(",") + + new_text = [text.strip() for text in new_text] + + return new_text + +def update_code_json(json_file_path): + # Read the JSON + with open(json_file_path, 'r') as file: + data = json.load(file) + + # Add date_information and labor hours to the JSON + data['date'] = get_date_fields() + + # Calculate labor hours + hours = get_scc_labor_hours() + if hours: + data['laborHours'] = hours + else: + data['laborHours'] = None + + # Check if usageType is an exemption + if data['permissions']['usageType'].startswith('exempt'): + exemption_text = prompt_exemption_text(data['permissions']['usageType']) + data['permissions']['exemptionText'] = exemption_text + else: + del data['permissions']['exemptionText'] + + # Format multi-select options + data['categories'] = format_multi_select_fields(data['categories'][0]) + data['languages'] = format_multi_select_fields(data['languages'][0]) + data['tags'] = format_multi_select_fields(data['tags'][0]) + + # Update the JSON + with open(json_file_path, 'w') as file: + json.dump(data, file, indent = 2) + +def main(): + try: + # Change to the parent directory + os.chdir('..') + + # Define the codejson directory to remove + dir_name = "codejson" + + # Check if codejson directory exists and remove it + if os.path.exists(dir_name): + shutil.rmtree(dir_name) + + # Get the project name from cookiecutter + sub_project_dir = "{{cookiecutter.project_name}}" + codejson_file = "code.json" + project_root_dir = os.path.abspath('..') + + json_file_path = os.path.join(sub_project_dir, codejson_file) + + if os.path.exists(json_file_path): + # Move code.json file to parent directory + new_json_path = os.path.join(project_root_dir, codejson_file) + shutil.move(json_file_path, new_json_path) + + # Remove the source directory + shutil.rmtree(sub_project_dir) + + # Update the json with date and scc information + update_code_json(new_json_path) + print("Succesfully generated code.json file!") + + else: + print(f"Error: {codejson_file} not found in {sub_project_dir}") + + except OSError as error: + print(f"Error during OS operations: {error}") + except shutil.Error as error: + print(f"Error during shutil operations: {error}") + +if __name__ == "__main__": + main() diff --git a/.github/codejson/{{cookiecutter.project_name}}/code.json b/.github/codejson/{{cookiecutter.project_name}}/code.json new file mode 100644 index 0000000..014804a --- /dev/null +++ b/.github/codejson/{{cookiecutter.project_name}}/code.json @@ -0,0 +1,44 @@ +{ + "name": "{{ cookiecutter.project_name }}", + "description": { + "en": { + "shortDescription": "{{ cookiecutter.short_description }}", + "longDescription": "{{ cookiecutter.long_description }}" + } + }, + "status": "{{ cookiecutter.status }}", + "permissions": { + "licenses": [ + { + "URL": "LICENSE", + "name": "{{ cookiecutter.license }}" + } + ], + "usageType": "{{ cookiecutter.usage_type }}", + "exemptionText": "" + }, + "organization": "Centers for Medicare & Medicaid Services", + "repositoryURL": "https://github.com/{{ cookiecutter.project_org }}/{{ cookiecutter.project_name }}", + "vcs": "{{ cookiecutter.vcs }}", + "laborHours": [""], + "platforms": [ "{{ cookiecutter.platforms }}" ], + "categories": [ "{{ cookiecutter.categories }}" ], + "softwareType": "{{ cookiecutter.software_type }}", + "languages": [ "{{ cookiecutter.languages }}" ], + "maintenance": "{{ cookiecutter.maintenance }}", + "date": [""], + "tags": [ "{{ cookiecutter.tags }}" ], + "contact": { + "email": "{{ cookiecutter.contact_email }}", + "name": "{{ cookiecutter.contact_name }}" + }, + "localisation": "{{ cookiecutter.localisation }}", + "repoType": "{{ cookiecutter.repo_type }}", + "userInput": "{{ cookiecutter.user_input }}", + "fismaLevel": "{{ cookiecutter.fisma_level }}", + "group": "{{ cookiecutter.group }}", + "subsetInHealthcare": "{{ cookiecutter.subset_in_healthcare }}", + "userType": "{{ cookiecutter.user_type }}", + "repositoryHost": "{{ cookiecutter.repository_host }}", + "maturityModelTier": "1" +} \ No newline at end of file diff --git a/.github/repometrics/cookiecutter.json b/.github/repometrics/cookiecutter.json deleted file mode 100644 index 461520e..0000000 --- a/.github/repometrics/cookiecutter.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "project_type" : ["Package", "Website", "Standards", "Libraries", "Data", "Apps", "Tools", "APIs", "Docs"], - "user_input": ["Yes", "No"], - "fisma_level": ["Low", "Moderate", "High"], - "group": "CMS/OA/DSAC", - "subset_in_healthcare": "Policy, Operational", - "user_type": "Providers, Patients, Government", - "repository_host": ["Github.com", "GitHub ENT", "GitHub Cloud", "GitLab.com", "GitLab ENT", "GitLab ENT CCSQ"], - "__prompts__": { - "group": "Which group is the project part of?", - "subset_in_healthcare": "Which subset of healthcare does the project belong to?", - "user_type": "Who are the intended users?", - "user_input": "Does the project accept user input? (e.g. allows user to query a database, allows login by users, upload files, etc.)", - "repository_host": "Where is the repository hosted?" - } -} \ No newline at end of file diff --git a/.github/repometrics/hooks/post_gen_project.sh b/.github/repometrics/hooks/post_gen_project.sh deleted file mode 100644 index f551af9..0000000 --- a/.github/repometrics/hooks/post_gen_project.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Change to the parent directory -cd .. - -# Define the repometrics directory to remove -dir_name="repometrics" - -# Check if repometrics directory exists and remove it -if [ -d "$dir_name" ]; then - rm -rf "$dir_name" -fi - -project_type="{{cookiecutter.project_type}}" -sub_project_dir="${project_type}" -repometrics_file="code.json" -project_root_dir="../" - -if [ -f "${sub_project_dir}/${repometrics_file}" ]; then - # Move code.json file to parent directory - mv "${sub_project_dir}/${repometrics_file}" "${project_root_dir}" - - # Check if the move was successful - if [ $? -eq 0 ]; then - # Remove the source directory - rm -rf "${sub_project_dir}" - - # Check if the deletion was successful - if [ $? -eq 0 ]; then - echo "Successfully generated code.json file." - fi - fi -fi \ No newline at end of file diff --git a/.github/repometrics/{{cookiecutter.project_type}}/code.json b/.github/repometrics/{{cookiecutter.project_type}}/code.json deleted file mode 100644 index 67432dd..0000000 --- a/.github/repometrics/{{cookiecutter.project_type}}/code.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "projectType": "{{ cookiecutter.project_type }}", - "userInput": "{{ cookiecutter.user_input }}", - "fismaLevel": "{{ cookiecutter.fisma_level }}", - "group": "{{ cookiecutter.group }}", - "subsetInHealthcare": "{{ cookiecutter.subset_in_healthcare }}", - "userType": "{{ cookiecutter.user_type }}", - "repositoryHost": "{{ cookiecutter.repository_host }}", - "maturityModelTier": "3" -} \ No newline at end of file diff --git a/.github/workflows/auto-changelog.yml b/.github/workflows/auto-changelog.yml new file mode 100644 index 0000000..90879c0 --- /dev/null +++ b/.github/workflows/auto-changelog.yml @@ -0,0 +1,15 @@ +name: Changelog +on: + release: + types: + - created +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: "Auto Generate changelog" + uses: heinrichreimer/action-github-changelog-generator@v2.3 + with: + {% raw %} + token: ${{ secrets.GITHUB_TOKEN }} + {% endraw %} \ No newline at end of file diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index 3ab38c5..80d45eb 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -1,68 +1,78 @@ +name: Update Contributors Information + on: - push: - branches: - - main - workflow_dispatch: + workflow_dispatch: {} + schedule: + # Weekly on Saturdays. + - cron: "30 1 * * 6" + push: + branches: [main] jobs: - update-contributors: - runs-on: ubuntu-latest - name: Update contributors info in MAINTAINERS.md - permissions: - contents: write - pull-requests: write - steps: - # Update contributor list - - name: Contribute List - uses: akhilmhdh/contributors-readme-action@v2.3.10 - env: - {% raw %} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - {% endraw %} - with: - # https://github.com/marketplace/actions/contribute-list#optional-parameters - readme_path: MAINTAINERS.md - use_username: false - commit_message: "BOT: Update contributors info in MAINTAINERS.md" + update-contributors: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Update contributor list + id: contrib_list + uses: akhilmhdh/contributors-readme-action@v2.3.10 + env: + {% raw %} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + {% endraw %} + with: + readme_path: MAINTAINERS.md + use_username: false + commit_message: "update contributors information" + + - name: Get contributors count + id: get_contributors + env: + {% raw %} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + {% endraw %} + + run: | + OWNER=$(echo $GITHUB_REPOSITORY | cut -d'/' -f1) + REPO=$(echo $GITHUB_REPOSITORY | cut -d'/' -f2) + QUERY='query { repository(owner: \"'"$OWNER"'\", name: \"'"$REPO"'\") { collaborators { totalCount } } }' + + CONTRIBUTORS=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/$OWNER/$REPO/contributors?per_page=100" | \ + jq '[.[] | select(.type != "Bot" and (.login | test("\\[bot\\]$") | not) and (.login | test("-bot$") | not))] | length') + + echo "Total contributors: $CONTRIBUTORS" + echo "contributors=$CONTRIBUTORS" >> $GITHUB_OUTPUT + - # Update contributor count - - name: Checkout repository - uses: actions/checkout@v4 + - name: Update MAINTAINERS.md + run: | + {% raw %} + CONTRIBUTORS="${{ steps.get_contributors.outputs.contributors }}" + {% endraw %} - - name: Pull changes from contributors-readme-action - run: | - git pull + perl -i -pe 's/().*?()/$1 '"$CONTRIBUTORS"' $2/' MAINTAINERS.md - - name: Get repository contributors count - id: get_contributors - # https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-contributors - # https://docs.github.com/en/graphql/reference/objects#repositorycollaboratorconnection - # https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#communicating-with-graphql - # CANNOT have newlines! - run: | - {% raw %} - OWNER=$(echo $GITHUB_REPOSITORY | cut -d'/' -f1) - REPO=$(echo $GITHUB_REPOSITORY | cut -d'/' -f2) - QUERY='query { repository(owner: \"'"$OWNER"'\", name: \"'"$REPO"'\") { collaborators { totalCount } } }' - CONTRIBUTORS=$(curl -s -X POST -H "Authorization: bearer ${{ secrets.GITHUB_TOKEN }}" -H "Content-Type: application/json" -d "{\"query\": \"$QUERY\"}" https://api.github.com/graphql | jq -r '.data.repository.collaborators.totalCount') - echo "Total contributors: $CONTRIBUTORS" - echo "contributors=$CONTRIBUTORS" >> $GITHUB_OUTPUT - {% endraw %} + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git add MAINTAINERS.md + git commit -m "update contributors count to $CONTRIBUTORS" || exit 0 - - name: Replace slug in MAINTAINERS.md with number of contributors - # https://stackoverflow.com/questions/10613643/replace-a-unknown-string-between-two-known-strings-with-sed - run: | - {% raw %} - CONTRIBUTORS=${{ steps.get_contributors.outputs.contributors }} - sed -i 's/.*/ '"$CONTRIBUTORS"' /g' MAINTAINERS.md - {% endraw %} + - name: Push protected + uses: CasperWA/push-protected@v2 + with: + {% raw %} + token: ${{ secrets.PUSH_TO_PROTECTED_BRANCH }} + {% endraw %} - - name: Commit and push changes - # https://github.com/orgs/community/discussions/26560#discussioncomment-3531273 - # commit changes, but if no changes exist, then exit cleanly - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - git add MAINTAINERS.md - git commit -m "BOT: Update contributors info in MAINTAINERS.md" || exit 0 - git push + branch: main \ No newline at end of file diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 0000000..94ae3f0 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,15 @@ +name: Check for Secrets +on: + pull_request: + push: + +jobs: + scan-for-secrets: + name: Run gitleaks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: {fetch-depth: 0} + + - name: Check for GitLeaks + uses: gacts/gitleaks@v1 \ No newline at end of file diff --git a/.github/workflows/repoHygieneCheck.yml b/.github/workflows/repoHygieneCheck.yml new file mode 100644 index 0000000..043e706 --- /dev/null +++ b/.github/workflows/repoHygieneCheck.yml @@ -0,0 +1,80 @@ +name: "Repository Hygiene Check" +on: + push: + branches: + - 'main' + workflow_dispatch: + +jobs: + check-first-run: + name: Check For First Run + runs-on: ubuntu-latest + outputs: + {% raw %} + should_run: ${{ steps.check.outputs.should_run }} + {% endraw %} + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + - id: check + run: | + # If manually triggered, always run + {% raw %} + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + {% endraw %} + echo "should_run=true" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if initialization label exists + has_label=$(gh label list --json name | jq '.[] | select(.name=="repolinter-initialized")') + + if [[ -z "$has_label" ]]; then + # First time - create label and allow run + gh label create repolinter-initialized --description "Marks repo as having run initial repolinter check" + echo "should_run=true" >> $GITHUB_OUTPUT + else + echo "should_run=false" >> $GITHUB_OUTPUT + fi + env: + {% raw %} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + {% endraw %} + + resolve-repolinter-json: + name: Get Repolinter Config + needs: check-first-run + {% raw %} + if: needs.check-first-run.outputs.should_run == 'true' + {% endraw %} + uses: DSACMS/repo-scaffolder/.github/workflows/extendJSONFile.yml@main + with: + url_to_json: 'https://raw.githubusercontent.com/DSACMS/repo-scaffolder/main/tier3/%7B%7Bcookiecutter.project_slug%7D%7D/repolinter.json' + + repolinter-checks: + name: Tier 3 Checks + needs: [check-first-run, resolve-repolinter-json] + {% raw %} + if: needs.check-first-run.outputs.should_run == 'true' + {% endraw %} + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + {% raw %} + RAW_JSON: ${{ needs.resolve-repolinter-json.outputs.raw-json }} + {% endraw %} + steps: + - uses: actions/checkout@v4 + - run: echo $RAW_JSON > repolinter.json + - uses: DSACMS/repolinter-action@main + with: + config_file: 'repolinter.json' + output_type: 'pull-request' + pull_request_labels: 'repolinter-initialized, cms-oss, cms-gov' + {% raw %} + token: ${{ secrets.REPOLINTER_AUTO_TOKEN }} + {% endraw %} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a5c79f..787a9f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,7 +153,9 @@ authorship metadata will be preserved. ## Documentation @@ -174,7 +176,7 @@ questions, just [shoot us an email](mailto:opensource@cms.hhs.gov). ### Security and Responsible Disclosure Policy -*Submit a vulnerability:* Vulnerability reports can be submitted through [Bugcrowd](https://bugcrowd.com/cms-vdp). Reports may be submitted anonymously. If you share contact information, we will acknowledge receipt of your report within 3 business days. +_Submit a vulnerability:_ Vulnerability reports can be submitted through [Bugcrowd](https://bugcrowd.com/cms-vdp). Reports may be submitted anonymously. If you share contact information, we will acknowledge receipt of your report within 3 business days. For more information about our Security, Vulnerability, and Responsible Disclosure Policies, see [SECURITY.md](SECURITY.md). diff --git a/MAINTAINERS.md b/MAINTAINERS.md index db8132d..97ea8fa 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,27 +1,260 @@ # Maintainers + + This is a list of maintainers for this project. See [CODEOWNERS.md](./CODEOWNERS.md) for list of reviewers for different parts of the codebase. Team members include: ## Maintainers: + -- + +- ## Approvers: -- + +- ## Reviewers: -- -| Roles | Responsibilities| Requirements | Defined by| -| -------------|:---------------|:-------------|:-------------| -| member | active contributor in the community | multiple contributions to the project. | PROJECT GitHub org Committer Team| -| reviewer | review contributions from other members | history of review and authorship in a sub-project | MAINTAINERS file reviewer entry, and GitHub Org Triage Team| -| approver | approve accepting contributions | highly experienced and active reviewer + contributor to a sub-project | MAINTAINERS file approver entry and GitHub Triage Team | -| lead | set direction and priorities for a sub-project | demonstrated responsibility and excellent technical judgement for the sub-project | MAINTAINERS file owner entry and GitHub Org Admin Team| +- + +| Roles | Responsibilities | Requirements | Defined by | +| -------- | :--------------------------------------------- | :-------------------------------------------------------------------------------- | :---------------------------------------------------------- | +| member | active contributor in the community | multiple contributions to the project. | PROJECT GitHub org Committer Team | +| reviewer | review contributions from other members | history of review and authorship in a sub-project | MAINTAINERS file reviewer entry, and GitHub Org Triage Team | +| approver | approve accepting contributions | highly experienced and active reviewer + contributor to a sub-project | MAINTAINERS file approver entry and GitHub Triage Team | +| lead | set direction and priorities for a sub-project | demonstrated responsibility and excellent technical judgement for the sub-project | MAINTAINERS file owner entry and GitHub Org Admin Team | ## Contributors + + Total number of contributors: + +# Tier 3 Release Guidelines + +{{ cookiecutter.project_repo_name }} will see regular updates and new releases. This document describes the general guidelines around how and when a new release is cut. + +## Table of Contents + +- [Versioning](#versioning) + + - [Ongoing version support](#ongoing-version-support) +- [Release Process](#release-process) + - [Goals](#goals) + - [Schedule](#schedule) + - [Communication and Workflow](#communication-and-workflow) + +- [Preparing a Release Candidate](#preparing-a-release-candidate) + - [Incorporating feedback from review](#incorporating-feedback-from-review) +- [Making a Release](#making-a-release) +- [Auto Changelog](#auto-changelog) +- [Hotfix Releases](#hotfix-releases) + +## Versioning + +{{ cookiecutter.project_repo_name }} uses [Semantic Versioning](https://semver.org/). Each release is associated with a [`git tag`](github.com/{{ cookiecutter.project_org }}/{{ cookiecutter.project_repo_name }}/tags) of the form `X.Y.Z`. + +Given a version number in the `MAJOR.MINOR.PATCH` (eg., `X.Y.Z`) format, here are the differences in these terms: + +- **MAJOR** version - make breaking/incompatible API changes +- **MINOR** version - add functionality in a backwards compatible manner +- **PATCH** version - make backwards compatible bug fixes + + + + + +### Ongoing version support + + + + + +## Release Process + +The sections below define the release process itself, including timeline, roles, and communication best practices. + +### Goals + + + +. + +### Schedule + + + +### Communication and Workflow + + + + + +## Preparing a Release Candidate + +The following steps outline the process to prepare a Release Candidate of {{ cookiecutter.project_repo_name }}. This process makes public the intention and contents of an upcoming release, while allowing work on the next release to continue as usual in `dev`. + +1. Create a _Release branch_ from the tip of `dev` named `release-x.y.z`, where `x.y.z` is the intended version of the release. This branch will be used to prepare the Release Candidate. For example, to prepare a Release Candidate for `0.5.0`: + + ```bash + git fetch + git checkout origin/dev + git checkout -b release-0.5.0 + git push -u origin release-0.5.0 + ``` + + Changes generated by the steps below should be committed to this branch later. + +2. Create a tag like `x.y.z-rcN` for this Release Candidate. For example, for the first `0.5.0` Release Candidate: + + ```bash + git fetch + git checkout origin/release-0.5.0 + git tag 0.5.0-rc1 + git push --tags + ``` + +3. Publish a [pre-Release in GitHub](proj-releases-new): + + ```md + Tag version: [tag you just pushed] + Target: [release branch] + Release title: [X.Y.Z Release Candidate N] + Description: [copy in ReleaseNotes.md created earlier] + This is a pre-release: Check + ``` + +4. Open a Pull Request to `main` from the release branch (eg. `0.5.0-rc1`). This pull request is where review comments and feedback will be collected. + +5. Conduct Review of the Pull Request that was opened. + +### Incorporating feedback from review + +The review process may result in changes being necessary to the release candidate. + +For example, if the second Release Candidate for `0.5.0` is being prepared, after committing necessary changes, create a tag on the tip of the release branch like `0.5.0-rc2` and make a new [GitHub pre-Release](proj-releases-new) from there: + +```bash +git fetch +git checkout origin/release-0.5.0 +# more commits per OMF review +git tag 0.5.0-rc2 +git push --tags +``` + +Repeat as-needed for subsequent Release Candidates. Note the release branch will be pushed to `dev` at key points in the approval process to ensure the community is working with the latest code. + +## Making a Release + +The following steps describe how to make an approved [Release Candidate](#preparing-a-release-candidate) an official release of {{ cookiecutter.project_repo_name }}: + +1. **Approved**. Ensure review has been completed and approval granted. + +2. **Main**. Merge the Pull Request created during the Release Candidate process to `main` to make the release official. + +3. **Dev**. Open a Pull Request from the release branch to `dev`. Merge this PR to ensure any changes to the Release Candidate during the review process make their way back into `dev`. + +4. **Release**. Publish a [Release in GitHub](proj-releases-new) with the following information + + - Tag version: [X.Y.Z] (note this will create the tag for the `main` branch code when you publish the release) + - Target: main + - Release title: [X.Y.Z] + - Description: copy in Release Notes created earlier + - This is a pre-release: DO NOT check + +5. **Branch**. Finally, keep the release branch and don't delete it. This allows easy access to a browsable spec. + +## Auto Changelog + +It is recommended to use the provided auto changelog github workflow to populate the project’s CHANGELOG.md file: + +```yml +name: Changelog +on: + release: + types: + - created +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: "Auto Generate changelog" + uses: heinrichreimer/action-github-changelog-generator@v2.3 + with: + {% raw %} + token: ${{{{ secrets.GITHUB_TOKEN }}}} + {% endraw %} +``` + +This provided workflow will automatically populate the CHANGELOG.md with all of the associated changes created since the last release that are included in the current release. + +This workflow will be triggered when a new release is created. + +If you do not wish to use automatic changelogs, you can delete the workflow and update the CHANGELOG.md file manually. Although, this is not recommended. + +## Hotfix Releases + +In rare cases, a hotfix for a prior release may be required out-of-phase with the normal release cycle. For example, if a critical bug is discovered in the `0.3.x` line after `0.4.0` has already been released. + +1. Create a _Support branch_ from the tag in `main` at which the hotfix is needed. For example if the bug was discovered in `0.3.2`, create a branch from this tag: + + ```bash + git fetch + git checkout 0.3.2 + git checkout -b 0.3.x + git push -u origin 0.3.x + ``` + +2. Merge (or commit directly) the hotfix work into this branch. + +3. Tag the support branch with the hotfix version. For example if `0.3.2` is the version being hotfixed: + + ```bash + git fetch + git checkout 0.3.x + git tag 0.3.3 + git push --tags + ``` + +4. Create a [GitHub Release](proj-releases-new) from this tag and the support branch. For example if `0.3.3` is the new hotfix version: + + ```md + Tag version: 0.3.3 + Target: 0.3.x + Release title: 0.3.3 + Description: [copy in ReleaseNotes created earlier] + This is a pre-release: DO NOT check + ``` + +[proj-releases-new]: https://github.com/{{ cookiecutter.project_org }}/{{ cookiecutter.project_repo_name }}/releases/new diff --git a/README.md b/README.md index 7af2bfc..704b652 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,7 @@ An up-to-date list of core team members can be found in [MAINTAINERS.md](MAINTAI ## Repository Structure - -```plaintext -. -``` - + **{list directories and descriptions}**