From 85a04b66557c4dc290d11627caa21c3d57c6f2d6 Mon Sep 17 00:00:00 2001 From: Ben Elan Date: Tue, 14 May 2024 14:52:45 -0700 Subject: [PATCH] ci: setup hotfix releasing scripts and settings (#9337) **Related Issue:** N/A ## Summary Setup continuous integration for releasing hotfixes from a separate branch so normal work can continue on `main`. --- .github/workflows/chromatic-rc.yml | 44 ---------- .../{chromatic-main.yml => chromatic.yml} | 8 +- .github/workflows/deploy-latest.yml | 24 ++++-- .github/workflows/pr-bot.yml | 2 +- .github/workflows/pr-e2e.yml | 2 +- ...tests_eslint-plugin-calcite-components.yml | 4 +- lerna.json | 2 +- package.json | 17 ++-- support/release.sh | 83 +++++++++++++++++++ support/syncLinkedPackageVersions.ts | 2 +- 10 files changed, 119 insertions(+), 69 deletions(-) delete mode 100644 .github/workflows/chromatic-rc.yml rename .github/workflows/{chromatic-main.yml => chromatic.yml} (89%) create mode 100755 support/release.sh diff --git a/.github/workflows/chromatic-rc.yml b/.github/workflows/chromatic-rc.yml deleted file mode 100644 index ea94d5c8f8e..00000000000 --- a/.github/workflows/chromatic-rc.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: "Chromatic - RC" -on: - push: - branches: [rc] - pull_request: - branches: [rc] - types: [labeled, synchronize] -jobs: - run: - if: | - (github.event.action == 'labeled' && github.event.label.name == 'pr ready for visual snapshots') || github.event_name == 'push' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version-file: package.json - - run: npm install - - run: npm --workspace="packages/calcite-design-tokens" run build - - name: Publish to Chromatic - uses: chromaui/action@v1 - with: - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - zip: true - exitOnceUploaded: true - autoAcceptChanges: rc - workingDir: packages/calcite-components - env: - STORYBOOK_SCREENSHOT_TEST_BUILD: true - CHROMATIC_DIFF_THRESHOLD: ${{ secrets.CHROMATIC_DIFF_THRESHOLD }} - skip: - if: contains(github.event.pull_request.labels.*.name, 'skip visual snapshots') - runs-on: ubuntu-latest - steps: - - name: Skip Chromatic - uses: Sibz/github-status-action@v1 - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - context: UI Tests - description: Chromatic run skipped - state: success - sha: ${{github.event.pull_request.head.sha || github.sha}} diff --git a/.github/workflows/chromatic-main.yml b/.github/workflows/chromatic.yml similarity index 89% rename from .github/workflows/chromatic-main.yml rename to .github/workflows/chromatic.yml index 8c59c832ffb..faa8e133107 100644 --- a/.github/workflows/chromatic-main.yml +++ b/.github/workflows/chromatic.yml @@ -1,9 +1,9 @@ -name: "Chromatic - Main" +name: Chromatic on: push: - branches: [main] + branches: [main, hotfix, rc] pull_request: - branches: [main] + branches: [main, hotfix, rc] types: [labeled, synchronize] jobs: run: @@ -25,7 +25,7 @@ jobs: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} zip: true exitOnceUploaded: true - autoAcceptChanges: main + autoAcceptChanges: ${{ github.base_ref || github.ref_name }} workingDir: packages/calcite-components env: STORYBOOK_SCREENSHOT_TEST_BUILD: true diff --git a/.github/workflows/deploy-latest.yml b/.github/workflows/deploy-latest.yml index d6dd50931db..c5d4b4ae97e 100644 --- a/.github/workflows/deploy-latest.yml +++ b/.github/workflows/deploy-latest.yml @@ -2,7 +2,7 @@ name: Deploy Latest on: workflow_dispatch: push: - branches: [main] + branches: [main, hotfix] permissions: contents: write pull-requests: write @@ -15,7 +15,7 @@ jobs: with: command: manifest token: ${{ secrets.ADMIN_TOKEN }} - default-branch: main + default-branch: ${{ github.ref_name }} extra-files: | packages/calcite-components/readme.md - name: Checkout Repository @@ -24,7 +24,6 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.ADMIN_TOKEN }} - ref: main - name: Setup Node if: ${{ steps.release.outputs.releases_created }} uses: actions/setup-node@v4 @@ -41,9 +40,22 @@ jobs: # For more info, see: https://github.com/Esri/calcite-design-system/pull/9011 git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" - # the "|| true" prevents failure if there are no changes - git add packages/calcite-components/src/components.d.ts || true - git commit -m "build: update types" || true + + # The "|| true" prevents failure if there are no changes + git add packages/calcite-components/src/components.d.ts package-lock.json || true + + # The release-please PR only updates when there are new deployable + # commits, e.g., fixes, features, or breaking changes. This is fine + # but it means autogenerated files can become outdated. + # + # Lerna will only publish when the working tree is clean, so changes + # to autogenerated files cause the release to fail. + # + # The workaround is to commit the files before releasing so everything + # will be up to date in the dists. The commit will be discarded once + # the container is destroyed, and then the autogenerated files will be + # updated in a subsequent PR. + git commit -m "build: update types and package-lock" || true npm run publish:latest env: diff --git a/.github/workflows/pr-bot.yml b/.github/workflows/pr-bot.yml index c6f4b87e224..c1fc87dd027 100644 --- a/.github/workflows/pr-bot.yml +++ b/.github/workflows/pr-bot.yml @@ -1,7 +1,7 @@ name: PR Bot on: pull_request: - branches: [main, rc] + branches: [main, rc, hotfix] permissions: pull-requests: write issues: write diff --git a/.github/workflows/pr-e2e.yml b/.github/workflows/pr-e2e.yml index 0dc38364e70..cbe5e9c6c31 100644 --- a/.github/workflows/pr-e2e.yml +++ b/.github/workflows/pr-e2e.yml @@ -2,7 +2,7 @@ name: E2E on: workflow_dispatch: pull_request: - branches: [main, rc] + branches: [main, rc, hotfix] jobs: e2e: runs-on: ubuntu-20.04 diff --git a/.github/workflows/pr-tests_eslint-plugin-calcite-components.yml b/.github/workflows/pr-tests_eslint-plugin-calcite-components.yml index 9ea510d7ba3..5eecf132457 100644 --- a/.github/workflows/pr-tests_eslint-plugin-calcite-components.yml +++ b/.github/workflows/pr-tests_eslint-plugin-calcite-components.yml @@ -3,10 +3,10 @@ name: Run eslint-plugin-calcite-components tests on: pull_request: paths: ["packages/eslint-plugin-calcite-components/**"] - branches: [main, rc] + branches: [main, rc, hotfix] push: paths: ["packages/eslint-plugin-calcite-components/**"] - branches: [main, rc] + branches: [main, rc, hotfix] jobs: build: diff --git a/lerna.json b/lerna.json index 29ec8ebb685..6df7a3f8da8 100644 --- a/lerna.json +++ b/lerna.json @@ -5,7 +5,7 @@ "command": { "version": { "conventionalCommits": true, - "allowBranch": ["main", "rc"] + "allowBranch": ["main", "rc", "hotfix"] } } } diff --git a/package.json b/package.json index 1400a1a5f25..0ab1372df7f 100644 --- a/package.json +++ b/package.json @@ -14,19 +14,18 @@ "lint:md": "prettier --write \"**/*.md\" >/dev/null && markdownlint \"{,documentation}/*.md\" --fix --dot --ignore-path .gitignore", "lint:yml": "prettier --write \".github/**/*.yml\" >/dev/null", "lint:json": "prettier --write \"*.json\" >/dev/null", - "publish:next": "lerna publish from-package --dist-tag next --yes", - "publish:rc": "lerna publish from-package --dist-tag rc --yes", - "publish:latest": "lerna publish from-package --yes", - "version:next": "npm run util:is-in-sync-with-origin-main && npm run util:is-working-tree-clean && lerna version --conventional-prerelease --preid next --no-git-tag-version --no-push --yes && npm run util:sync-linked-package-versions -- next", - "version:rc": "npm run util:is-in-sync-with-origin-rc && npm run util:is-working-tree-clean && lerna version --conventional-prerelease --preid rc --no-git-tag-version --no-push --yes && npm run util:sync-linked-package-versions -- rc", - "version:latest": "npm run util:is-in-sync-with-origin-main && npm run util:is-working-tree-clean && lerna version --conventional-commits --create-release github --no-git-tag-version --no-push --yes && npm run util:sync-linked-package-versions -- latest", + "version:latest": "./support/release.sh version", + "publish:latest": "./support/release.sh publish", + "version:next": "./support/release.sh version next", + "publish:next": "./support/release.sh publish next", + "version:rc": "./support/release.sh version rc", + "publish:rc": "./support/release.sh publish rc", + "version:hotfix": "./support/release.sh version hotfix", + "publish:hotfix": "./support/release.sh publish hotfix", "prepare": "husky install", "start": "turbo run start --log-order=stream", "test": "turbo run test --log-order=stream", - "util:is-in-sync-with-origin-main": "[ \"$(git rev-parse --abbrev-ref HEAD)\" = \"main\" ] && [ \"$(git rev-parse main)\" = \"$(git rev-parse origin/main)\" ]", - "util:is-in-sync-with-origin-rc": "[ \"$(git rev-parse --abbrev-ref HEAD)\" = \"rc\" ] && [ \"$(git rev-parse rc)\" = \"$(git rev-parse origin/rc)\" ]", "util:is-next-deployable": "tsx support/isNextDeployable.ts", - "util:is-working-tree-clean": "[ -z \"$(git status --porcelain=v1)\" ]", "util:push-tags": "git push origin main --follow-tags", "util:remove-next-changelog-entries": "tsx support/removeNextChangelogEntries.ts", "util:sync-linked-package-versions": "tsx support/syncLinkedPackageVersions.ts" diff --git a/support/release.sh b/support/release.sh new file mode 100755 index 00000000000..1ef3efb7889 --- /dev/null +++ b/support/release.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -e + +# This script is used to version and publish releases and pre-releases. +# +# @arg1 The deployment step to run, must be either "version" or "publish". +# @arg2 [optional] The pre-release tag, e.g., "next", "hotfix", or "rc". +# Omit this optional argument for a "latest" release + +help() { + [ -n "$1" ] && printf "%s\n" "$@" + echo "Usage: ./release.sh []" + exit 1 +} + +correct_branch_checked_out() { + [ "$(git rev-parse --abbrev-ref HEAD)" = "$branch" ] +} + +in_sync_with_origin() { + [ "$(git rev-parse "$branch")" = "$(git rev-parse "origin/$branch")" ] +} + +working_tree_clean() { + [ -z "$(git status --porcelain=v1)" ] +} + +sanity_checks() { + if ! correct_branch_checked_out; then + help "The '$branch' branch must be checked out before deploying $dist_tag" + elif ! in_sync_with_origin; then + help "The repository must be in sync with 'origin/$branch'" + elif ! working_tree_clean; then + help "The working tree must be clean before running this script." \ + "Use 'git stash push' to save your changes for later." + fi +} + +version() { + sanity_checks + + if [ -z "$dist_tag" ]; then + lerna version --no-push --no-git-tag-version --yes \ + --conventional-commits \ + --create-release github + else + lerna version --no-push --no-git-tag-version --yes \ + --conventional-prerelease \ + --preid "$dist_tag" + fi + + # default to latest if no dist tag was provided in the second argument + npm run util:sync-linked-package-versions -- "${dist_tag:-latest}" +} + +publish() { + # only add the dist-tag flag if the second argument was provided + lerna publish from-package --yes ${dist_tag:+--dist-tag $dist_tag} +} + +main() { + cmd="$1" + dist_tag="$2" + + if [ -z "$dist_tag" ] || [ "$dist_tag" = "next" ]; then + branch="main" + else + branch="$dist_tag" + fi + + if [ -z "$cmd" ]; then + help "missing argument(s). Specify the command: 'version' or 'publish'" + elif [ "$cmd" = "version" ]; then + version + elif [ "$cmd" = "publish" ]; then + publish + else + help "invalid command: '$cmd'" + exit 1 + fi +} + +main "$@" diff --git a/support/syncLinkedPackageVersions.ts b/support/syncLinkedPackageVersions.ts index 2cf104a84ed..b2dc8f18b5b 100644 --- a/support/syncLinkedPackageVersions.ts +++ b/support/syncLinkedPackageVersions.ts @@ -8,7 +8,7 @@ const exec = promisify(childProcess.exec); const releaseTarget = process.argv[2]; - if (!["latest", "next", "rc"].includes(releaseTarget)) { + if (!["latest", "next", "rc", "hotfix"].includes(releaseTarget)) { throw new Error(`Invalid release target: "${releaseTarget}"`); }