From 1ca593fdb32df921f877146622c93a83062a6b65 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:13:01 -0700 Subject: [PATCH 1/9] initial release automation setup --- .github/workflows/build.yml | 35 ----- .github/workflows/create-tag.yml | 33 +++++ .github/workflows/draft-release.yml | 37 ++++++ .github/workflows/package.yml | 43 +++++++ .github/workflows/publish.yml | 66 ++++++++++ bin/bump-version.js | 44 ------- bin/publish.sh | 9 -- docs/release-workflow.md | 93 ++++++++++++++ package.json | 4 +- scripts/release/draft-release | 78 ++++++++++++ scripts/release/tps-check-lock | 66 ++++++++++ scripts/release/tps-record-release | 85 +++++++++++++ scripts/release/update-changelog | 190 ++++++++++++++++++++++++++++ 13 files changed, 692 insertions(+), 91 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/create-tag.yml create mode 100644 .github/workflows/draft-release.yml create mode 100644 .github/workflows/package.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 bin/bump-version.js delete mode 100755 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/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 01ee23f..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Build -on: - push: - # Avoid duplicate builds on PRs. - branches: - - main - pull_request: -permissions: - contents: read -jobs: - build: - strategy: - fail-fast: false - matrix: - version: [22, 20, 18] - os: - [sfdc-hk-ubuntu-latest, sfdc-hk-macos-latest, sfdc-hk-windows-latest] - name: Node ${{ matrix.version }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - 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: ${{ matrix.version }} - cache: "npm" - - run: npm install - - run: npm run format:check - - run: npm run lint - - run: npm run test - - run: npm run build diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml new file mode 100644 index 0000000..c21beb0 --- /dev/null +++ b/.github/workflows/create-tag.yml @@ -0,0 +1,33 @@ +name: Create Tag + +on: + workflow_run: + workflows: ["Package Library"] + types: + - completed + branches: + - main + +jobs: + push-git-tag: + if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.head_branch, 'release') }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from branch name + id: version + run: | + BRANCH_NAME="${{ github.event.workflow_run.head_branch }}" + VERSION=${BRANCH_NAME#release-v} + VERSION=${VERSION#release/} + VERSION=${VERSION#release-} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create tag + run: | + git tag "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" + git push origin "v${{ steps.version.outputs.version }}" diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml new file mode 100644 index 0000000..ab268e7 --- /dev/null +++ b/.github/workflows/draft-release.yml @@ -0,0 +1,37 @@ +name: Draft Release Branch + +on: + workflow_dispatch: + inputs: + bump_type: + description: 'Type of version bump (major, minor, patch)' + required: true + type: choice + options: + - major + - minor + - patch + +jobs: + draft-release-branch: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Make scripts executable + run: | + chmod +x scripts/release/draft-release + chmod +x scripts/release/update-changelog + + - name: Run release script + run: ./scripts/release/draft-release "${{ github.event.inputs.bump_type }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..b52e212 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,43 @@ +name: Package Library + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x, 20.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Test + run: npm run test + + - name: Build + run: npm run build + + - name: Upload artifacts + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'release') + uses: actions/upload-artifact@v4 + with: + name: ${{ github.ref_name }} + path: dist/ + retention-days: 90 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..99fad9e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,66 @@ +name: Publish Release + +on: + push: + tags: + - 'v*' + +jobs: + check_for_moratorium: + 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: + runs-on: ubuntu-latest + environment: change_management + permissions: + contents: write + 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: Download artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ github.ref_name }} + path: . + + - name: Install dependencies + run: npm ci + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + generate_release_notes: true + files: | + dist/**/* + package.json + README.md + CHANGELOG.md + LICENSE.txt + SECURITY.md + TERMS_OF_USE.md + + - 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 }} + run: ./scripts/release/tps-record-release heroku-applink-nodejs ${{ github.sha }} \ No newline at end of file diff --git a/bin/bump-version.js b/bin/bump-version.js deleted file mode 100644 index c31247a..0000000 --- a/bin/bump-version.js +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -import { readFile, readFileSync, writeFileSync } from "fs"; -import { exec } from "child_process"; -const version = process.argv[2]; - -const semver = new RegExp( - /^((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)?)$/ -); - -if (!version.match(semver)) { - console.error( - "Please use a valid numeric semver string:", - "https://semver.org/" - ); - process.exit(1); -} - -// bump package.json -console.log("Bumping version on package.json..."); - -readFile("./package.json", (err, jsonString) => { - const jsonObj = JSON.parse(jsonString); - jsonObj["version"] = version; - jsonString = JSON.stringify(jsonObj, undefined, 2) + "\n"; - writeFileSync("./package.json", jsonString); -}); - -// bump CHANGELOG -console.log("Bumping version in CHANGELOG.md..."); - -const changelog = readFileSync("./CHANGELOG.md") - .toString() - .split("## [Unreleased]"); -const today = new Date(); - -// JS doesn't support custom date formatting strings such as 'YYYY-MM-DD', so the best we can -// do is extract the date from the ISO 8601 string (which is YYYY-MM-DDTHH:mm:ss.sssZ). -// As an added bonus this uses UTC, ensuring consistency regardless of which machine runs this script. -const date = today.toISOString().split("T")[0]; -changelog.splice(1, 0, `## [Unreleased]\n\n## [${version}] - ${date}`); -writeFileSync("./CHANGELOG.md", changelog.join("")); - -console.log("Completed version bumping."); diff --git a/bin/publish.sh b/bin/publish.sh deleted file mode 100755 index 24bf2ed..0000000 --- a/bin/publish.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "Running npm publish..." -npm publish - -echo "Creating git tag..." -git tag "v${version}" -git push --tags diff --git a/docs/release-workflow.md b/docs/release-workflow.md new file mode 100644 index 0000000..5f09c7e --- /dev/null +++ b/docs/release-workflow.md @@ -0,0 +1,93 @@ +# Release Workflow + +This document outlines the process for releasing new versions of the Heroku Salesforce SDK Node.js package. + +## Release Process + +### 1. Creating a Release Branch + +There are two ways to start a release: + +#### Option 1: GitHub Actions Workflow +1. Go to the "Actions" tab in GitHub +2. Select "Draft Release Branch" workflow +3. Click "Run workflow" +4. Select the type of version bump (major, minor, patch) + +#### Option 2: Command Line Script +Run the draft-release script locally: +```bash +./scripts/release/draft-release [ | major | minor | patch] +``` + +This will: +1. Run `npm version` with the provided version type +* `npm version` will update the version in package.json and commit that change. +2. Create a release branch named `release-v{version}` +3. Update `CHANGELOG.md` with all commits since the last tag +4. Create a draft PR from the release branch to main + +### 2. Testing and Building + +When a PR is created or updated, the following GitHub Actions workflows run automatically: + +1. `package.yml`: + - Runs on pushes to main and PRs + - Lints the code + - Runs tests + - Builds the package + - Uploads build artifacts for release branches + +### 3. Creating a Release Tag + +After the PR is merged to main and tests pass: +1. The `create-tag.yml` workflow runs automatically +2. Creates a git tag with the version number +3. Pushes the tag to the repository + +### 4. Publishing the Release + +When a new tag is pushed: +1. The `publish.yml` workflow runs automatically +2. Publishes the package to npm +3. Creates a GitHub release with the changelog + +## Workflow Files + +- `.github/workflows/package.yml`: Runs tests and builds on PRs and pushes +- `.github/workflows/create-tag.yml`: Creates git tags after successful merges +- `.github/workflows/publish.yml`: Publishes to npm and creates GitHub releases + +## Requirements + +- GitHub Actions permissions for: + - Contents (read/write) + - Packages (read/write) +- Environment secrets: + - `NPM_TOKEN`: For publishing to npm + +## Best Practices + +1. **Version Naming**: + - Use semantic versioning (MAJOR.MINOR.PATCH) + - Major: Breaking changes + - Minor: New features, no breaking changes + - Patch: Bug fixes, no breaking changes + +2. **Changelog**: + - Review the generated changelog in the PR + - Ensure all significant changes are documented + - Follow conventional commit format for better changelog generation + +3. **Testing**: + - Ensure all tests pass before merging + - Test the package locally before releasing + +## Rolling Back + +If issues are found in a production release: +1. Create a new patch release to fix the issue +2. Follow the standard release process +3. Document the fix in the changelog + +Note: We follow a "fix forward" approach rather than reverting releases to maintain a clear audit trail and versioning history. \ No newline at end of file diff --git a/package.json b/package.json index ab5e062..3099a8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@heroku/salesforce-sdk-nodejs", - "version": "0.3.4-ea", + "version": "0.3.5", "description": "Salesforce SDK for Heroku Apps.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -15,8 +15,6 @@ "format:check": "prettier --check .", "format:write": "prettier --write .", "prepack": "tsc -b --clean && tsc -b --force", - "bump": "node bin/bump-version.js", - "release": "bin/publish.sh", "docs": "typedoc" }, "files": [ diff --git a/scripts/release/draft-release b/scripts/release/draft-release new file mode 100755 index 0000000..3232e20 --- /dev/null +++ b/scripts/release/draft-release @@ -0,0 +1,78 @@ +#!/bin/bash +set -eu +set -o pipefail + +# Script to create a draft release branch and PR +# +# Usage: +# ./scripts/release/draft-release [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] +# +# This will: +# 1. Run npm version with the provided version type +# 2. Create a release branch named release-v{version} +# 3. Update CHANGELOG.md using update-changelog.sh +# 4. Create a draft PR from the release branch to main + +usage() { + echo "Usage: $0 [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]" + echo " newversion: The new version to release (e.g., 1.2.3)" + echo " major: Bump major version (1.0.0 -> 2.0.0)" + echo " minor: Bump minor version (1.0.0 -> 1.1.0)" + echo " patch: Bump patch version (1.0.0 -> 1.0.1)" + echo " premajor: Bump major version with prerelease (1.0.0 -> 2.0.0-0)" + echo " preminor: Bump minor version with prerelease (1.0.0 -> 1.1.0-0)" + echo " prepatch: Bump patch version with prerelease (1.0.0 -> 1.0.1-0)" + echo " prerelease: Bump prerelease version (1.0.0 -> 1.0.0-0)" + echo " from-git: Use version from git tags" + exit 1 +} + +# Check if version type is provided +if [ "$#" -ne 1 ]; then + usage +fi + +VERSION_TYPE=$1 + +# Run npm version and capture the new version +echo "Running npm version $VERSION_TYPE..." +NEW_VERSION=$(npm version "$VERSION_TYPE" --no-git-tag-version) +# Remove the 'v' prefix that npm version adds +NEW_VERSION=${NEW_VERSION#v} + +# Create release branch with actual version number +echo "Creating release branch..." +git checkout -b "release-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" \ No newline at end of file diff --git a/scripts/release/tps-check-lock b/scripts/release/tps-check-lock new file mode 100755 index 0000000..2f0b2bb --- /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..bf25223 --- /dev/null +++ b/scripts/release/tps-record-release @@ -0,0 +1,85 @@ +#!/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..7d2325b --- /dev/null +++ b/scripts/release/update-changelog @@ -0,0 +1,190 @@ +#!/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 +} + +# Function to categorize commits based on conventional commit types +categorize_commit() { + local message=$1 + local hash=$2 + local body=$3 + + # Check for conventional commit types + if [[ "$message" =~ ^feat(\([a-z-]+\))?: ]]; then + echo "Features" + elif [[ "$message" =~ ^fix(\([a-z-]+\))?: ]]; then + echo "Fixes" + elif [[ "$message" =~ ^docs(\([a-z-]+\))?: ]]; then + echo "Docs" + else + echo "Other" + fi +} + +# Check if version is provided +if [ "$#" -ne 1 ]; then + usage +fi + +NEW_VERSION=$1 + +# 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 + + # Extract PR or issue number if present in the message + ref_number="" + ref_type="" + # Look for (#1234) or #1234 in the message + if [[ "$message" =~ \(#([0-9]+)\) ]]; then + ref_number="${BASH_REMATCH[1]}" + ref_type="pull" + elif [[ "$message" =~ \#([0-9]+) ]]; then + ref_number="${BASH_REMATCH[1]}" + ref_type="issues" + 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 + if [ -n "$ref_number" ]; then + echo "* $formatted_message ([#$ref_number]($REPO_URL/$ref_type/$ref_number))" >> "$tmp_file" + else + echo "* $formatted_message" >> "$tmp_file" + fi + 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" + 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" \ No newline at end of file From 9a9c4cee932a4e15b30077b57e158a35c38c3d12 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:16:48 -0700 Subject: [PATCH 2/9] wrong order --- scripts/release/draft-release | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/release/draft-release b/scripts/release/draft-release index 3232e20..1f1cf42 100755 --- a/scripts/release/draft-release +++ b/scripts/release/draft-release @@ -44,6 +44,10 @@ NEW_VERSION=${NEW_VERSION#v} 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" From 0d56f0d2f9884991d6582920e0a5bc1aee71a3b2 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:39:41 -0700 Subject: [PATCH 3/9] updating versions to match in changelog and package.json --- CHANGELOG.md | 13 +++---- package.json | 2 +- scripts/release/update-changelog | 61 ++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f53dd6..06bb483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Changelog + All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.3.4-ea] - 2024-03-21 -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 -- Update CODEOWNERS +### Changes -## [0.1.0-ea] - 2024-08-12 +### Features +* Initial release ([c5d593f](https://github.com/heroku/heroku-applink-nodejs/commit/c5d593fa3c0f37607239e3ded7c2c24d7354383c)) -- Initial diff --git a/package.json b/package.json index 3099a8a..237e712 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@heroku/salesforce-sdk-nodejs", - "version": "0.3.5", + "version": "0.3.4-ea", "description": "Salesforce SDK for Heroku Apps.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/scripts/release/update-changelog b/scripts/release/update-changelog index 7d2325b..8a78983 100755 --- a/scripts/release/update-changelog +++ b/scripts/release/update-changelog @@ -17,30 +17,55 @@ usage() { exit 1 } -# Function to categorize commits based on conventional commit types +# 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 - # Check for conventional commit types - if [[ "$message" =~ ^feat(\([a-z-]+\))?: ]]; then + # 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" - elif [[ "$message" =~ ^fix(\([a-z-]+\))?: ]]; then + return + fi + + # Bug Fixes + if [[ "$message_lower" =~ ^(fix|fixes|fixed|resolve|resolves|resolved|correct|corrects|corrected|bug|issue|error|err|typo) ]]; then echo "Fixes" - elif [[ "$message" =~ ^docs(\([a-z-]+\))?: ]]; then - echo "Docs" - else - echo "Other" + return fi -} -# Check if version is provided -if [ "$#" -ne 1 ]; then - usage -fi + # Documentation + if [[ "$message_lower" =~ ^(doc|docs|document|documentation|readme|comment|comments|note|notes|changelog) ]]; then + echo "Docs" + return + fi -NEW_VERSION=$1 + # 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 "") @@ -73,7 +98,7 @@ fi 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)" + VERSION_HEADER="# [$NEW_VERSION] - $(date +%Y-%m-%d)" fi echo "Version header: $VERSION_HEADER" @@ -137,11 +162,11 @@ echo "$COMMITS" | while IFS='|' read -r message hash; do *) tmp_file="$OTHER_TMP" ;; esac - # Add the commit to the appropriate category file + # Add the commit to the appropriate category file with commit hash link if [ -n "$ref_number" ]; then - echo "* $formatted_message ([#$ref_number]($REPO_URL/$ref_type/$ref_number))" >> "$tmp_file" + echo "* $formatted_message ([#$ref_number]($REPO_URL/$ref_type/$ref_number)) ([${hash:0:7}]($REPO_URL/commit/$hash))" >> "$tmp_file" else - echo "* $formatted_message" >> "$tmp_file" + echo "* $formatted_message ([${hash:0:7}]($REPO_URL/commit/$hash))" >> "$tmp_file" fi fi done From a0bb06e826eca2de2cd93f38158ecdb1350c61e9 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:46:34 -0700 Subject: [PATCH 4/9] may not have previous tag --- scripts/release/update-changelog | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/release/update-changelog b/scripts/release/update-changelog index 8a78983..1f366e9 100755 --- a/scripts/release/update-changelog +++ b/scripts/release/update-changelog @@ -94,12 +94,8 @@ 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] - $(date +%Y-%m-%d)" -fi +# Create the new version content with comparison to HEAD +VERSION_HEADER="# [$NEW_VERSION]($REPO_URL/compare/HEAD...$NEW_VERSION) - $(date +%Y-%m-%d)" echo "Version header: $VERSION_HEADER" From 4cdecb129d3a25faa91c94a146cac1928577b567 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:53:21 -0700 Subject: [PATCH 5/9] update build --- .github/workflows/package.yml | 62 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index b52e212..320ed08 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -4,40 +4,38 @@ on: push: branches: [ main ] pull_request: - branches: [ main ] - +permissions: + contents: read jobs: build: - runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - node-version: [18.x, 20.x] - + version: [22, 20, 18] + os: + [sfdc-hk-ubuntu-latest, sfdc-hk-macos-latest, sfdc-hk-windows-latest] + name: Node ${{ matrix.version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Lint - run: npm run lint - - - name: Test - run: npm run test - - - name: Build - run: npm run build - - - name: Upload artifacts - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'release') - uses: actions/upload-artifact@v4 - with: - name: ${{ github.ref_name }} - path: dist/ - retention-days: 90 + - uses: actions/checkout@v4 + - 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: ${{ matrix.version }} + cache: "npm" + - run: npm install + - run: npm run format:check + - run: npm run lint + - run: npm run test + - run: npm run build + - name: Upload artifacts + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'release') + uses: actions/upload-artifact@v4 + with: + name: ${{ github.ref_name }} + path: dist/ + retention-days: 90 From 4d9da76f3f30125016215d8593f3a17e08910405 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 15:58:12 -0700 Subject: [PATCH 6/9] get rid of unused versions --- scripts/release/draft-release | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/release/draft-release b/scripts/release/draft-release index 1f1cf42..6f82330 100755 --- a/scripts/release/draft-release +++ b/scripts/release/draft-release @@ -5,7 +5,7 @@ set -o pipefail # Script to create a draft release branch and PR # # Usage: -# ./scripts/release/draft-release [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] +# ./scripts/release/draft-release [ | major | minor | patch | ] # # This will: # 1. Run npm version with the provided version type @@ -14,16 +14,10 @@ set -o pipefail # 4. Create a draft PR from the release branch to main usage() { - echo "Usage: $0 [ | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]" - echo " newversion: The new version to release (e.g., 1.2.3)" + echo "Usage: $0 [ | major | minor | patch | ]" echo " major: Bump major version (1.0.0 -> 2.0.0)" echo " minor: Bump minor version (1.0.0 -> 1.1.0)" echo " patch: Bump patch version (1.0.0 -> 1.0.1)" - echo " premajor: Bump major version with prerelease (1.0.0 -> 2.0.0-0)" - echo " preminor: Bump minor version with prerelease (1.0.0 -> 1.1.0-0)" - echo " prepatch: Bump patch version with prerelease (1.0.0 -> 1.0.1-0)" - echo " prerelease: Bump prerelease version (1.0.0 -> 1.0.0-0)" - echo " from-git: Use version from git tags" exit 1 } From 81e49e98aeaccb436176da1424f82b786350dbfa Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Mon, 19 May 2025 16:02:05 -0700 Subject: [PATCH 7/9] prettier formatting --- .github/workflows/draft-release.yml | 4 ++-- .github/workflows/package.yml | 2 +- .github/workflows/publish.yml | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index ab268e7..af5b29a 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: bump_type: - description: 'Type of version bump (major, minor, patch)' + description: "Type of version bump (major, minor, patch)" required: true type: choice options: @@ -24,7 +24,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: "20.x" - name: Make scripts executable run: | diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 320ed08..36b3500 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -2,7 +2,7 @@ name: Package Library on: push: - branches: [ main ] + branches: [main] pull_request: permissions: contents: read diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 99fad9e..5379887 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ name: Publish Release on: push: tags: - - 'v*' + - "v*" jobs: check_for_moratorium: @@ -29,8 +29,8 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' - registry-url: 'https://registry.npmjs.org' + node-version: "20.x" + registry-url: "https://registry.npmjs.org" - name: Download artifacts uses: actions/download-artifact@v4 @@ -63,4 +63,4 @@ jobs: env: ACTOR_EMAIL: ${{ secrets.TPS_API_RELEASE_ACTOR_EMAIL }} TPS_API_TOKEN: ${{ secrets.TPS_API_TOKEN_PARAM }} - run: ./scripts/release/tps-record-release heroku-applink-nodejs ${{ github.sha }} \ No newline at end of file + run: ./scripts/release/tps-record-release heroku-applink-nodejs ${{ github.sha }} From 5f902f2470dc16c4d71f14ee1dd3ecaddd5e87b3 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Fri, 6 Jun 2025 10:48:04 -0700 Subject: [PATCH 8/9] overhaul based on testing python outcomes --- .github/workflows/create-tag.yml | 33 ----------- .github/workflows/draft-release.yml | 31 ++++++---- .github/workflows/package.yml | 4 +- .github/workflows/publish.yml | 54 ++++++++--------- .github/workflows/tag-and-release.yml | 73 +++++++++++++++++++++++ scripts/release/draft-release | 54 ++++++++++++----- scripts/release/tps-record-release | 85 --------------------------- scripts/release/update-changelog | 29 +++------ 8 files changed, 171 insertions(+), 192 deletions(-) delete mode 100644 .github/workflows/create-tag.yml create mode 100644 .github/workflows/tag-and-release.yml delete mode 100755 scripts/release/tps-record-release diff --git a/.github/workflows/create-tag.yml b/.github/workflows/create-tag.yml deleted file mode 100644 index c21beb0..0000000 --- a/.github/workflows/create-tag.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Create Tag - -on: - workflow_run: - workflows: ["Package Library"] - types: - - completed - branches: - - main - -jobs: - push-git-tag: - if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.head_branch, 'release') }} - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Extract version from branch name - id: version - run: | - BRANCH_NAME="${{ github.event.workflow_run.head_branch }}" - VERSION=${BRANCH_NAME#release-v} - VERSION=${VERSION#release/} - VERSION=${VERSION#release-} - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Create tag - run: | - git tag "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" - git push origin "v${{ steps.version.outputs.version }}" diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index af5b29a..33be7f7 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -12,6 +12,10 @@ on: - minor - patch +permissions: + contents: write + pull-requests: write + jobs: draft-release-branch: runs-on: ubuntu-latest @@ -19,19 +23,26 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: + ref: main fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.x" + - name: Authenticate GitHub CLI + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if ! gh auth status; then + echo "GitHub CLI authentication failed" + exit 1 + fi - - name: Make scripts executable + - name: Configure Git run: | - chmod +x scripts/release/draft-release - chmod +x scripts/release/update-changelog + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" - - name: Run release script - run: ./scripts/release/draft-release "${{ github.event.inputs.bump_type }}" + - name: Create release branch env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ./scripts/release/draft-release "${{ github.event.inputs.bump_type }}" diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 36b3500..f68cf43 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -32,10 +32,10 @@ jobs: - run: npm run lint - run: npm run test - run: npm run build - - name: Upload artifacts + - name: Upload artifacts for release if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'release') uses: actions/upload-artifact@v4 with: - name: ${{ github.ref_name }} + name: release-artifacts-main path: dist/ retention-days: 90 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5379887..821184c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,12 +1,21 @@ -name: Publish Release +name: Publish to NPM and Change Management on: - push: - tags: - - "v*" + 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: @@ -16,6 +25,8 @@ jobs: 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: @@ -32,35 +43,22 @@ jobs: node-version: "20.x" registry-url: "https://registry.npmjs.org" - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - name: ${{ github.ref_name }} - path: . + - name: Get workflow run ID + id: get_workflow + run: | + echo "run_id=${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT + + - name: Download build artifacts + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p dist + gh run download ${{ steps.get_workflow.outputs.run_id }} --name release-artifacts-main --dir dist/ - name: Install dependencies run: npm ci - - name: Create GitHub Release - uses: softprops/action-gh-release@v1 - with: - generate_release_notes: true - files: | - dist/**/* - package.json - README.md - CHANGELOG.md - LICENSE.txt - SECURITY.md - TERMS_OF_USE.md - - 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 }} - 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..1354734 --- /dev/null +++ b/.github/workflows/tag-and-release.yml @@ -0,0 +1,73 @@ +name: Create Github Tag and Release + +on: + workflow_run: + workflows: ["Package Library"] + 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 }} + + - name: Download artifact using GitHub CLI + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p dist + gh run download ${{ github.event.workflow_run.id }} --name release-artifacts-main --dir dist/ + + - 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 "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@v1 + with: + generate_release_notes: true + files: | + dist/**/* + package.json + README.md + CHANGELOG.md + LICENSE.txt + SECURITY.md + TERMS_OF_USE.md diff --git a/scripts/release/draft-release b/scripts/release/draft-release index 6f82330..6b110ab 100755 --- a/scripts/release/draft-release +++ b/scripts/release/draft-release @@ -2,38 +2,64 @@ set -eu set -o pipefail -# Script to create a draft release branch and PR +# draft-release +# Script to create a release branch and PR for a new version # # Usage: -# ./scripts/release/draft-release [ | major | minor | patch | ] +# ./release/draft-release +# bump_type: major, minor, or patch # -# This will: -# 1. Run npm version with the provided version type -# 2. Create a release branch named release-v{version} -# 3. Update CHANGELOG.md using update-changelog.sh -# 4. Create a draft PR from the release branch to main +# Example: +# ./scripts/release/draft-release minor +# ./scripts/release/draft-release patch + +# Function to display usage usage() { - echo "Usage: $0 [ | major | minor | patch | ]" - echo " major: Bump major version (1.0.0 -> 2.0.0)" - echo " minor: Bump minor version (1.0.0 -> 1.1.0)" - echo " patch: Bump patch version (1.0.0 -> 1.0.1)" + 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 } -# Check if version type is provided -if [ "$#" -ne 1 ]; then +# 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 -VERSION_TYPE=$1 +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 and capture the new version echo "Running npm version $VERSION_TYPE..." NEW_VERSION=$(npm version "$VERSION_TYPE" --no-git-tag-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}" diff --git a/scripts/release/tps-record-release b/scripts/release/tps-record-release deleted file mode 100755 index bf25223..0000000 --- a/scripts/release/tps-record-release +++ /dev/null @@ -1,85 +0,0 @@ -#!/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 index 1f366e9..4ca4f97 100755 --- a/scripts/release/update-changelog +++ b/scripts/release/update-changelog @@ -94,8 +94,12 @@ if [ -f "CHANGELOG.md" ]; then cp "CHANGELOG.md" "$OLD_CONTENT" fi -# Create the new version content with comparison to HEAD -VERSION_HEADER="# [$NEW_VERSION]($REPO_URL/compare/HEAD...$NEW_VERSION) - $(date +%Y-%m-%d)" +# 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" @@ -137,18 +141,6 @@ echo "$COMMITS" | while IFS='|' read -r message hash; do formatted_message="$formatted_message (BREAKING CHANGE)" fi - # Extract PR or issue number if present in the message - ref_number="" - ref_type="" - # Look for (#1234) or #1234 in the message - if [[ "$message" =~ \(#([0-9]+)\) ]]; then - ref_number="${BASH_REMATCH[1]}" - ref_type="pull" - elif [[ "$message" =~ \#([0-9]+) ]]; then - ref_number="${BASH_REMATCH[1]}" - ref_type="issues" - fi - # Get the category for this commit category=$(categorize_commit "$message" "$hash" "") case "$category" in @@ -158,12 +150,8 @@ echo "$COMMITS" | while IFS='|' read -r message hash; do *) tmp_file="$OTHER_TMP" ;; esac - # Add the commit to the appropriate category file with commit hash link - if [ -n "$ref_number" ]; then - echo "* $formatted_message ([#$ref_number]($REPO_URL/$ref_type/$ref_number)) ([${hash:0:7}]($REPO_URL/commit/$hash))" >> "$tmp_file" - else - echo "* $formatted_message ([${hash:0:7}]($REPO_URL/commit/$hash))" >> "$tmp_file" - fi + # 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 @@ -189,6 +177,7 @@ if [ -f "$OLD_CONTENT" ]; then 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" From 3f498c11f44bcc9233098b2bc425115fa9aa3c04 Mon Sep 17 00:00:00 2001 From: Brittany Jones Date: Thu, 19 Jun 2025 10:28:07 -0700 Subject: [PATCH 9/9] more cleanup --- .github/workflows/draft-release.yml | 48 --------------- .github/workflows/package.yml | 2 +- .github/workflows/publish.yml | 25 +++++++- .github/workflows/tag-and-release.yml | 3 +- .gitignore | 7 +++ scripts/release/tps-record-release | 84 +++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 53 deletions(-) delete mode 100644 .github/workflows/draft-release.yml create mode 100755 scripts/release/tps-record-release diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml deleted file mode 100644 index 33be7f7..0000000 --- a/.github/workflows/draft-release.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Draft Release Branch - -on: - workflow_dispatch: - inputs: - bump_type: - description: "Type of version bump (major, minor, patch)" - required: true - type: choice - options: - - major - - minor - - patch - -permissions: - contents: write - pull-requests: write - -jobs: - draft-release-branch: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: main - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Authenticate GitHub CLI - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if ! gh auth status; then - echo "GitHub CLI authentication failed" - exit 1 - fi - - - name: Configure Git - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - - - name: Create release branch - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - ./scripts/release/draft-release "${{ github.event.inputs.bump_type }}" diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index f68cf43..2daabd6 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -36,6 +36,6 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'release') uses: actions/upload-artifact@v4 with: - name: release-artifacts-main + name: heroku-applink-nodejs path: dist/ retention-days: 90 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 821184c..0328fa4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,17 +43,28 @@ jobs: node-version: "20.x" registry-url: "https://registry.npmjs.org" - - name: Get workflow run ID + - name: Get latest build workflow run id: get_workflow + env: + GH_TOKEN: ${{ github.token }} run: | - echo "run_id=${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT + # Get the most recent successful build workflow run for this commit + RUN_ID=$(gh run list --workflow "test-lint-build.yml" --branch main --json databaseId,conclusion,headSha --jq ".[] | select(.headSha == \"${{ github.sha }}\" and .conclusion == \"success\") | .databaseId" | head -n 1) + + if [ -z "$RUN_ID" ]; then + echo "No successful build found for commit ${{ github.sha }}" + exit 1 + fi + + echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT + echo "Found build workflow run: $RUN_ID" - name: Download build artifacts env: GH_TOKEN: ${{ github.token }} run: | mkdir -p dist - gh run download ${{ steps.get_workflow.outputs.run_id }} --name release-artifacts-main --dir dist/ + gh run download ${{ steps.get_workflow.outputs.run_id }} --name heroku-applink-nodejs --dir dist/ - name: Install dependencies run: npm ci @@ -62,3 +73,11 @@ jobs: 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 index 1354734..7a6511a 100644 --- a/.github/workflows/tag-and-release.yml +++ b/.github/workflows/tag-and-release.yml @@ -46,7 +46,7 @@ jobs: - name: Create and push Github tag run: | echo "Creating tag v${{ steps.version.outputs.version }}" - git tag "v${{ steps.version.outputs.version }}" -m "Release 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:" @@ -62,6 +62,7 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: + tag_name: ${{ steps.get_tag.outputs.tag }} generate_release_notes: true files: | dist/**/* diff --git a/.gitignore b/.gitignore index 3fc2826..dad4ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,10 @@ tsconfig.tsbuildinfo .DS_Store src/**/*.js src/**/*.js.map + +# Salesforce +.codegenie/ + +# Heroku TPS TEMP +tpsGetLock_response.txt +tpsRecordRelease_response.txt \ No newline at end of file diff --git a/scripts/release/tps-record-release b/scripts/release/tps-record-release new file mode 100755 index 0000000..82553af --- /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