diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7eb4bd03f..360d8bfb2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,9 +9,13 @@ name: CodeQL Analysis on: push: - branches: [main] + branches: + - main + - 'release/**' pull_request: - branches: [main] + branches: + - main + - 'release/**' schedule: - cron: '17 4 * * 5' # Weekly on Friday at 4:17 AM UTC diff --git a/.github/workflows/dev-cleanup.yml b/.github/workflows/dev-cleanup.yml index 8f4062c69..c9c76fb9f 100644 --- a/.github/workflows/dev-cleanup.yml +++ b/.github/workflows/dev-cleanup.yml @@ -5,6 +5,7 @@ on: types: [closed] branches: - main + - 'release/**' env: GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} @@ -25,7 +26,7 @@ jobs: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: main + ref: ${{ github.event.pull_request.base.ref }} - name: Setup pnpm uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 diff --git a/.github/workflows/dev-publish.yml b/.github/workflows/dev-publish.yml index 9bf83652d..a7111093f 100644 --- a/.github/workflows/dev-publish.yml +++ b/.github/workflows/dev-publish.yml @@ -9,12 +9,14 @@ on: types: [labeled, synchronize] branches: - main + - 'release/**' env: GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} TURBO_TELEMETRY_DISABLED: 1 DO_NOT_TRACK: 1 + BASE_REF: ${{ github.event.pull_request.base.ref }} concurrency: group: dev-publish-pr-${{ github.event.pull_request.number }} @@ -96,7 +98,7 @@ jobs: - name: Get changed packages id: changed run: | - CHANGED_FILES=$(git diff --name-only origin/main...HEAD) + CHANGED_FILES=$(git diff --name-only origin/${BASE_REF}...HEAD) PACKAGES="" for pkg_dir in packages/* web-packages/*; do diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index cd3eb9479..e95fda532 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -4,10 +4,12 @@ on: pull_request: branches: - main + - 'release/**' env: GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + BASE_REF: ${{ github.event.pull_request.base.ref }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -99,7 +101,7 @@ jobs: - name: Run Build if: matrix.check == 'Build' run: | - if git diff --name-only origin/main...HEAD | grep -qE '^(pnpm-lock\.yaml|package\.json)$'; then + if git diff --name-only origin/${BASE_REF}...HEAD | grep -qE '^(pnpm-lock\.yaml|package\.json)$'; then echo "📦 Root dependency files changed - building all packages" pnpm build else @@ -133,7 +135,7 @@ jobs: - name: Check for lockfile changes id: lockfile_check run: | - if git diff --name-only origin/main...HEAD | grep -q '^pnpm-lock\.yaml$'; then + if git diff --name-only origin/${BASE_REF}...HEAD | grep -q '^pnpm-lock\.yaml$'; then echo "changed=true" >> $GITHUB_OUTPUT else echo "changed=false" >> $GITHUB_OUTPUT diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml index 68fbe62e6..d41ac9e82 100644 --- a/.github/workflows/pr-size.yml +++ b/.github/workflows/pr-size.yml @@ -8,7 +8,9 @@ on: - synchronize - ready_for_review - converted_to_draft - branches: [main] + branches: + - main + - 'release/**' permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5cde822de..28b2ac916 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - 'release/**' env: GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} @@ -12,11 +13,12 @@ env: DO_NOT_TRACK: 1 concurrency: - group: ${{ github.workflow }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false jobs: release: + if: "!contains(github.event.head_commit.message, '[skip ci]')" name: Release and Publish runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 5e6eee4f3..9ff8c2e24 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - main + - 'release/**' push: branches: - main + - 'release/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1629e7567..6049d7f5b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -344,6 +344,75 @@ feat(apollo-react): add ApButton variant feat(apollo-core): add new color token for button variant ``` +## Maintenance Releases + +Maintenance branches allow backporting fixes to older major versions after a new major version has been released. For example, releasing `@uipath/apollo-react@3.70.4` after `4.0.0` ships. + +### Branch Naming + +Maintenance branches are package-scoped: `release/@.x` + +Examples: `release/apollo-react@3.x`, `release/apollo-core@5.x` + +### When to Create a Maintenance Branch + +Create one when you ship a new major version and need to continue supporting the previous major for existing consumers. + +### Creating a Maintenance Branch + +Use the helper script: + +```bash +scripts/create-maintenance-branch.sh apollo-react 3 +``` + +This will: +1. Find the latest `@uipath/apollo-react@3.*` tag +2. Create `release/apollo-react@3.x` from that tag +3. Configure semantic-release on the new branch +4. Print next steps + +After running the script: +1. Push the maintenance branch: `git push -u origin 'release/apollo-react@3.x'` +2. On `main`, update `packages/apollo-react/.releaserc.json` to add the maintenance branch entry **before** `"main"` in the `branches` array: + ```json + "branches": [ + { "name": "release/apollo-react@3.x", "range": "3.x", "channel": "release-3.x" }, + "main" + ] + ``` + +### Backporting Fixes + +Cherry-pick from `main` or create a PR targeting the maintenance branch directly: + +```bash +git checkout release/apollo-react@3.x +git cherry-pick +git push +# → CI releases apollo-react@3.70.4 with dist-tag `release-3.x` +``` + +### Installing Maintenance Releases + +Maintenance releases publish under a dedicated npm dist-tag (not `latest`): + +```bash +# Install latest maintenance release for 3.x +npm install @uipath/apollo-react@release-3.x + +# Install a specific version +npm install @uipath/apollo-react@3.70.4 +``` + +### Cross-Package Fixes + +If a fix touches multiple packages (e.g., `apollo-core` and `apollo-react`), add the maintenance branch entry to each package's `.releaserc.json`. Both packages will be released from the same push. + +### How Other Packages Behave + +When CI runs `pnpm release` on a maintenance branch, semantic-release executes for all packages. Packages whose `.releaserc.json` doesn't list the branch simply skip with exit code 0 — no special handling needed. + ## Package Structure ``` diff --git a/packages/apollo-core/.releaserc.json b/packages/apollo-core/.releaserc.json index 1e8245969..9f9798a19 100644 --- a/packages/apollo-core/.releaserc.json +++ b/packages/apollo-core/.releaserc.json @@ -30,7 +30,7 @@ [ "@semantic-release/exec", { - "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public" + "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public --tag ${nextRelease.channel || 'latest'}" } ], [ diff --git a/packages/apollo-react/.releaserc.json b/packages/apollo-react/.releaserc.json index 180120655..4ab83d0e7 100644 --- a/packages/apollo-react/.releaserc.json +++ b/packages/apollo-react/.releaserc.json @@ -30,7 +30,7 @@ [ "@semantic-release/exec", { - "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public" + "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public --tag ${nextRelease.channel || 'latest'}" } ], [ diff --git a/packages/apollo-wind/.releaserc.json b/packages/apollo-wind/.releaserc.json index e9ef5efb1..a9be46737 100644 --- a/packages/apollo-wind/.releaserc.json +++ b/packages/apollo-wind/.releaserc.json @@ -30,7 +30,7 @@ [ "@semantic-release/exec", { - "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public" + "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public --tag ${nextRelease.channel || 'latest'}" } ], [ diff --git a/scripts/create-maintenance-branch.sh b/scripts/create-maintenance-branch.sh new file mode 100755 index 000000000..5ab3f7866 --- /dev/null +++ b/scripts/create-maintenance-branch.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# Create a maintenance branch for an older major version of a package. +# +# Usage: scripts/create-maintenance-branch.sh +# Example: scripts/create-maintenance-branch.sh apollo-react 3 +# +# This script: +# 1. Finds the latest git tag for @uipath/@.* +# 2. Creates release/@.x branch from that tag +# 3. Updates the package's .releaserc.json with maintenance branch config +# 4. Commits the config change +# 5. Prints next steps (push, update main's .releaserc.json) + +set -euo pipefail + +PACKAGE="${1:?Usage: $0 }" +MAJOR="${2:?Usage: $0 }" + +if ! [[ "$MAJOR" =~ ^[0-9]+$ ]]; then + echo "Error: major version must be a number, got '$MAJOR'" + exit 1 +fi + +TAG_PATTERN="@uipath/${PACKAGE}@${MAJOR}.*" +BRANCH="release/${PACKAGE}@${MAJOR}.x" +CHANNEL="release-${MAJOR}.x" + +# Find package directory +if [ -d "packages/${PACKAGE}" ]; then + RELEASERC="packages/${PACKAGE}/.releaserc.json" +elif [ -d "web-packages/${PACKAGE}" ]; then + RELEASERC="web-packages/${PACKAGE}/.releaserc.json" +else + echo "Error: package '${PACKAGE}' not found in packages/ or web-packages/" + exit 1 +fi + +if [ ! -f "${RELEASERC}" ]; then + echo "Error: release config '${RELEASERC}' not found for package '${PACKAGE}'" + exit 1 +fi + +# Find latest tag for this major version +LATEST_TAG=$(git tag --list "${TAG_PATTERN}" --sort=-version:refname | head -1) + +if [ -z "$LATEST_TAG" ]; then + echo "Error: no tags found matching '${TAG_PATTERN}'" + echo "" + echo "Available tags for @uipath/${PACKAGE}:" + git tag --list "@uipath/${PACKAGE}@*" --sort=-version:refname | head -5 + exit 1 +fi + +echo "Latest tag: ${LATEST_TAG}" +echo "Branch: ${BRANCH}" +echo "Channel: ${CHANNEL}" +echo "" + +# Check if branch already exists +if git show-ref --verify --quiet "refs/heads/${BRANCH}" 2>/dev/null || \ + git show-ref --verify --quiet "refs/remotes/origin/${BRANCH}" 2>/dev/null; then + echo "Error: branch '${BRANCH}' already exists" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "Error: jq is required. Please install jq using your package manager (e.g. 'brew install jq' on macOS) and try again." + exit 1 +fi + +# Create branch from tag +git checkout -b "${BRANCH}" "${LATEST_TAG}" + +# Update .releaserc.json — insert the maintenance branch entry immediately before +# "main", preserving order of all existing branches (other maintenance branches, +# prereleases that come after main, etc.). If an entry for this branch already +# exists, it's replaced in place. +jq --arg name "${BRANCH}" \ + --arg range "${MAJOR}.x" \ + --arg channel "${CHANNEL}" \ + ' + .branches as $existing + | { name: $name, range: $range, channel: $channel } as $entry + | ($existing | map((type == "object" and .name == $name) or . == $name) | index(true)) as $existing_idx + | ($existing | map((type == "object" and .name == "main") or . == "main") | index(true)) as $main_idx + | if $existing_idx != null then + .branches = ($existing | .[0:$existing_idx] + [$entry] + .[$existing_idx+1:]) + elif $main_idx != null then + .branches = ($existing | .[0:$main_idx] + [$entry] + .[$main_idx:]) + else + .branches = ($existing + [$entry]) + end + ' \ + "${RELEASERC}" > "${RELEASERC}.tmp" && mv "${RELEASERC}.tmp" "${RELEASERC}" + +echo "✓ Updated ${RELEASERC}" +echo "" + +# Commit the change +git add "${RELEASERC}" +git commit -m "ci(${PACKAGE}): configure maintenance branch for ${MAJOR}.x [skip ci]" + +echo "" +echo "✓ Branch '${BRANCH}' created and configured" +echo "" +echo "Next steps:" +echo "" +echo " 1. Push the branch:" +echo " git push -u origin '${BRANCH}'" +echo "" +echo " 2. On main, update ${RELEASERC} to add this entry" +echo " BEFORE \"main\" in the branches array:" +echo "" +echo " {\"name\": \"${BRANCH}\", \"range\": \"${MAJOR}.x\", \"channel\": \"${CHANNEL}\"}" +echo "" +echo " Example .releaserc.json branches on main:" +echo " \"branches\": [" +echo " {\"name\": \"${BRANCH}\", \"range\": \"${MAJOR}.x\", \"channel\": \"${CHANNEL}\"}," +echo " \"main\"" +echo " ]" diff --git a/web-packages/ap-chat/.releaserc.json b/web-packages/ap-chat/.releaserc.json index e75afd532..50aec36f0 100644 --- a/web-packages/ap-chat/.releaserc.json +++ b/web-packages/ap-chat/.releaserc.json @@ -30,7 +30,7 @@ [ "@semantic-release/exec", { - "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public" + "publishCmd": "bash ../../scripts/publish-to-registries.sh --no-git-checks --access public --tag ${nextRelease.channel || 'latest'}" } ], [