From 7652e45a311a41865e1ce831fc984cf8f944ac09 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Wed, 2 Jul 2025 13:13:48 -0700 Subject: [PATCH] Release workflow cleanup and documentation updates --- .github/workflows/check_changelog.yml | 6 +- .github/workflows/publish.yml | 72 ++++++ .github/workflows/tag-and-release.yml | 71 ++++++ .../{build.yml => test-lint-build.yml} | 7 +- .gitignore | 7 + CHANGELOG.md | 67 ++++-- bin/publish.sh | 2 +- docs/release-workflow.md | 214 ++++++++++++++++++ scripts/release/draft-release | 103 +++++++++ scripts/release/tps-check-lock | 66 ++++++ scripts/release/tps-record-release | 84 +++++++ scripts/release/update-changelog | 200 ++++++++++++++++ 12 files changed, 868 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/tag-and-release.yml rename .github/workflows/{build.yml => test-lint-build.yml} (91%) mode change 100755 => 100644 bin/publish.sh create mode 100644 docs/release-workflow.md create mode 100755 scripts/release/draft-release create mode 100755 scripts/release/tps-check-lock create mode 100755 scripts/release/tps-record-release create mode 100755 scripts/release/update-changelog diff --git a/.github/workflows/check_changelog.yml b/.github/workflows/check_changelog.yml index 608553c..1d20f6c 100644 --- a/.github/workflows/check_changelog.yml +++ b/.github/workflows/check_changelog.yml @@ -8,11 +8,7 @@ jobs: check-changelog: runs-on: ubuntu-latest if: | - !contains(github.event.pull_request.body, '[skip changelog]') && - !contains(github.event.pull_request.body, '[changelog skip]') && - !contains(github.event.pull_request.body, '[skip ci]') && - !contains(github.event.pull_request.labels.*.name, 'skip changelog') && - !contains(github.event.pull_request.labels.*.name, 'dependencies') + contains(github.event.pull_request.head.ref, 'release-') steps: - uses: actions/checkout@v4 - name: Check that CHANGELOG is touched diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a4d1708 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,72 @@ +name: Publish to NPM and Change Management + +on: + workflow_run: + workflows: ["Create Github Tag and Release"] + types: + - completed + branches: + - main + +permissions: + contents: write + actions: read + id-token: write + +jobs: + check_for_moratorium: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + environment: change_management + steps: + - uses: actions/checkout@v4 + - env: + TPS_API_TOKEN: ${{ secrets.TPS_API_TOKEN_PARAM }} + run: ./scripts/release/tps-check-lock heroku-applink-nodejs ${{ github.sha }} + + publish: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + needs: check_for_moratorium + runs-on: ubuntu-latest + environment: change_management + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + registry-url: "https://registry.npmjs.org" + + - name: Get the latest tag + id: get_tag + run: | + TAG=$(git describe --tags --abbrev=0) + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Using tag: $TAG" + + - name: Checkout the tagged version + run: | + git checkout ${{ steps.get_tag.outputs.tag }} + + - name: Install dependencies + run: npm ci + + - name: Build the project + run: npm run build + + - name: Publish to npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_AUTOMATION_TOKEN }} + + - name: Publish To Change Management + env: + ACTOR_EMAIL: ${{ secrets.TPS_API_RELEASE_ACTOR_EMAIL }} + TPS_API_TOKEN: ${{ secrets.TPS_API_TOKEN_PARAM }} + # Failure to record the release should not fail the workflow for now. + continue-on-error: true + run: ./scripts/release/tps-record-release heroku-applink-nodejs ${{ github.sha }} diff --git a/.github/workflows/tag-and-release.yml b/.github/workflows/tag-and-release.yml new file mode 100644 index 0000000..e355f51 --- /dev/null +++ b/.github/workflows/tag-and-release.yml @@ -0,0 +1,71 @@ +name: Create Github Tag and Release + +on: + workflow_run: + workflows: ["Test, Lint, and Build"] + types: + - completed + branches: + - main + +permissions: + contents: write + actions: read + id-token: write + +jobs: + push-git-tag: + if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.head_commit.message, 'Merge pull request') && contains(github.event.workflow_run.head_commit.message, 'release-v') }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-java@v4 # java required for testing with wiremock + with: + java-package: jre + java-version: "11" + distribution: "zulu" + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + - run: npm install + - run: npm run build + + - name: Extract version from branch name + id: version + run: | + VERSION=$(echo "${{ github.event.workflow_run.head_commit.message }}" | grep -o 'release-v[0-9]\+\.[0-9]\+\.[0-9]\+' | sed 's/release-v//') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Configure Git + run: | + git config --global user.name "GitHub Actions" + git config --global user.email "github-actions@github.com" + + - name: Create and push Github tag + run: | + echo "Creating tag v${{ steps.version.outputs.version }}" + git tag -s "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" + echo "Pushing tag to origin..." + git push origin "v${{ steps.version.outputs.version }}" + echo "Verifying tag was pushed:" + git ls-remote --tags origin "v${{ steps.version.outputs.version }}" + + - name: Get tag name + id: get_tag + run: | + TAG=$(git describe --tags --abbrev=0) + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Using tag: $TAG" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.get_tag.outputs.tag }} + generate_release_notes: true diff --git a/.github/workflows/build.yml b/.github/workflows/test-lint-build.yml similarity index 91% rename from .github/workflows/build.yml rename to .github/workflows/test-lint-build.yml index 58e51cf..b766f17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/test-lint-build.yml @@ -1,9 +1,8 @@ -name: Build +name: Test, Lint, and Build + on: push: - # Avoid duplicate builds on PRs. - branches: - - main + branches: [main] pull_request: permissions: contents: read diff --git a/.gitignore b/.gitignore index b95ad20..b4d61f1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,10 @@ src/**/*.js src/**/*.js.map **/coverage/ **/.nyc_output + +# Salesforce +.codegenie/ + +# Heroku TPS TEMP +tpsGetLock_response.txt +tpsRecordRelease_response.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c23b31..017d08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,31 +1,56 @@ # Changelog + All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -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). +## [1.0.0-ea.2](https://github.com/heroku/heroku-applink-nodejs/compare/v1.0.0-ea.1...v1.0.0-ea.2) - 2025-06-20 +### Changes -## [0.1.0-ea] - 2024-08-12 +### Features +* Add X-Request-Id header +* Extract User-Agent header from package.json +* Add GitHub pull request template + +### Fixes +* Fix Java heap memory test issue + +### Docs +* Update HttpRequestUtil.request documentation +* Update license year + +### Other +* Rename HTTPResponseError -> HttpResponseError + +## [1.0.0-ea.1](https://github.com/heroku/heroku-applink-nodejs/compare/v1.0.0-ea.0...v1.0.0-ea.1) - 2025-06-06 + +### Changes -- Initial +### Other +* Remove dynamic UA -## [1.0.0-ea] - 2025-06-05 -- Update CODEOWNERS -- Updated `getAuthorization` to use the correct API URL. -- Rename `getConnection(name: string)` -> `getAuthorization(developerName: string, attachmentNameOrColorUrl = "HEROKU_APPLINK")`, accepting a new attachmentNameOrColorOrUrl to use a specific Applink addon's config. -- Remove node-fetch in favor of native fetch, add `HTTPResponseError` +## [1.0.0-ea.0](https://github.com/heroku/heroku-applink-nodejs/compare/v1.0.0-ea...v1.0.0-ea.0) - 2025-06-06 -## [1.0.0-ea.0] - 2025-06-06 -- Add `X-App-UUID` header, `heroku-applink-node-sdk` UA. +### Changes + +### Features +* Add X-App-UUID header, heroku-applink-node-sdk UA + +## [1.0.0-ea](https://github.com/heroku/heroku-applink-nodejs/compare/v0.1.0-ea...v1.0.0-ea) - 2025-06-05 + +### Changes + +### Features +* Updated getAuthorization to use the correct API URL +* Rename getConnection(name: string) -> getAuthorization(developerName: string, attachmentNameOrColorUrl = "HEROKU_APPLINK"), accepting a new attachmentNameOrColorOrUrl to use a specific Applink addon's config +* Remove node-fetch in favor of native fetch, add HTTPResponseError + +### Other +* Update CODEOWNERS + +## [0.1.0-ea] - 2024-08-12 -## [1.0.0-ea.1] - 2025-06-06 -- Remove dynamic UA. +### Changes -## [1.0.0-ea.2] - 2025-06-20 -- Update `HttpRequestUtil.request` documentation -- Update license year -- Add `X-Request-Id` header -- Extract `User-Agent` header from package.json -- Rename `HTTPResponseError` -> `HttpResponseError` -- Add GitHub pull request template -- Fix Java heap memory test issue +### Features +* Initial release diff --git a/bin/publish.sh b/bin/publish.sh old mode 100755 new mode 100644 index 24bf2ed..3ad25a4 --- a/bin/publish.sh +++ b/bin/publish.sh @@ -5,5 +5,5 @@ echo "Running npm publish..." npm publish echo "Creating git tag..." -git tag "v${version}" +git tag -s "v${version}" git push --tags diff --git a/docs/release-workflow.md b/docs/release-workflow.md new file mode 100644 index 0000000..526e392 --- /dev/null +++ b/docs/release-workflow.md @@ -0,0 +1,214 @@ +# Manual and Automated Release Workflows +updated: 06/20/2025 + +This document outlines the process for releasing new versions of the Heroku Salesforce SDK Node.js package. + +## Overview + +The automated release process is automated through GitHub Actions and consists of three main workflows: + +1. Test, Lint, and Build (`test-lint-build.yml`) +2. Create Github Tag and Release (`tag-and-release.yml`) +3. Publish to PyPI and Change Management (`publish.yml`) + +## Automated Release Process + +### 1. Creating a Release Branch + +Run the draft-release script locally: +```bash +# For a minor version bump +./scripts/release/draft-release minor + +# You can also bump from a specific previous version +./scripts/release/draft-release patch 1.2.2 +``` + +This method will: +- Run `npm version` with the provided version type +- `npm version` will update the version in package.json and commit that change. +- Create a release branch named `release-v{version}` +- Update `CHANGELOG.md` with all changes since the last release +- Create a draft pull request + +**Requirements** +- Review all changelog entries +- The commit message for the release branch must include "Merge pull request" and "release-v$VERSION" to trigger the release workflows. + +ex. +``` +Merge pull request release-v$VERSION +``` + +### 2. Testing and Building + +When a pull request is pushed into main from a branch named `release-*`, with commit message `Merge pull request release-*`: + +1. The Test, Lint, and Build workflow runs automatically and: + - Runs tests across Node 18, 20, and 22 + - Runs linting and checks formatting + - Builds the package + +### 3. Creating a Release Tag + +After the release branch pull request is merged to main and the Test, Lint, and Build workflow completes successfully: + +1. The Create Github Tag and Release workflow is triggered, and automatically: + - Extracts the version from the release branch name + - Creates a new tag (e.g., `v$VERSION`) + - Pushes the tag to the repository + - Creates a Github release + +### 4. Publishing the Release + +When the Create Github Tag and Release workflow completes successfully: + +1. The Publish Release workflow is triggered, and automatically: + - Checks for deployment moratorium using TPS and obtains release lock + - Checks out the new tag and builds the package + - Publishes the package to NPM + - Records the release in Change Management + +## Workflow Files + +### test-lint-build.yml +- Triggers on pushes to main and pull requests +- Runs tests and linting +- Builds the package + +### tag-and-release.yml +- Triggers on successful completion of Test, Lint, and Build workflow if it is a release branch and contains the necessary commit message +- Creates and pushes version tags +- Creates a Gihub release + +### publish.yml +- Triggers on successful completion of Create Github Tag and Release +- Handles the actual release process +- Checks for moratorium and obtains release locks +- Publishes to NPM, and Change Management + +## Requirements + +- GitHub Actions permissions for: + - `contents: write` (for creating releases) + - `id-token: write` (for NPM publishing) +- Environment secrets: + - `TPS_API_TOKEN_PARAM` - stored in token store + - `TPS_API_RELEASE_ACTOR_EMAIL` - brittany.jones@salesforce.com +- Release Branch Commit Message + - The commit message for the release branch must include "Merge pull request" and "release-v$VERSION" to trigger the release workflows + ``` + Merge pull request release-v$VERSION + ``` + +## Best Practices + +1. Always use the "Draft Release" script to generate the release branch +2. Review the changelog entries before merging +3. Ensure all tests pass before merging +4. Wait for the complete release process to finish before starting a new release + +### Rolling Back + +If an issue is discovered in a production released version: + +1. **DO NOT** roll back the release in code or revert the tag +2. Create a new release branch with a version bump +3. Fix the issue in the new release branch +4. Follow the standard release process to deploy the fix +5. Document the issue and fix in the changelog + +This "fix forward" approach ensures: +- A clear audit trail of changes +- Proper versioning of fixes +- Consistent release history +- No disruption to users who have already upgraded + +# Manual Release Workflow + +The automated release process is new, and may need some debugging pending more runs in production. In the case you need to release a new version and the release workflow in github actions fails, you can do a manual release. + +## Manual Release Process + +### 1. Creating a Release Branch + +Run the draft-release script locally: +```bash +# For a minor version bump, auto generating the version +./scripts/release/draft-release minor + +# You can also bump from a specific previous version +./scripts/release/draft-release patch 1.2.2 +``` + +This method will: +- Create a new release branch (e.g., `release-v$VERSION`) +- Update version in `package.json` +- Update `CHANGELOG.md` with all changes since the last release +- Create a draft pull request + +**Requirements** +- Review all changelog entries +- The commit message for the release branch must include "Merge pull request" and "release-v$VERSION" to trigger the release workflows. +``` +Merge pull request release-v$VERSION +``` + +### 2. Moratorium Check and Obtain Release Lock +Run script manually to check for moratorium and obtain release lock. + +```bash +# Get the latest SHA from the most recent commit (ensure this commit is the release branch commit to main) +export SHA="$(git rev-parse origin/main)" + +./scripts/release/tps-check-lock heroku-applink-nodejs $SHA +``` + +### 3. Pushing the tag to Github + +Once the release branch as been merged into main, you will want to push the signed tag to github. + +```bash +export VERSION="1.0.0" + +git tag -s v$VERSION -m "Release v$VERSION" +``` + +**Required environment variables** +TPS_API_TOKEN - This can be found in team password manager + +### 4. Build the package + +Check out the latest tag, and then run npm build to build the package. You should see the new version generated in the dist directory. + +```bash +git checkout $(git describe --tags --abbrev=0) + +npm run build +``` + +### 5. Create the Github Release + +```bash +gh release create v$VERSION --generate-notes +``` + +### 6. Publish to NPM +Publish the package to NPM + +```bash +npm publish +``` + +### 7. Publish to Change Management +Run script manually to publish to change management. Ensure you are using '@salesforce' email address. + +```bash +export ACTOR_EMAIL="brittany.jones@salesforce.com" + +./scripts/release/tps-record-release heroku-applink-nodejs $SHA $ACTOR_EMAIL +``` + +**Required environment variables** + +TPS_API_TOKEN - This can be found in team password manager diff --git a/scripts/release/draft-release b/scripts/release/draft-release new file mode 100755 index 0000000..be11e8e --- /dev/null +++ b/scripts/release/draft-release @@ -0,0 +1,103 @@ +#!/bin/bash +set -eu +set -o pipefail + +# draft-release +# Script to create a release branch and PR for a new version +# +# Usage: +# ./release/draft-release +# bump_type: major, minor, or patch +# +# Example: +# ./scripts/release/draft-release minor +# ./scripts/release/draft-release patch + + +# Function to display usage +usage() { + echo "Usage: $0 " + echo " bump_type: major, minor, or patch" + exit 1 +} + +# Function to handle errors +handle_error() { + local line_no=$1 + local error_code=$2 + local error_command=$3 + echo "Error occurred in line $line_no (exit code $error_code): $error_command" + exit 1 +} + +# Set up error handling +trap 'handle_error ${LINENO} $? "$BASH_COMMAND"' ERR + +# Check if required arguments are provided +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then + usage +fi + +BUMP_TYPE=$1 + +# Validate bump type +if [[ ! "$BUMP_TYPE" =~ ^(major|minor|patch)$ ]]; then + echo "Error: bump_type must be major, minor, or patch" + usage +fi + +# Run npm version to update package.json and get the new version +echo "Running npm version $BUMP_TYPE..." +NEW_VERSION=$(npm version "$BUMP_TYPE" --no-git-tag-version) +echo "New version: $NEW_VERSION" + +# Remove the 'v' prefix that npm version adds +NEW_VERSION=${NEW_VERSION#v} + +# Validate semver format +if ! [[ $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$ ]]; then + echo "Error: Invalid semver format: $NEW_VERSION" + echo "Please use a valid numeric semver string: https://semver.org/" + exit 1 +fi + +# Create release branch with actual version number +echo "Creating release branch..." +git checkout -b "release-v${NEW_VERSION}" + +# Commit the version change +git add package.json +git commit -m "chore: bump version to v$NEW_VERSION" + +# Update changelog +echo "Updating changelog..." +./scripts/release/update-changelog "$NEW_VERSION" + +# Create commit for changelog update +git add CHANGELOG.md +git commit -m "docs: update changelog for v$NEW_VERSION" + +echo "Created commits for version bump and changelog update" + +# Push the release branch +git push origin "release-v${NEW_VERSION}" + +# Create draft PR +PR_BODY=$(cat << EOF +This is a draft release PR. Please review the changes: + +- Version bump in package.json +- Changelog updates + +Once approved, this PR can be merged to trigger the release process. +EOF +) + +gh pr create \ + --base main \ + --head "release-v${NEW_VERSION}" \ + --title "Release v$NEW_VERSION" \ + --body "$PR_BODY" \ + --draft + +echo "Created draft PR for release v$NEW_VERSION" diff --git a/scripts/release/tps-check-lock b/scripts/release/tps-check-lock new file mode 100755 index 0000000..d099948 --- /dev/null +++ b/scripts/release/tps-check-lock @@ -0,0 +1,66 @@ +#!/bin/bash +set -eu +set -o pipefail + +# Usage: ./scripts/release/tps-check-lock +# Required env vars: TPS_API_TOKEN, COMPONENT_SLUG, RELEASE_SHA +# +# Alternate Usage: ./scripts/release/tps-check-lock +# Required env vars: TPS_API_TOKEN + +if [ -z "${TPS_HOSTNAME:-}" ]; then + TPS_HOSTNAME="tps.heroku.tools" +fi + +if [ -z "${TPS_API_TOKEN:-}" ]; then + echo "Requires environment variable: TPS_API_TOKEN" >&2 + exit 1 +fi + +# Argument overrides the environment variable +component_slug="${1:-$COMPONENT_SLUG}" +if [ -z "$component_slug" ]; then + echo "Requires first argument or env var COMPONENT_SLUG: Heroku component slug" >&2 + exit 1 +fi + +release_sha="${2:-$RELEASE_SHA}" +if [ -z "$release_sha" ]; then + echo "Requires second argument or env var RELEASE_SHA: SHA of the commit being released" >&2 + exit 1 +fi + +response_status=0 + +tpsGetLock() { + response_status="$(curl --silent \ + -o tpsGetLock_response.txt -w "%{response_code}" \ + -X PUT \ + -H "Accept: */*" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${TPS_API_TOKEN}" \ + -d "{\"lock\": {\"sha\": \"${release_sha}\", \"component_slug\": \"${component_slug}\"}}" \ + https://${TPS_HOSTNAME}/api/ctc)" + + echo Response status $response_status: $(cat tpsGetLock_response.txt) >&2 +} + +echo "Requesting deployment lock from ${TPS_HOSTNAME}…" >&2 +retry_count=0 +set +e +tpsGetLock +until [ "$response_status" == "200" -o "$response_status" == "201" ] +do + ((retry_count++)) + if [ $retry_count -gt 40 ] + then + echo "❌ Could not get deployment lock for \"$component_slug\" after retrying for 10-minutes." >&2 + exit 2 + fi + echo "⏳ Retry in 15-seconds…" >&2 + sleep 15 + tpsGetLock +done +set -e +echo "✅ Lock acquired" >&2 + diff --git a/scripts/release/tps-record-release b/scripts/release/tps-record-release new file mode 100755 index 0000000..6b094d9 --- /dev/null +++ b/scripts/release/tps-record-release @@ -0,0 +1,84 @@ +#!/bin/bash +set -eu +set -o pipefail + +# Usage: ./scripts/release/tps-record-release +# Required env vars: TPS_API_TOKEN, COMPONENT_SLUG, RELEASE_SHA, ACTOR_EMAIL + +# Alternate Usage: ./scripts/release/tps-record-release +# Required env vars: TPS_API_TOKEN, ACTOR_EMAIL + +# Alternate Usage: ./scripts/release/tps-record-release +# Required env vars: TPS_API_TOKEN + +if [ -z "${TPS_HOSTNAME:-}" ]; then + TPS_HOSTNAME="tps.heroku.tools" +fi + +if [ -z "${TPS_API_TOKEN:-}" ]; then + echo "Requires environment variable: TPS_API_TOKEN" >&2 + exit 1 +fi + +# Argument overrides the environment variable +component_slug="${1:-$COMPONENT_SLUG}" +if [ -z "$component_slug" ]; then + echo "Requires first argument or env var COMPONENT_SLUG: Heroku component slug" >&2 + exit 1 +fi + +release_sha="${2:-$RELEASE_SHA}" +if [ -z "$release_sha" ]; then + echo "Requires second argument or env var RELEASE_SHA: SHA of the commit being released" >&2 + exit 1 +fi + +actor_email="${3:-$ACTOR_EMAIL}" +if [ -z "$actor_email" ]; then + echo "Requires third argument or env var ACTOR_EMAIL: email of actor performing the release" >&2 + exit 1 +fi + +# No app_id for releases +# app_id="${4:-$APP_ID}" +# if [ -z "$app_id" ]; then +# echo "Requires fourth argument: UUID of app being released" >&2 +# exit 1 +# fi + +stage="production" +description="Deploy ${release_sha} of ${component_slug} in ${stage}" + +response_status=0 + +tpsRecordRelease() { + response_status="$(curl --silent \ + -o tpsRecordRelease_response.txt -w "%{response_code}" \ + -X POST \ + -H "Accept: */*" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${TPS_API_TOKEN}" \ + -d "{\"component_slug\": \"${component_slug}\", \"release\": {\"sha\": \"${release_sha}\", \"actor_email\": \"${actor_email}\", \"stage\": \"${stage}\", \"description\": \"${description}\"}}" \ + https://${TPS_HOSTNAME}/api/component/${component_slug}/releases)" + + echo Response status $response_status: $(cat tpsRecordRelease_response.txt) >&2 +} + +echo "Recording release with ${TPS_HOSTNAME}…" >&2 +retry_count=0 +set +e +tpsRecordRelease +until [ "$response_status" == "204" ] +do + ((retry_count++)) + if [ $retry_count -gt 120 ] + then + echo "❌ Could not record release for \"$component_slug\" after retrying for 30-minutes." >&2 + exit 2 + fi + echo "⏳ Retry in 15-seconds…" >&2 + sleep 15 + tpsRecordRelease +done +set -e +echo "✅ Release recorded" >&2 diff --git a/scripts/release/update-changelog b/scripts/release/update-changelog new file mode 100755 index 0000000..f08f78d --- /dev/null +++ b/scripts/release/update-changelog @@ -0,0 +1,200 @@ +#!/bin/bash +set -eu +set -o pipefail + +# Script to update CHANGELOG.md with git log entries since last tag +# +# Usage: +# ./scripts/release/update-changelog.sh +# +# This will: +# 1. Update CHANGELOG.md with all commits since the last tag +# 2. Format the changelog with proper headers and commit references + +usage() { + echo "Usage: $0 " + echo " version: The version to add to the changelog (e.g., 1.2.3)" + exit 1 +} + +# Check if version is provided +if [ "$#" -ne 1 ]; then + usage +fi + +NEW_VERSION=$1 + +# Function to categorize commit message +categorize_commit() { + local message=$1 + local hash=$2 + local body=$3 + + # First try conventional commit format + if [[ "$message" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z-]+\))?: ]]; then + local type="${BASH_REMATCH[1]}" + case "$type" in + feat) echo "Features" ;; + fix) echo "Fixes" ;; + docs) echo "Docs" ;; + *) echo "Other" ;; + esac + return + fi + + # If not conventional, analyze message content + message_lower=$(echo "$message" | tr '[:upper:]' '[:lower:]') + + # Features + if [[ "$message_lower" =~ ^(add|create|implement|new|update|upgrade|enhance|improve|support|adds|creates|implements|updates|upgrades|enhances|improves|supports) ]]; then + echo "Features" + return + fi + + # Bug Fixes + if [[ "$message_lower" =~ ^(fix|fixes|fixed|resolve|resolves|resolved|correct|corrects|corrected|bug|issue|error|err|typo) ]]; then + echo "Fixes" + return + fi + + # Documentation + if [[ "$message_lower" =~ ^(doc|docs|document|documentation|readme|comment|comments|note|notes|changelog) ]]; then + echo "Docs" + return + fi + + # Default to other + echo "Other" +} + +# Get the last tag (sorted by creation date) +LAST_TAG=$(git tag --sort=creatordate | tail -n 1 2>/dev/null || echo "") + +echo "Last tag: $LAST_TAG" + +# Get the repository URL +REPO_URL=$(git config --get remote.origin.url | sed 's/\.git$//' | sed 's/git@github.com:/https:\/\/github.com\//') + +echo "Repository URL: $REPO_URL" + +# Create temporary directory for our files +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +# Create temporary files in the temp directory +NEW_CONTENT="$TEMP_DIR/new_content" +FEATURES_TMP="$TEMP_DIR/features" +FIXES_TMP="$TEMP_DIR/fixes" +DOCS_TMP="$TEMP_DIR/docs" +OTHER_TMP="$TEMP_DIR/other" +OLD_CONTENT="$TEMP_DIR/old_content" + +# Save the old changelog if it exists +if [ -f "CHANGELOG.md" ]; then + cp "CHANGELOG.md" "$OLD_CONTENT" +fi + +# Create the new version content +if [ -n "$LAST_TAG" ]; then + VERSION_HEADER="# [$NEW_VERSION]($REPO_URL/compare/$LAST_TAG...$NEW_VERSION) - $(date +%Y-%m-%d)" +else + VERSION_HEADER="# [$NEW_VERSION]($REPO_URL/compare/HEAD...$NEW_VERSION) - $(date +%Y-%m-%d)" +fi + +echo "Version header: $VERSION_HEADER" + +cat > "$NEW_CONTENT" << EOL +$VERSION_HEADER + + +### Changes + +EOL + +# Get all commits since last tag +if [ -n "$LAST_TAG" ]; then + # Use the last tag as reference point + COMMITS=$(git log --pretty=format:"%s|%H" "${LAST_TAG}..HEAD") +else + # If no tags, use all commits + COMMITS=$(git log --pretty=format:"%s|%H") +fi + +echo "Found $(echo "$COMMITS" | wc -l) commits to process" + +# Initialize temporary files +touch "$FEATURES_TMP" "$FIXES_TMP" "$DOCS_TMP" "$OTHER_TMP" + +echo "$COMMITS" | while IFS='|' read -r message hash; do + # Skip merge commits + if [[ ! $message =~ ^Merge ]]; then + # Format the commit message + if [[ "$message" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z-]+\))?: ]]; then + # Remove the type prefix for conventional commits + formatted_message="${message#*:}" + else + formatted_message="$message" + fi + + # Add breaking change notice if present + if [[ "$formatted_message" == *"BREAKING CHANGE"* ]]; then + formatted_message="$formatted_message (BREAKING CHANGE)" + fi + + # Get the category for this commit + category=$(categorize_commit "$message" "$hash" "") + case "$category" in + "Features") tmp_file="$FEATURES_TMP" ;; + "Fixes") tmp_file="$FIXES_TMP" ;; + "Docs") tmp_file="$DOCS_TMP" ;; + *) tmp_file="$OTHER_TMP" ;; + esac + + # Add the commit to the appropriate category file with commit hash + echo "* $formatted_message ([${hash:0:7}]($REPO_URL/commit/$hash))" >> "$tmp_file" + fi +done + +# Combine all categories into the new content +for category in "Features" "Fixes" "Docs" "Other"; do + case "$category" in + "Features") tmp_file="$FEATURES_TMP" ;; + "Fixes") tmp_file="$FIXES_TMP" ;; + "Docs") tmp_file="$DOCS_TMP" ;; + *) tmp_file="$OTHER_TMP" ;; + esac + if [ -s "$tmp_file" ]; then + echo -e "\n### $category\n" >> "$NEW_CONTENT" + cat "$tmp_file" >> "$NEW_CONTENT" + fi +done + +# Create the new changelog +if [ -f "$OLD_CONTENT" ]; then + # Create a new changelog with the updated content + { + # Get the header (title and description) + awk '/^# \[/ {exit} {print}' "$OLD_CONTENT" + # Add the new version section + cat "$NEW_CONTENT" + # Add a blank line before the next version + echo + # Add all existing content after the header + awk '/^# \[/ {p=1} p {print}' "$OLD_CONTENT" + } > "CHANGELOG.md.new" + + # Move the new file to replace the old one + mv "CHANGELOG.md.new" "CHANGELOG.md" +else + # Create a new changelog + { + echo "# Changelog" + echo + echo "All notable changes to this project will be documented in this file." + echo "See [Conventional Commits](https://conventionalcommits.org) for commit guidelines." + echo + cat "$NEW_CONTENT" + } > "CHANGELOG.md" +fi + +echo "Updated CHANGELOG.md"