From 1f8320790ce018dc151fba38ca550a2e0a5ca796 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 14:52:40 -0800 Subject: [PATCH 1/9] ci: introduce beachball-release action --- .github/actions/beachball-release/README.md | 205 +++++++++++++++++++ .github/actions/beachball-release/action.yml | 162 +++++++++++++++ .github/workflows/create-version-bump-pr.yml | 50 +++++ beachball.config.js => beachball.config.mts | 16 +- 4 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 .github/actions/beachball-release/README.md create mode 100644 .github/actions/beachball-release/action.yml create mode 100644 .github/workflows/create-version-bump-pr.yml rename beachball.config.js => beachball.config.mts (81%) diff --git a/.github/actions/beachball-release/README.md b/.github/actions/beachball-release/README.md new file mode 100644 index 0000000000..9e1b2354db --- /dev/null +++ b/.github/actions/beachball-release/README.md @@ -0,0 +1,205 @@ +# Beachball Release Action + +A GitHub Action that automatically creates release PRs with version bumps using [Beachball](https://github.com/microsoft/beachball), similar to [changesets/action](https://github.com/changesets/action). + +## Features + +- šŸ” Detects change files in your repository +- šŸ“¦ Runs `beachball bump` to update versions and changelogs +- šŸ”€ Creates a pull request with all version changes +- šŸ·ļø Automatically labels PRs for easy identification +- āœ… Provides outputs for downstream workflows + +## Usage + +### Basic Example + +```yaml +name: Create Version Bump PR + +on: + push: + branches: + - main + paths: + - 'change/**' + +permissions: + contents: write + pull-requests: write + +jobs: + version-bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - run: yarn install --immutable + + - name: Create version bump PR + uses: ./.github/actions/beachball-release + with: + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + +### With Custom Options + +```yaml +- name: Create version bump PR + id: beachball + uses: ./.github/actions/beachball-release + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + branch-prefix: 'release/version-bump' + commit-message: 'chore: bump package versions' + pr-title: 'šŸš€ Release: Version Bump' + +- name: Comment on PR + if: steps.beachball.outputs.published == 'true' + run: | + echo "Created PR #${{ steps.beachball.outputs.pr-number }}" + echo "URL: ${{ steps.beachball.outputs.pr-url }}" +``` + +## Inputs + +| Input | Description | Required | Default | +| ---------------- | --------------------------------------- | -------- | ------------------------------------ | +| `github-token` | GitHub token for creating PRs | Yes | - | +| `branch-prefix` | Prefix for the version bump branch name | No | `beachball/version-bump` | +| `commit-message` | Commit message for version bumps | No | `šŸ“¦ Bump package versions [skip ci]` | +| `pr-title` | Title for the version bump PR | No | `šŸ“¦ Bump package versions` | + +## Outputs + +| Output | Description | +| --------------- | ------------------------------------------------------ | +| `has-changeset` | Whether change files were found (`true`/`false`) | +| `published` | Whether a version bump PR was created (`true`/`false`) | +| `pr-number` | The PR number if created | +| `pr-url` | The PR URL if created | + +## How It Works + +1. **Checks for change files** - Uses `beachball check` to detect if there are any change files +2. **Creates a branch** - Creates a new branch with timestamp (e.g., `beachball/version-bump-1234567890`) +3. **Runs beachball bump** - Executes `beachball bump` to update package versions and CHANGELOGs +4. **Commits changes** - Commits all version changes with a descriptive message +5. **Creates PR** - Opens a pull request with the version changes, labeled as `version-bump` and `automated` + +## Requirements + +### Repository Setup + +1. **Workflow Permissions** + + - Go to Settings → Actions → General → Workflow permissions + - Enable "Read and write permissions" + - Enable "Allow GitHub Actions to create and approve pull requests" + +2. **Beachball Configuration** + + - Repository must have a `beachball.config.js` file + - Must be using beachball for version management + +3. **Change Files** + - Change files should be in the `change/` directory + - Created using `beachball change` command + +## Comparison with Changesets Action + +| Feature | Beachball Action | Changesets Action | +| ------------------ | ----------------- | ----------------- | +| Version bumping | āœ… Beachball | āœ… Changesets | +| Auto PR creation | āœ… Yes | āœ… Yes | +| Publishing | Separate workflow | Built-in optional | +| Monorepo support | āœ… Yes | āœ… Yes | +| Change file format | JSON/Markdown | Markdown | + +## Examples + +### Stage 1: PR Creation Only + +Use this action to create version bump PRs while keeping your existing publish workflow: + +```yaml +name: Version Bump PR + +on: + push: + branches: [main] + paths: ['change/**'] + +permissions: + contents: write + pull-requests: write + +jobs: + version-bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: npm install + + - uses: ./.github/actions/beachball-release + with: + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + +### Stage 2: Full Workflow with Publishing + +Combine with tag creation and publishing workflows: + +```yaml +# After version bump PR is merged, create tags +on: + pull_request: + types: [closed] + +jobs: + create-tags: + if: github.event.pull_request.merged == true && + contains(github.event.pull_request.labels.*.name, 'version-bump') + # ... tag creation logic +``` + +## Troubleshooting + +### Action fails with "permission denied" + +Ensure workflow permissions are correctly set: + +- Settings → Actions → General → Workflow permissions +- Select "Read and write permissions" + +### No PR is created + +Check the action outputs: + +- `has-changeset` should be `true` +- Look for change files in the `change/` directory +- Run `npx beachball check` locally to verify + +### PR created but no changes + +Verify: + +- Beachball config is correct +- Change files have valid format +- Packages have correct version in package.json + +## License + +MIT + +## Related + +- [Beachball](https://github.com/microsoft/beachball) - The underlying version management tool +- [Changesets Action](https://github.com/changesets/action) - Similar action for Changesets diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml new file mode 100644 index 0000000000..84cb764a98 --- /dev/null +++ b/.github/actions/beachball-release/action.yml @@ -0,0 +1,162 @@ +name: 'Beachball Release' +description: 'Creates a release PR with version bumps using Beachball' +author: 'Microsoft' + +branding: + icon: 'package' + color: 'blue' + +inputs: + github-token: + description: 'GitHub token for creating PRs' + required: true + branch-prefix: + description: 'Prefix for the version bump branch name' + required: false + default: 'beachball/version-bump' + commit-message: + description: 'Commit message for version bumps' + required: false + default: 'šŸ“¦ Bump package versions [skip ci]' + pr-title: + description: 'Title for the version bump PR' + required: false + default: 'šŸ“¦ Bump package versions' + +outputs: + has-changeset: + description: 'Whether change files were found' + value: ${{ steps.check-changes.outputs.has_changes }} + published: + description: 'Whether version bump PR was created' + value: ${{ steps.create-pr.outputs.pr_created || steps.no-pr.outputs.pr_created || 'false' }} + pr-number: + description: 'The PR number if created' + value: ${{ steps.create-pr.outputs.pr_number || steps.no-pr.outputs.pr_number }} + pr-url: + description: 'The PR URL if created' + value: ${{ steps.create-pr.outputs.pr_url || steps.no-pr.outputs.pr_url }} + +runs: + using: composite + steps: + - name: Configure Git + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Check for change files + id: check-changes + shell: bash + run: | + # Use beachball's built-in check command + if npx beachball check --verbose; then + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "āœ… Change files detected" + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "ā„¹ļø No change files found" + fi + + - name: Create version bump branch + if: steps.check-changes.outputs.has_changes == 'true' + id: create-branch + shell: bash + run: | + BRANCH_NAME="${{ inputs.branch-prefix }}-$(date +%s)" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + git switch -c "$BRANCH_NAME" + echo "šŸ“ Created branch: $BRANCH_NAME" + + - name: Run beachball bump + if: steps.check-changes.outputs.has_changes == 'true' + shell: bash + run: | + echo "šŸ”„ Running beachball bump..." + npx beachball bump --verbose + + - name: Check for version changes + if: steps.check-changes.outputs.has_changes == 'true' + id: check-version-changes + shell: bash + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "has_version_changes=true" >> $GITHUB_OUTPUT + echo "āœ… Version changes detected" + else + echo "has_version_changes=false" >> $GITHUB_OUTPUT + echo "āš ļø No version changes after bump" + fi + + - name: Commit version bumps + if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + shell: bash + run: | + git add . + git commit -m "${{ inputs.commit-message }}" + echo "āœ… Committed version changes" + + - name: Push branch + if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + shell: bash + run: | + git push origin "${{ steps.create-branch.outputs.branch_name }}" + echo "āœ… Pushed branch to origin" + + - name: Create Pull Request + if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + id: create-pr + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + run: | + # Extract changed packages for PR body + CHANGED_PACKAGES=$(git diff HEAD~1 --name-only | grep 'package.json' | sort | uniq || echo "See commit for details") + + PR_URL=$(gh pr create \ + --title "${{ inputs.pr-title }}" \ + --body-file - \ + --base main \ + --head "${{ steps.create-branch.outputs.branch_name }}" \ + --label "automated" \ + --label "version-bump" <> $GITHUB_OUTPUT + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + + echo "āœ… Created PR: $PR_URL" + + - name: Set no-PR outputs + if: steps.check-changes.outputs.has_changes != 'true' || steps.check-version-changes.outputs.has_version_changes != 'true' + id: no-pr + shell: bash + run: | + echo "pr_created=false" >> $GITHUB_OUTPUT + echo "pr_number=" >> $GITHUB_OUTPUT + echo "pr_url=" >> $GITHUB_OUTPUT + if [ "${{ steps.check-changes.outputs.has_changes }}" != "true" ]; then + echo "ā„¹ļø No action taken - no change files found" + else + echo "ā„¹ļø No action taken - no version changes after bump" + fi diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml new file mode 100644 index 0000000000..41de5c64a5 --- /dev/null +++ b/.github/workflows/create-version-bump-pr.yml @@ -0,0 +1,50 @@ +name: Create Version Bump PR with Beachball + +on: + push: + branches: + - main + paths: + - 'change/**' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + version-bump: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup the toolchain + uses: microsoft/react-native-test-app/.github/actions/setup-toolchain@4.4.5 + with: + platform: node + project-root: . + cache-key-prefix: version-bump + node-version: '22.12' + cache-npm-dependencies: yarn + + - name: Install dependencies + run: yarn install --immutable + + - name: Create version bump PR + id: beachball + uses: ./.github/actions/beachball-release + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore(release): bump package versions [skip ci]' + pr-title: 'chore(release): bump package versions' + + - name: Output PR info + if: steps.beachball.outputs.published == 'true' + run: | + echo "āœ… Version bump PR created!" + echo "PR Number: ${{ steps.beachball.outputs.pr-number }}" + echo "PR URL: ${{ steps.beachball.outputs.pr-url }}" diff --git a/beachball.config.js b/beachball.config.mts similarity index 81% rename from beachball.config.js rename to beachball.config.mts index f7be9faeef..7021041f97 100644 --- a/beachball.config.js +++ b/beachball.config.mts @@ -1,8 +1,9 @@ -const fs = require('fs'); -const path = require('path'); -const execSync = require('child_process').execSync; +import fs from 'fs'; +import path from 'path'; +import { execSync } from 'child_process'; +import type { BeachballConfig } from 'beachball'; -module.exports = { +const config: BeachballConfig = { disallowedChangeTypes: ['major'], hooks: { prepublish: (packagePath) => { @@ -36,8 +37,13 @@ module.exports = { }, }; -function getPackagesToInclude() { +/** + * Get list of packages to include in the changelog + */ +function getPackagesToInclude(): string[] { const content = fs.readFileSync(path.resolve('packages/libraries/core/src/index.ts'), 'utf8'); const matches = Array.from(content.matchAll(new RegExp("'(@.*)'", 'g')), (m) => m[1]); return matches; } + +export default config; From 9ac8bbc7240be07cb1a39a61f690963d9c55c0f2 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 15:45:18 -0800 Subject: [PATCH 2/9] updates --- .github/actions/beachball-release/README.md | 16 +++--- .github/actions/beachball-release/action.yml | 51 +++++--------------- .github/workflows/create-version-bump-pr.yml | 2 +- 3 files changed, 21 insertions(+), 48 deletions(-) diff --git a/.github/actions/beachball-release/README.md b/.github/actions/beachball-release/README.md index 9e1b2354db..066251107c 100644 --- a/.github/actions/beachball-release/README.md +++ b/.github/actions/beachball-release/README.md @@ -61,7 +61,7 @@ jobs: pr-title: 'šŸš€ Release: Version Bump' - name: Comment on PR - if: steps.beachball.outputs.published == 'true' + if: steps.beachball.outputs.published == 'yes' run: | echo "Created PR #${{ steps.beachball.outputs.pr-number }}" echo "URL: ${{ steps.beachball.outputs.pr-url }}" @@ -78,12 +78,12 @@ jobs: ## Outputs -| Output | Description | -| --------------- | ------------------------------------------------------ | -| `has-changeset` | Whether change files were found (`true`/`false`) | -| `published` | Whether a version bump PR was created (`true`/`false`) | -| `pr-number` | The PR number if created | -| `pr-url` | The PR URL if created | +| Output | Description | +| ------------- | ---------------------------------------------------- | +| `has-changes` | Whether change files were found (`yes`/`no`) | +| `published` | Whether a version bump PR was created (`yes`/`no`) | +| `pr-number` | The PR number if created | +| `pr-url` | The PR URL if created | ## How It Works @@ -183,7 +183,7 @@ Ensure workflow permissions are correctly set: Check the action outputs: -- `has-changeset` should be `true` +- `has-changes` should be `yes` - Look for change files in the `change/` directory - Run `npx beachball check` locally to verify diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml index 84cb764a98..c1f8bc2f5e 100644 --- a/.github/actions/beachball-release/action.yml +++ b/.github/actions/beachball-release/action.yml @@ -24,18 +24,18 @@ inputs: default: 'šŸ“¦ Bump package versions' outputs: - has-changeset: + has-changes: description: 'Whether change files were found' value: ${{ steps.check-changes.outputs.has_changes }} published: description: 'Whether version bump PR was created' - value: ${{ steps.create-pr.outputs.pr_created || steps.no-pr.outputs.pr_created || 'false' }} + value: ${{ steps.create-pr.outputs.pr_created || 'no' }} pr-number: description: 'The PR number if created' - value: ${{ steps.create-pr.outputs.pr_number || steps.no-pr.outputs.pr_number }} + value: ${{ steps.create-pr.outputs.pr_number }} pr-url: description: 'The PR URL if created' - value: ${{ steps.create-pr.outputs.pr_url || steps.no-pr.outputs.pr_url }} + value: ${{ steps.create-pr.outputs.pr_url }} runs: using: composite @@ -52,15 +52,15 @@ runs: run: | # Use beachball's built-in check command if npx beachball check --verbose; then - echo "has_changes=true" >> $GITHUB_OUTPUT + echo "has_changes=yes" >> $GITHUB_OUTPUT echo "āœ… Change files detected" else - echo "has_changes=false" >> $GITHUB_OUTPUT + echo "has_changes=no" >> $GITHUB_OUTPUT echo "ā„¹ļø No change files found" fi - name: Create version bump branch - if: steps.check-changes.outputs.has_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'yes' id: create-branch shell: bash run: | @@ -70,27 +70,14 @@ runs: echo "šŸ“ Created branch: $BRANCH_NAME" - name: Run beachball bump - if: steps.check-changes.outputs.has_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'yes' shell: bash run: | echo "šŸ”„ Running beachball bump..." npx beachball bump --verbose - - name: Check for version changes - if: steps.check-changes.outputs.has_changes == 'true' - id: check-version-changes - shell: bash - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "has_version_changes=true" >> $GITHUB_OUTPUT - echo "āœ… Version changes detected" - else - echo "has_version_changes=false" >> $GITHUB_OUTPUT - echo "āš ļø No version changes after bump" - fi - - name: Commit version bumps - if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'yes' shell: bash run: | git add . @@ -98,14 +85,14 @@ runs: echo "āœ… Committed version changes" - name: Push branch - if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'yes' shell: bash run: | git push origin "${{ steps.create-branch.outputs.branch_name }}" echo "āœ… Pushed branch to origin" - name: Create Pull Request - if: steps.check-changes.outputs.has_changes == 'true' && steps.check-version-changes.outputs.has_version_changes == 'true' + if: steps.check-changes.outputs.has_changes == 'yes' id: create-pr shell: bash env: @@ -141,22 +128,8 @@ runs: PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') - echo "pr_created=true" >> $GITHUB_OUTPUT + echo "pr_created=yes" >> $GITHUB_OUTPUT echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT echo "āœ… Created PR: $PR_URL" - - - name: Set no-PR outputs - if: steps.check-changes.outputs.has_changes != 'true' || steps.check-version-changes.outputs.has_version_changes != 'true' - id: no-pr - shell: bash - run: | - echo "pr_created=false" >> $GITHUB_OUTPUT - echo "pr_number=" >> $GITHUB_OUTPUT - echo "pr_url=" >> $GITHUB_OUTPUT - if [ "${{ steps.check-changes.outputs.has_changes }}" != "true" ]; then - echo "ā„¹ļø No action taken - no change files found" - else - echo "ā„¹ļø No action taken - no version changes after bump" - fi diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml index 41de5c64a5..7902dbfd62 100644 --- a/.github/workflows/create-version-bump-pr.yml +++ b/.github/workflows/create-version-bump-pr.yml @@ -43,7 +43,7 @@ jobs: pr-title: 'chore(release): bump package versions' - name: Output PR info - if: steps.beachball.outputs.published == 'true' + if: steps.beachball.outputs.published == 'yes' run: | echo "āœ… Version bump PR created!" echo "PR Number: ${{ steps.beachball.outputs.pr-number }}" From 022a86248c3654f9153e1b41fa8a8b7005d73ce9 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 16:07:51 -0800 Subject: [PATCH 3/9] f --- .github/actions/beachball-release/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml index c1f8bc2f5e..ecfe519a2d 100644 --- a/.github/actions/beachball-release/action.yml +++ b/.github/actions/beachball-release/action.yml @@ -120,9 +120,9 @@ runs: ### What to do next: 1. Review the version changes 2. Merge this PR when ready - 3. The merge will trigger tag creation and NPM publishing + 3. Azure Pipelines will automatically publish to NPM - **Note:** Once merged, tags will be created automatically and packages will be published to NPM. + **Note:** Once merged, Azure Pipelines will detect the version changes and publish to NPM. EOF ) From 23216ebe967280ddbdf42064dfdfe1a617028ebb Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 16:08:22 -0800 Subject: [PATCH 4/9] ci(release): refactor Azure Pipelines to use new workflow --- .ado/azure-pipelines.publish.yml | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/.ado/azure-pipelines.publish.yml b/.ado/azure-pipelines.publish.yml index c3905c9211..309f9552cd 100644 --- a/.ado/azure-pipelines.publish.yml +++ b/.ado/azure-pipelines.publish.yml @@ -71,13 +71,7 @@ extends: - template: .ado/templates/setup-repo.yml@self - script: | - git config user.name "UI-Fabric-RN-Bot" - git config user.email "uifrnbot@microsoft.com" - git remote set-url origin https://$(githubUser):$(githubPAT)@github.com/microsoft/fluentui-react-native.git - displayName: Git Authentication - - - script: | - yarn + yarn install --immutable displayName: 'yarn install' - script: | @@ -90,20 +84,11 @@ extends: condition: ${{ parameters.skipNpmPublish }} - script: | - echo ##vso[task.setvariable variable=SkipGitPushPublishArgs]--no-push - displayName: Enable No-Publish (git) - condition: ${{ parameters.skipGitPush }} - - - script: | - yarn publish:beachball $(SkipNpmPublishArgs) $(SkipGitPushPublishArgs) --access public --token $(npmAuth) -b origin/main -y + # Use --no-bump and --no-push because versions have already been committed by Github Actions earlier + npx beachball publish --no-bump --no-push $(SkipNpmPublishArgs) --access public --token $(npmAuth) -y --verbose displayName: 'Publish NPM Packages (for main branch)' condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - - script: | - yarn publish:beachball $(SkipNpmPublishArgs) $(SkipGitPushPublishArgs) --access public --token $(npmAuth) -y -t v${{ replace(variables['Build.SourceBranch'],'refs/heads/releases/','') }} -b origin/${{ replace(variables['Build.SourceBranch'],'refs/heads/','') }} --prerelease-prefix ${{ replace(variables['Build.SourceBranch'],'refs/heads/releases/','') }} - displayName: 'Publish NPM Packages (for other release branches)' - condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/main')) - - template: .ado/templates/win32-nuget-publish.yml@self parameters: skipNugetPublish: ${{ parameters.skipNugetPublish }} From 0bc30d0b50194a18ec544aa0bc2428660e13c96d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 16:49:21 -0800 Subject: [PATCH 5/9] more updates --- .ado/azure-pipelines.publish.yml | 5 - .github/actions/beachball-release/action.yml | 114 +++++++++++++------ .github/workflows/create-version-bump-pr.yml | 4 + 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/.ado/azure-pipelines.publish.yml b/.ado/azure-pipelines.publish.yml index 309f9552cd..2ff368e919 100644 --- a/.ado/azure-pipelines.publish.yml +++ b/.ado/azure-pipelines.publish.yml @@ -5,7 +5,6 @@ trigger: branches: include: - main - - releases/* pr: none @@ -14,10 +13,6 @@ parameters: displayName: Skip Npm Publish type: boolean default: false - - name: skipGitPush - displayName: Skip Git Push - type: boolean - default: false - name: skipNugetPublish displayName: Skip Nuget Publish type: boolean diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml index ecfe519a2d..ebea9064da 100644 --- a/.github/actions/beachball-release/action.yml +++ b/.github/actions/beachball-release/action.yml @@ -10,10 +10,6 @@ inputs: github-token: description: 'GitHub token for creating PRs' required: true - branch-prefix: - description: 'Prefix for the version bump branch name' - required: false - default: 'beachball/version-bump' commit-message: description: 'Commit message for version bumps' required: false @@ -59,15 +55,45 @@ runs: echo "ā„¹ļø No change files found" fi - - name: Create version bump branch + - name: Check for existing version bump PR if: steps.check-changes.outputs.has_changes == 'yes' - id: create-branch + id: check-pr shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} run: | - BRANCH_NAME="${{ inputs.branch-prefix }}-$(date +%s)" + # Use fixed branch name (like changesets does) + BRANCH_NAME="beachball/version-bump/main" echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - git switch -c "$BRANCH_NAME" - echo "šŸ“ Created branch: $BRANCH_NAME" + + # Check if PR already exists from this branch + EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number' || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "existing_pr=$EXISTING_PR" >> $GITHUB_OUTPUT + echo "šŸ“ Found existing PR #$EXISTING_PR, will update it" + else + echo "existing_pr=" >> $GITHUB_OUTPUT + echo "šŸ“ No existing PR found, will create new one" + fi + + - name: Create or update version bump branch + if: steps.check-changes.outputs.has_changes == 'yes' + id: create-branch + shell: bash + run: | + BRANCH_NAME="${{ steps.check-pr.outputs.branch_name }}" + + # Check if branch exists remotely + if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then + echo "šŸ”„ Branch exists, updating it" + git fetch origin "$BRANCH_NAME" + git switch "$BRANCH_NAME" + git reset --hard origin/main + else + echo "šŸ“ Creating new branch: $BRANCH_NAME" + git switch -c "$BRANCH_NAME" + fi - name: Run beachball bump if: steps.check-changes.outputs.has_changes == 'yes' @@ -81,6 +107,14 @@ runs: shell: bash run: | git add . + + # Check if there are any changes to commit + if git diff --staged --quiet; then + echo "āš ļø No changes after beachball bump, skipping commit" + echo "This likely means change files were invalid or already processed" + exit 0 + fi + git commit -m "${{ inputs.commit-message }}" echo "āœ… Committed version changes" @@ -88,48 +122,62 @@ runs: if: steps.check-changes.outputs.has_changes == 'yes' shell: bash run: | - git push origin "${{ steps.create-branch.outputs.branch_name }}" + # Force push since we might be updating an existing branch + git push --force-with-lease origin "${{ steps.check-pr.outputs.branch_name }}" echo "āœ… Pushed branch to origin" - - name: Create Pull Request + - name: Create or Update Pull Request if: steps.check-changes.outputs.has_changes == 'yes' id: create-pr shell: bash env: GH_TOKEN: ${{ inputs.github-token }} run: | - # Extract changed packages for PR body - CHANGED_PACKAGES=$(git diff HEAD~1 --name-only | grep 'package.json' | sort | uniq || echo "See commit for details") - - PR_URL=$(gh pr create \ - --title "${{ inputs.pr-title }}" \ - --body-file - \ - --base main \ - --head "${{ steps.create-branch.outputs.branch_name }}" \ - --label "automated" \ - --label "version-bump" <> $GITHUB_OUTPUT - echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + echo "pr_created=yes" >> $GITHUB_OUTPUT + echo "pr_number=$EXISTING_PR" >> $GITHUB_OUTPUT + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT - echo "āœ… Created PR: $PR_URL" + echo "āœ… Updated PR #$EXISTING_PR: $PR_URL" + else + # Create new PR + PR_URL=$(echo "$PR_BODY" | gh pr create \ + --title "${{ inputs.pr-title }}" \ + --body-file - \ + --base main \ + --head "${{ steps.check-pr.outputs.branch_name }}" \ + --label "automated" \ + --label "version-bump") + + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + + echo "pr_created=yes" >> $GITHUB_OUTPUT + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + + echo "āœ… Created PR #$PR_NUMBER: $PR_URL" + fi diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml index 7902dbfd62..3649a9b9e7 100644 --- a/.github/workflows/create-version-bump-pr.yml +++ b/.github/workflows/create-version-bump-pr.yml @@ -12,6 +12,10 @@ permissions: contents: write pull-requests: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: version-bump: runs-on: ubuntu-latest From ec265d995512e99757314c04b94a5e97da00291c Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 17:29:39 -0800 Subject: [PATCH 6/9] Add dry run, trigger on PR --- .github/actions/beachball-release/action.yml | 18 ++++++++++++++++-- .github/workflows/create-version-bump-pr.yml | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml index ebea9064da..05da53df5c 100644 --- a/.github/actions/beachball-release/action.yml +++ b/.github/actions/beachball-release/action.yml @@ -18,6 +18,10 @@ inputs: description: 'Title for the version bump PR' required: false default: 'šŸ“¦ Bump package versions' + dry-run: + description: 'Run in dry-run mode (skip push and PR creation)' + required: false + default: 'no' outputs: has-changes: @@ -119,7 +123,7 @@ runs: echo "āœ… Committed version changes" - name: Push branch - if: steps.check-changes.outputs.has_changes == 'yes' + if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'no' shell: bash run: | # Force push since we might be updating an existing branch @@ -127,7 +131,7 @@ runs: echo "āœ… Pushed branch to origin" - name: Create or Update Pull Request - if: steps.check-changes.outputs.has_changes == 'yes' + if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'no' id: create-pr shell: bash env: @@ -181,3 +185,13 @@ runs: echo "āœ… Created PR #$PR_NUMBER: $PR_URL" fi + + - name: Dry-run summary + if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'yes' + shell: bash + run: | + echo "šŸ” Dry-run mode: Skipping push and PR creation" + echo "āœ… Version bump validation successful" + echo "" + echo "Changes that would be committed:" + git diff --stat HEAD diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml index 3649a9b9e7..0a2a145dbf 100644 --- a/.github/workflows/create-version-bump-pr.yml +++ b/.github/workflows/create-version-bump-pr.yml @@ -6,7 +6,16 @@ on: - main paths: - 'change/**' + pull_request: + paths: + - '.github/workflows/create-version-bump-pr.yml' + - '.github/actions/beachball-release/**' workflow_dispatch: + inputs: + dry-run: + description: 'Run in dry-run mode (skip push and PR creation)' + type: boolean + default: false permissions: contents: write @@ -45,6 +54,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore(release): bump package versions [skip ci]' pr-title: 'chore(release): bump package versions' + dry-run: ${{ (github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.dry-run)) && 'yes' || 'no' }} - name: Output PR info if: steps.beachball.outputs.published == 'yes' @@ -52,3 +62,9 @@ jobs: echo "āœ… Version bump PR created!" echo "PR Number: ${{ steps.beachball.outputs.pr-number }}" echo "PR URL: ${{ steps.beachball.outputs.pr-url }}" + + - name: Dry-run info + if: (github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.dry-run)) + run: | + echo "šŸ” Workflow ran in dry-run mode" + echo "āœ… Validation successful - no PRs were created" From 8509da7a8a607db3269006f53469431cd1338bdf Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Wed, 21 Jan 2026 17:32:09 -0800 Subject: [PATCH 7/9] don't run on pull_request --- .github/workflows/create-version-bump-pr.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml index 0a2a145dbf..7bf28ce897 100644 --- a/.github/workflows/create-version-bump-pr.yml +++ b/.github/workflows/create-version-bump-pr.yml @@ -6,10 +6,6 @@ on: - main paths: - 'change/**' - pull_request: - paths: - - '.github/workflows/create-version-bump-pr.yml' - - '.github/actions/beachball-release/**' workflow_dispatch: inputs: dry-run: From c90c74a41b25bf6eff22edf864602559f11cb0f6 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Thu, 22 Jan 2026 15:15:49 -0800 Subject: [PATCH 8/9] WIP --- .ado/azure-pipelines.publish.yml | 5 + .ado/templates/setup-repo.yml | 4 +- .github/workflows/create-version-bump-pr.yml | 2 +- .github/workflows/validate-changefiles.yml | 69 ++++++++ .node-version | 1 - CONTRIBUTING.md | 15 ++ scripts/check-packages-need-publishing.ts | 167 +++++++++++++++++++ scripts/test-package-check.sh | 76 +++++++++ 8 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/validate-changefiles.yml delete mode 100644 .node-version create mode 100755 scripts/check-packages-need-publishing.ts create mode 100755 scripts/test-package-check.sh diff --git a/.ado/azure-pipelines.publish.yml b/.ado/azure-pipelines.publish.yml index 2ff368e919..da52e93911 100644 --- a/.ado/azure-pipelines.publish.yml +++ b/.ado/azure-pipelines.publish.yml @@ -78,6 +78,11 @@ extends: displayName: Enable No-Publish (npm) condition: ${{ parameters.skipNpmPublish }} + - script: | + node scripts/check-packages-need-publishing.ts + displayName: 'Validate packages need publishing' + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), ne('${{ parameters.skipNpmPublish }}', true)) + - script: | # Use --no-bump and --no-push because versions have already been committed by Github Actions earlier npx beachball publish --no-bump --no-push $(SkipNpmPublishArgs) --access public --token $(npmAuth) -y --verbose diff --git a/.ado/templates/setup-repo.yml b/.ado/templates/setup-repo.yml index 171dd961ec..9b8ca92db2 100644 --- a/.ado/templates/setup-repo.yml +++ b/.ado/templates/setup-repo.yml @@ -7,8 +7,8 @@ steps: - task: UseNode@1 inputs: - version: '22.x' - displayName: 'Use Node.js 22.x' + version: '24' + displayName: 'Use Node.js 24' - script: | yarn diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml index 7bf28ce897..2fc663615b 100644 --- a/.github/workflows/create-version-bump-pr.yml +++ b/.github/workflows/create-version-bump-pr.yml @@ -37,7 +37,7 @@ jobs: platform: node project-root: . cache-key-prefix: version-bump - node-version: '22.12' + node-version: '24' cache-npm-dependencies: yarn - name: Install dependencies diff --git a/.github/workflows/validate-changefiles.yml b/.github/workflows/validate-changefiles.yml new file mode 100644 index 0000000000..6f8487c810 --- /dev/null +++ b/.github/workflows/validate-changefiles.yml @@ -0,0 +1,69 @@ +name: Validate Change Files + +on: + pull_request: + paths: + - 'change/**' + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup the toolchain + uses: microsoft/react-native-test-app/.github/actions/setup-toolchain@4.4.5 + with: + platform: node + project-root: . + cache-key-prefix: validate-changefiles + node-version: '24' + cache-npm-dependencies: yarn + + - name: Install dependencies + run: yarn install --immutable + + - name: Check change files are valid + run: npx beachball check --verbose + + - name: Preview packages that would be published + run: | + echo "## šŸ“¦ Packages that would be published after this PR merges" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Run the check script in dry-run mode and capture output + # Node 24 has native TypeScript support + if node scripts/check-packages-need-publishing.ts --dry-run; then + echo "āœ… Package validation successful" >> $GITHUB_STEP_SUMMARY + else + echo "āŒ Package validation failed" >> $GITHUB_STEP_SUMMARY + fi + + - name: Run beachball bump (dry-run) + run: | + echo "šŸ”„ Running beachball bump to preview version changes..." + npx beachball bump --verbose + + - name: Preview NPM publish (dry-run) + run: | + echo "## šŸš€ NPM Publish Preview" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Running \`beachball publish\` in dry-run mode (--no-publish)..." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + # Run beachball publish with --no-publish flag (dry-run) + # This does everything except actually publish to NPM + npx beachball publish --no-bump --no-push --no-publish --access public -y --verbose 2>&1 | tee /tmp/publish-preview.log + + # Add relevant output to summary + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "āœ… Dry-run completed successfully" >> $GITHUB_STEP_SUMMARY diff --git a/.node-version b/.node-version deleted file mode 100644 index 8fdd954df9..0000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -22 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47598bb0d3..84e66013c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -236,6 +236,21 @@ This repo manages semantic versioning and publishing using [Beachball](https://g 1. `yarn change` will take you through a command line wizard to generate change files 2. Make sure to push the newly generated change file +#### Publishing Workflow + +The repository uses an automated publishing workflow: + +1. **Change Files**: Contributors create change files using `yarn change` in their PRs +2. **Version Bump PR**: When change files are merged to `main`, GitHub Actions automatically creates/updates a version bump PR + - This PR is updated automatically as more changes are merged + - The PR shows all packages that will be published and their new versions +3. **Review and Merge**: Maintainers review the version bump PR and merge when ready +4. **Automatic Publishing**: After the version bump PR is merged, Azure Pipelines automatically publishes packages to NPM + +**Branch Support**: Only the `main` branch is configured for automatic publishing. Release branches are not supported in this workflow. + +**For Maintainers**: The version bump PR is created by GitHub Actions using a fixed branch name (`beachball/version-bump/main`). This PR will be automatically updated as new change files are merged to main. Do not manually close or recreate this PR unless necessary. + #### Testing changes Before you create a pull request, test your changes with the FluentUI Tester on the platforms that are affected by your change. For more information on the FluentUI Tester, please follow instructions in the [FluentUI Tester readme](./apps/fluent-tester/README.md). diff --git a/scripts/check-packages-need-publishing.ts b/scripts/check-packages-need-publishing.ts new file mode 100755 index 0000000000..21fcbbc623 --- /dev/null +++ b/scripts/check-packages-need-publishing.ts @@ -0,0 +1,167 @@ +#!/usr/bin/env node + +/** + * Check which packages need publishing to NPM + * + * Scans all packages in the monorepo and checks if their current versions + * exist on NPM. Fails if no packages need publishing (all versions already exist). + * + * Exit codes: + * 0 - Success, packages need publishing + * 1 - Error, no packages need publishing or script failed + */ + +import { execSync, type ExecException } from 'child_process'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +interface PackageJson { + name: string; + version: string; + private?: boolean; +} + +/** + * Check if a specific package version exists on NPM + */ +function checkPackageOnNpm(packageName: string, version: string): boolean { + try { + execSync(`npm view ${packageName}@${version} version`, { + stdio: 'pipe', + encoding: 'utf8', + }); + return true; // Package exists on NPM + } catch (error) { + // npm view exits with code 1 when package doesn't exist (404) + // Check if this is a "not found" error vs a real error (network, etc) + const execError = error as ExecException; + const stderr = execError.stderr?.toString() || ''; + if (stderr.includes('404') || stderr.includes('Not Found')) { + return false; // Package doesn't exist on NPM + } + // For other errors (network issues, npm down, etc), throw so we don't incorrectly + // report that packages need publishing when we can't actually check NPM + throw error; + } +} + +/** + * Get all workspace packages using yarn workspaces list + */ +function getWorkspacePackages(): string[] { + try { + const output = execSync('yarn workspaces list --json', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + + const workspaces: string[] = []; + // Each line is a JSON object + for (const line of output.trim().split('\n')) { + const workspace = JSON.parse(line); + // Skip the root workspace (location is '.') + if (workspace.location && workspace.location !== '.') { + workspaces.push(join(process.cwd(), workspace.location, 'package.json')); + } + } + + return workspaces; + } catch (error) { + console.error('āŒ ERROR: Failed to get yarn workspaces'); + console.error((error as Error).message); + process.exit(1); + } +} + +/** + * Main function that checks all packages in the monorepo + */ +function main(dryRun = false): void { + console.log('šŸ” Checking which packages need publishing...\n'); + + const packagesToPublish: string[] = []; + const packagesAlreadyPublished: string[] = []; + const packagesSkipped: string[] = []; + + const packageJsonPaths = getWorkspacePackages(); + + for (const packageJsonPath of packageJsonPaths) { + + let packageJson: PackageJson; + try { + const content = readFileSync(packageJsonPath, 'utf8'); + packageJson = JSON.parse(content); + } catch (error) { + console.error(`āš ļø Failed to read ${packageJsonPath}:`, (error as Error).message); + continue; + } + + const { name, version, private: isPrivate } = packageJson; + + if (!name || !version) { + console.log(`ā­ļø Skipping ${packageJsonPath}: missing name or version`); + packagesSkipped.push(packageJsonPath); + continue; + } + + if (isPrivate) { + console.log(`ā­ļø Skipping private package: ${name}@${version}`); + packagesSkipped.push(`${name}@${version}`); + continue; + } + + const existsOnNpm = checkPackageOnNpm(name, version); + + if (existsOnNpm) { + console.log(`āœ… Already published: ${name}@${version}`); + packagesAlreadyPublished.push(`${name}@${version}`); + } else { + console.log(`šŸ“¦ Will publish: ${name}@${version}`); + packagesToPublish.push(`${name}@${version}`); + } + } + + // Print summary + console.log('\n' + '='.repeat(60)); + console.log('Summary:'); + console.log(` Packages to publish: ${packagesToPublish.length}`); + console.log(` Already on NPM: ${packagesAlreadyPublished.length}`); + console.log(` Skipped: ${packagesSkipped.length}`); + console.log('='.repeat(60)); + + // Print packages to publish if any + if (dryRun && packagesToPublish.length > 0) { + console.log('\nPackages that will be published:'); + packagesToPublish.forEach(pkg => console.log(` - ${pkg}`)); + } + + // Fail if nothing to publish (unless dry-run) + if (packagesToPublish.length === 0) { + if (dryRun) { + console.log('\nāœ… Dry-run: No packages would be published'); + console.log('All package versions already exist on NPM.'); + process.exit(0); + } else { + console.log('\nāŒ ERROR: No packages need publishing!'); + console.log('All package versions already exist on NPM.\n'); + console.log('This likely means:'); + console.log(' 1. The version bump PR was merged without actually bumping versions'); + console.log(' 2. Packages were already published manually'); + console.log(' 3. The version bump workflow didn\'t run correctly'); + process.exit(1); + } + } + + if (dryRun) { + console.log(`\nāœ… Dry-run: ${packagesToPublish.length} package(s) would be published`); + } else { + console.log(`\nāœ… Ready to publish ${packagesToPublish.length} package(s)`); + } + process.exit(0); +} + +// Parse CLI args +const args = process.argv.slice(2); +const dryRun = args.includes('--dry-run'); + +main(dryRun); diff --git a/scripts/test-package-check.sh b/scripts/test-package-check.sh new file mode 100755 index 0000000000..3d99776c5c --- /dev/null +++ b/scripts/test-package-check.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Test script to compare beachball bump output with check-packages-need-publishing.ts + +set -e + +# Activate mise if available +if [ -f "$HOME/.local/bin/mise" ]; then + eval "$("$HOME/.local/bin/mise" activate bash)" +fi + +echo "==========================================" +echo "Testing package check script" +echo "==========================================" +echo "" + +# Step 1: Run beachball bump in dry-run mode to see what it would change +echo "Step 1: Running beachball bump (dry-run)..." +echo "==========================================" + +# Create a temporary branch to test +TEMP_BRANCH="test-package-check-$(date +%s)" +git switch -c "$TEMP_BRANCH" 2>/dev/null || git checkout -b "$TEMP_BRANCH" + +# Run beachball bump and capture the output +echo "" +echo "Running: npx beachball bump --verbose" +mise exec -- npx beachball bump --verbose 2>&1 | tee /tmp/beachball-bump-output.txt + +echo "" +echo "==========================================" +echo "Step 2: Extracting packages bumped by beachball..." +echo "==========================================" + +# Get list of modified package.json files +BUMPED_PACKAGES=$(git diff --name-only | grep "package.json" | grep -v "^package.json$" || true) + +if [ -z "$BUMPED_PACKAGES" ]; then + echo "āš ļø No packages were bumped by beachball" + echo "This might mean there are no change files or they've already been processed" +else + echo "Beachball bumped the following package.json files:" + echo "$BUMPED_PACKAGES" + echo "" + + # Extract package names and versions + echo "Package versions after bump:" + for pkg in $BUMPED_PACKAGES; do + if [ -f "$pkg" ]; then + NAME=$(grep '"name"' "$pkg" | head -1 | sed 's/.*"name": "\(.*\)".*/\1/') + VERSION=$(grep '"version"' "$pkg" | head -1 | sed 's/.*"version": "\(.*\)".*/\1/') + PRIVATE=$(grep '"private"' "$pkg" | head -1 || echo "") + + if [ -z "$PRIVATE" ]; then + echo " - $NAME@$VERSION" + fi + fi + done +fi + +echo "" +echo "==========================================" +echo "Step 3: Running check-packages-need-publishing.ts..." +echo "==========================================" +echo "" + +mise exec -- node scripts/check-packages-need-publishing.ts --dry-run + +echo "" +echo "==========================================" +echo "Comparison complete!" +echo "==========================================" +echo "" +echo "To clean up:" +echo " git checkout main" +echo " git branch -D $TEMP_BRANCH" From 78fd61e87acb13833af5d40c84a29a527c035340 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Fri, 23 Jan 2026 13:59:34 -0800 Subject: [PATCH 9/9] redo as Azure Pipelines --- .ado/azure-pipelines.publish.yml | 227 ++++++++++++++++++- .ado/azure-pipelines.yml | 52 +++++ .github/actions/beachball-release/README.md | 205 ----------------- .github/actions/beachball-release/action.yml | 197 ---------------- .github/workflows/create-version-bump-pr.yml | 66 ------ .github/workflows/validate-changefiles.yml | 69 ------ CONTRIBUTING.md | 9 +- 7 files changed, 277 insertions(+), 548 deletions(-) delete mode 100644 .github/actions/beachball-release/README.md delete mode 100644 .github/actions/beachball-release/action.yml delete mode 100644 .github/workflows/create-version-bump-pr.yml delete mode 100644 .github/workflows/validate-changefiles.yml diff --git a/.ado/azure-pipelines.publish.yml b/.ado/azure-pipelines.publish.yml index da52e93911..93fc0341fb 100644 --- a/.ado/azure-pipelines.publish.yml +++ b/.ado/azure-pipelines.publish.yml @@ -1,4 +1,6 @@ -# Build pipeline for publishing +# Build and publish pipeline +# This pipeline runs on every commit to main and intelligently detects +# when packages need to be published to NPM trigger: batch: true @@ -23,6 +25,8 @@ variables: - group: InfoSec-SecurityResults - name: tags value: production,externalfacing + - name: BRANCH_NAME + value: 'beachball/version-bump/main' - template: variables/vars.yml resources: @@ -49,7 +53,205 @@ extends: environmentsEs6: true environmentsNode: true stages: + # Stage 1: Create version bump PR if change files exist + - stage: VersionBump + displayName: 'Create Version Bump PR' + jobs: + - job: CreatePR + displayName: 'Create or Update Version Bump PR' + pool: + name: Azure-Pipelines-1ESPT-ExDShared + image: ubuntu-latest + os: linux + steps: + - checkout: self + fetchDepth: 0 + persistCredentials: true + + - template: .ado/templates/setup-repo.yml@self + + - script: | + set -eox pipefail + yarn install --immutable + displayName: 'Install dependencies' + + # Check if there are change files + - script: | + set -eox pipefail + if npx beachball check --verbose; then + echo "##vso[task.setvariable variable=HasChangeFiles;isOutput=true]yes" + echo "āœ… Change files detected" + else + echo "##vso[task.setvariable variable=HasChangeFiles;isOutput=true]no" + echo "ā„¹ļø No change files found" + fi + name: CheckChanges + displayName: 'Check for change files' + + # Configure Git + - script: | + set -eox pipefail + git config user.name "React Native Bot" + git config user.email "53619745+rnbot@users.noreply.github.com" + displayName: 'Configure Git' + condition: eq(variables['CheckChanges.HasChangeFiles'], 'yes') + + # Check for existing PR + - task: AzureCLI@2 + displayName: 'Check for existing PR' + condition: eq(variables['CheckChanges.HasChangeFiles'], 'yes') + inputs: + azureSubscription: 'FluentUI React Native' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + set -eox pipefail + # Get GitHub token from Key Vault + GITHUB_TOKEN=$(az keyvault secret show --vault-name fluentui-rn-keyvault --name github-token --query value -o tsv) + export GH_TOKEN=$GITHUB_TOKEN + + # Check if PR already exists + EXISTING_PR=$(gh pr list --head "$(BRANCH_NAME)" --state open --json number --jq '.[0].number' || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "##vso[task.setvariable variable=ExistingPR;isOutput=true]$EXISTING_PR" + echo "šŸ“ Found existing PR #$EXISTING_PR, will update it" + else + echo "##vso[task.setvariable variable=ExistingPR;isOutput=true]" + echo "šŸ“ No existing PR found, will create new one" + fi + name: CheckPR + + # Create or update branch + - script: | + set -eox pipefail + # Check if branch exists remotely + if git ls-remote --heads origin "$(BRANCH_NAME)" | grep -q "$(BRANCH_NAME)"; then + echo "šŸ”„ Branch exists, updating it" + git fetch origin "$(BRANCH_NAME)" + git switch "$(BRANCH_NAME)" + git reset --hard origin/main + else + echo "šŸ“ Creating new branch: $(BRANCH_NAME)" + git switch -c "$(BRANCH_NAME)" + fi + displayName: 'Create or update version bump branch' + condition: eq(variables['CheckChanges.HasChangeFiles'], 'yes') + + # Run beachball bump + - script: | + set -eox pipefail + echo "šŸ”„ Running beachball bump..." + npx beachball bump --verbose + displayName: 'Run beachball bump' + condition: eq(variables['CheckChanges.HasChangeFiles'], 'yes') + + # Commit changes + - script: | + set -eox pipefail + git add . + + # Check if there are any changes to commit + if git diff --staged --quiet; then + echo "āš ļø No changes after beachball bump, skipping commit" + echo "##vso[task.setvariable variable=HasCommit;isOutput=true]no" + exit 0 + fi + + git commit -m "chore(release): bump package versions [skip ci]" + echo "##vso[task.setvariable variable=HasCommit;isOutput=true]yes" + echo "āœ… Committed version changes" + name: CommitChanges + displayName: 'Commit version bumps' + condition: eq(variables['CheckChanges.HasChangeFiles'], 'yes') + + # Push branch + - task: AzureCLI@2 + displayName: 'Push version bump branch' + condition: and(succeeded(), eq(variables['CheckChanges.HasChangeFiles'], 'yes'), eq(variables['CommitChanges.HasCommit'], 'yes')) + inputs: + azureSubscription: 'FluentUI React Native' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + set -eox pipefail + # Get GitHub token from Key Vault + GITHUB_TOKEN=$(az keyvault secret show --vault-name fluentui-rn-keyvault --name github-token --query value -o tsv) + + # Configure git to use token for authentication + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/microsoft/fluentui-react-native.git" + + # Force push since we might be updating an existing branch + git push --force-with-lease origin "$(BRANCH_NAME)" + echo "āœ… Pushed branch to origin" + + # Create or update PR + - task: AzureCLI@2 + displayName: 'Create or Update Pull Request' + condition: and(succeeded(), eq(variables['CheckChanges.HasChangeFiles'], 'yes'), eq(variables['CommitChanges.HasCommit'], 'yes')) + inputs: + azureSubscription: 'FluentUI React Native' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + set -eox pipefail + # Get GitHub token from Key Vault + GITHUB_TOKEN=$(az keyvault secret show --vault-name fluentui-rn-keyvault --name github-token --query value -o tsv) + export GH_TOKEN=$GITHUB_TOKEN + + # Create PR body + PR_BODY=$(cat <<'EOFBODY' + ## Version Bump + + This PR was automatically generated by Azure Pipelines. + + ### What to do next: + 1. Review the version changes and CHANGELOG.md files + 2. Merge this PR when ready + 3. Azure Pipelines will automatically publish to NPM + + **Note:** Once merged, Azure Pipelines will detect the merge and publish to NPM. + EOFBODY + ) + + EXISTING_PR="$(CheckPR.ExistingPR)" + + if [ -n "$EXISTING_PR" ]; then + # Update existing PR + echo "$PR_BODY" | gh pr edit "$EXISTING_PR" \ + --title "chore(release): bump package versions" \ + --body-file - + + PR_URL=$(gh pr view "$EXISTING_PR" --json url --jq '.url') + + echo "##vso[task.setvariable variable=PRNumber;isOutput=true]$EXISTING_PR" + echo "##vso[task.setvariable variable=PRUrl;isOutput=true]$PR_URL" + + echo "āœ… Updated PR #$EXISTING_PR: $PR_URL" + else + # Create new PR + PR_URL=$(echo "$PR_BODY" | gh pr create \ + --title "chore(release): bump package versions" \ + --body-file - \ + --base main \ + --head "$(BRANCH_NAME)" \ + --label "automated" \ + --label "version-bump") + + PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + + echo "##vso[task.setvariable variable=PRNumber;isOutput=true]$PR_NUMBER" + echo "##vso[task.setvariable variable=PRUrl;isOutput=true]$PR_URL" + + echo "āœ… Created PR #$PR_NUMBER: $PR_URL" + fi + name: CreatePRStep + + # Stage 2: Build and publish packages - stage: main + displayName: 'Build and Publish' + dependsOn: VersionBump + condition: always() jobs: - job: NPMPublish displayName: NPM Publish @@ -66,28 +268,39 @@ extends: - template: .ado/templates/setup-repo.yml@self - script: | + set -eox pipefail yarn install --immutable displayName: 'yarn install' - script: | + set -eox pipefail yarn buildci displayName: 'yarn buildci [test]' - script: | + set -eox pipefail echo ##vso[task.setvariable variable=SkipNpmPublishArgs]--no-publish displayName: Enable No-Publish (npm) condition: ${{ parameters.skipNpmPublish }} - script: | - node scripts/check-packages-need-publishing.ts - displayName: 'Validate packages need publishing' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), ne('${{ parameters.skipNpmPublish }}', true)) + set -eox pipefail + if node scripts/check-packages-need-publishing.ts; then + echo "##vso[task.setvariable variable=PackagesNeedPublishing]true" + echo "āœ… Packages need publishing" + else + echo "##vso[task.setvariable variable=PackagesNeedPublishing]false" + echo "ā„¹ļø No packages need publishing (all versions already exist on NPM)" + fi + displayName: 'Check if packages need publishing' + condition: and(succeeded(), ne('${{ parameters.skipNpmPublish }}', true)) - script: | - # Use --no-bump and --no-push because versions have already been committed by Github Actions earlier + set -eox pipefail + # Use --no-bump and --no-push because versions have already been committed via version bump PR npx beachball publish --no-bump --no-push $(SkipNpmPublishArgs) --access public --token $(npmAuth) -y --verbose - displayName: 'Publish NPM Packages (for main branch)' - condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + displayName: 'Publish NPM Packages' + condition: and(succeeded(), eq(variables['PackagesNeedPublishing'], 'true')) - template: .ado/templates/win32-nuget-publish.yml@self parameters: diff --git a/.ado/azure-pipelines.yml b/.ado/azure-pipelines.yml index b65685c253..f00cf873df 100644 --- a/.ado/azure-pipelines.yml +++ b/.ado/azure-pipelines.yml @@ -44,6 +44,58 @@ jobs: yarn check-for-changed-files displayName: 'verify API and Ensure Changed Files' + # Dedicated job to preview version bumps and package publishing + - job: NPMPublishDryRun + displayName: NPM Publish Dry Run + pool: + vmImage: 'ubuntu-latest' + timeoutInMinutes: 30 + cancelTimeoutInMinutes: 5 + + steps: + - checkout: self + persistCredentials: true + + - template: templates/setup-repo.yml + + - script: | + set -eox pipefail + echo "==========================================" + echo "Running beachball bump (dry-run)..." + echo "==========================================" + npx beachball bump --verbose + + echo "" + echo "==========================================" + echo "Packages that would be bumped:" + echo "==========================================" + + # Show which package.json files were modified + git diff --name-only | grep "package.json" | grep -v "^package.json$" | while read pkg; do + if [ -f "$pkg" ]; then + NAME=$(grep '"name"' "$pkg" | head -1 | sed 's/.*"name": "\(.*\)".*/\1/') + VERSION=$(grep '"version"' "$pkg" | head -1 | sed 's/.*"version": "\(.*\)".*/\1/') + PRIVATE=$(grep '"private"' "$pkg" | head -1 || echo "") + + if [ -z "$PRIVATE" ]; then + echo " šŸ“¦ $NAME@$VERSION" + fi + fi + done + + # Reset the changes so they don't affect other steps + git reset --hard HEAD + displayName: 'Preview version bumps (dry-run)' + + - script: | + set -eox pipefail + echo "" + echo "==========================================" + echo "Checking which packages need publishing..." + echo "==========================================" + node scripts/check-packages-need-publishing.ts --dry-run + displayName: 'Check packages to publish (dry-run)' + - job: AndroidPR displayName: Android PR pool: diff --git a/.github/actions/beachball-release/README.md b/.github/actions/beachball-release/README.md deleted file mode 100644 index 066251107c..0000000000 --- a/.github/actions/beachball-release/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# Beachball Release Action - -A GitHub Action that automatically creates release PRs with version bumps using [Beachball](https://github.com/microsoft/beachball), similar to [changesets/action](https://github.com/changesets/action). - -## Features - -- šŸ” Detects change files in your repository -- šŸ“¦ Runs `beachball bump` to update versions and changelogs -- šŸ”€ Creates a pull request with all version changes -- šŸ·ļø Automatically labels PRs for easy identification -- āœ… Provides outputs for downstream workflows - -## Usage - -### Basic Example - -```yaml -name: Create Version Bump PR - -on: - push: - branches: - - main - paths: - - 'change/**' - -permissions: - contents: write - pull-requests: write - -jobs: - version-bump: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version: '22' - - - run: yarn install --immutable - - - name: Create version bump PR - uses: ./.github/actions/beachball-release - with: - github-token: ${{ secrets.GITHUB_TOKEN }} -``` - -### With Custom Options - -```yaml -- name: Create version bump PR - id: beachball - uses: ./.github/actions/beachball-release - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - branch-prefix: 'release/version-bump' - commit-message: 'chore: bump package versions' - pr-title: 'šŸš€ Release: Version Bump' - -- name: Comment on PR - if: steps.beachball.outputs.published == 'yes' - run: | - echo "Created PR #${{ steps.beachball.outputs.pr-number }}" - echo "URL: ${{ steps.beachball.outputs.pr-url }}" -``` - -## Inputs - -| Input | Description | Required | Default | -| ---------------- | --------------------------------------- | -------- | ------------------------------------ | -| `github-token` | GitHub token for creating PRs | Yes | - | -| `branch-prefix` | Prefix for the version bump branch name | No | `beachball/version-bump` | -| `commit-message` | Commit message for version bumps | No | `šŸ“¦ Bump package versions [skip ci]` | -| `pr-title` | Title for the version bump PR | No | `šŸ“¦ Bump package versions` | - -## Outputs - -| Output | Description | -| ------------- | ---------------------------------------------------- | -| `has-changes` | Whether change files were found (`yes`/`no`) | -| `published` | Whether a version bump PR was created (`yes`/`no`) | -| `pr-number` | The PR number if created | -| `pr-url` | The PR URL if created | - -## How It Works - -1. **Checks for change files** - Uses `beachball check` to detect if there are any change files -2. **Creates a branch** - Creates a new branch with timestamp (e.g., `beachball/version-bump-1234567890`) -3. **Runs beachball bump** - Executes `beachball bump` to update package versions and CHANGELOGs -4. **Commits changes** - Commits all version changes with a descriptive message -5. **Creates PR** - Opens a pull request with the version changes, labeled as `version-bump` and `automated` - -## Requirements - -### Repository Setup - -1. **Workflow Permissions** - - - Go to Settings → Actions → General → Workflow permissions - - Enable "Read and write permissions" - - Enable "Allow GitHub Actions to create and approve pull requests" - -2. **Beachball Configuration** - - - Repository must have a `beachball.config.js` file - - Must be using beachball for version management - -3. **Change Files** - - Change files should be in the `change/` directory - - Created using `beachball change` command - -## Comparison with Changesets Action - -| Feature | Beachball Action | Changesets Action | -| ------------------ | ----------------- | ----------------- | -| Version bumping | āœ… Beachball | āœ… Changesets | -| Auto PR creation | āœ… Yes | āœ… Yes | -| Publishing | Separate workflow | Built-in optional | -| Monorepo support | āœ… Yes | āœ… Yes | -| Change file format | JSON/Markdown | Markdown | - -## Examples - -### Stage 1: PR Creation Only - -Use this action to create version bump PRs while keeping your existing publish workflow: - -```yaml -name: Version Bump PR - -on: - push: - branches: [main] - paths: ['change/**'] - -permissions: - contents: write - pull-requests: write - -jobs: - version-bump: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - run: npm install - - - uses: ./.github/actions/beachball-release - with: - github-token: ${{ secrets.GITHUB_TOKEN }} -``` - -### Stage 2: Full Workflow with Publishing - -Combine with tag creation and publishing workflows: - -```yaml -# After version bump PR is merged, create tags -on: - pull_request: - types: [closed] - -jobs: - create-tags: - if: github.event.pull_request.merged == true && - contains(github.event.pull_request.labels.*.name, 'version-bump') - # ... tag creation logic -``` - -## Troubleshooting - -### Action fails with "permission denied" - -Ensure workflow permissions are correctly set: - -- Settings → Actions → General → Workflow permissions -- Select "Read and write permissions" - -### No PR is created - -Check the action outputs: - -- `has-changes` should be `yes` -- Look for change files in the `change/` directory -- Run `npx beachball check` locally to verify - -### PR created but no changes - -Verify: - -- Beachball config is correct -- Change files have valid format -- Packages have correct version in package.json - -## License - -MIT - -## Related - -- [Beachball](https://github.com/microsoft/beachball) - The underlying version management tool -- [Changesets Action](https://github.com/changesets/action) - Similar action for Changesets diff --git a/.github/actions/beachball-release/action.yml b/.github/actions/beachball-release/action.yml deleted file mode 100644 index 05da53df5c..0000000000 --- a/.github/actions/beachball-release/action.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: 'Beachball Release' -description: 'Creates a release PR with version bumps using Beachball' -author: 'Microsoft' - -branding: - icon: 'package' - color: 'blue' - -inputs: - github-token: - description: 'GitHub token for creating PRs' - required: true - commit-message: - description: 'Commit message for version bumps' - required: false - default: 'šŸ“¦ Bump package versions [skip ci]' - pr-title: - description: 'Title for the version bump PR' - required: false - default: 'šŸ“¦ Bump package versions' - dry-run: - description: 'Run in dry-run mode (skip push and PR creation)' - required: false - default: 'no' - -outputs: - has-changes: - description: 'Whether change files were found' - value: ${{ steps.check-changes.outputs.has_changes }} - published: - description: 'Whether version bump PR was created' - value: ${{ steps.create-pr.outputs.pr_created || 'no' }} - pr-number: - description: 'The PR number if created' - value: ${{ steps.create-pr.outputs.pr_number }} - pr-url: - description: 'The PR URL if created' - value: ${{ steps.create-pr.outputs.pr_url }} - -runs: - using: composite - steps: - - name: Configure Git - shell: bash - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Check for change files - id: check-changes - shell: bash - run: | - # Use beachball's built-in check command - if npx beachball check --verbose; then - echo "has_changes=yes" >> $GITHUB_OUTPUT - echo "āœ… Change files detected" - else - echo "has_changes=no" >> $GITHUB_OUTPUT - echo "ā„¹ļø No change files found" - fi - - - name: Check for existing version bump PR - if: steps.check-changes.outputs.has_changes == 'yes' - id: check-pr - shell: bash - env: - GH_TOKEN: ${{ inputs.github-token }} - run: | - # Use fixed branch name (like changesets does) - BRANCH_NAME="beachball/version-bump/main" - echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - # Check if PR already exists from this branch - EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number --jq '.[0].number' || echo "") - - if [ -n "$EXISTING_PR" ]; then - echo "existing_pr=$EXISTING_PR" >> $GITHUB_OUTPUT - echo "šŸ“ Found existing PR #$EXISTING_PR, will update it" - else - echo "existing_pr=" >> $GITHUB_OUTPUT - echo "šŸ“ No existing PR found, will create new one" - fi - - - name: Create or update version bump branch - if: steps.check-changes.outputs.has_changes == 'yes' - id: create-branch - shell: bash - run: | - BRANCH_NAME="${{ steps.check-pr.outputs.branch_name }}" - - # Check if branch exists remotely - if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then - echo "šŸ”„ Branch exists, updating it" - git fetch origin "$BRANCH_NAME" - git switch "$BRANCH_NAME" - git reset --hard origin/main - else - echo "šŸ“ Creating new branch: $BRANCH_NAME" - git switch -c "$BRANCH_NAME" - fi - - - name: Run beachball bump - if: steps.check-changes.outputs.has_changes == 'yes' - shell: bash - run: | - echo "šŸ”„ Running beachball bump..." - npx beachball bump --verbose - - - name: Commit version bumps - if: steps.check-changes.outputs.has_changes == 'yes' - shell: bash - run: | - git add . - - # Check if there are any changes to commit - if git diff --staged --quiet; then - echo "āš ļø No changes after beachball bump, skipping commit" - echo "This likely means change files were invalid or already processed" - exit 0 - fi - - git commit -m "${{ inputs.commit-message }}" - echo "āœ… Committed version changes" - - - name: Push branch - if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'no' - shell: bash - run: | - # Force push since we might be updating an existing branch - git push --force-with-lease origin "${{ steps.check-pr.outputs.branch_name }}" - echo "āœ… Pushed branch to origin" - - - name: Create or Update Pull Request - if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'no' - id: create-pr - shell: bash - env: - GH_TOKEN: ${{ inputs.github-token }} - run: | - # Create PR body - PR_BODY=$(cat <<'EOFBODY' - ## Version Bump - - This PR was automatically generated by the Beachball version bump action. - - ### What to do next: - 1. Review the version changes and CHANGELOG.md files - 2. Merge this PR when ready - 3. Azure Pipelines will automatically publish to NPM - - **Note:** Once merged, Azure Pipelines will detect the version changes and publish to NPM. - EOFBODY - ) - - EXISTING_PR="${{ steps.check-pr.outputs.existing_pr }}" - - if [ -n "$EXISTING_PR" ]; then - # Update existing PR - echo "$PR_BODY" | gh pr edit "$EXISTING_PR" \ - --title "${{ inputs.pr-title }}" \ - --body-file - - - PR_URL=$(gh pr view "$EXISTING_PR" --json url --jq '.url') - - echo "pr_created=yes" >> $GITHUB_OUTPUT - echo "pr_number=$EXISTING_PR" >> $GITHUB_OUTPUT - echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT - - echo "āœ… Updated PR #$EXISTING_PR: $PR_URL" - else - # Create new PR - PR_URL=$(echo "$PR_BODY" | gh pr create \ - --title "${{ inputs.pr-title }}" \ - --body-file - \ - --base main \ - --head "${{ steps.check-pr.outputs.branch_name }}" \ - --label "automated" \ - --label "version-bump") - - PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') - - echo "pr_created=yes" >> $GITHUB_OUTPUT - echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT - echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT - - echo "āœ… Created PR #$PR_NUMBER: $PR_URL" - fi - - - name: Dry-run summary - if: steps.check-changes.outputs.has_changes == 'yes' && inputs.dry-run == 'yes' - shell: bash - run: | - echo "šŸ” Dry-run mode: Skipping push and PR creation" - echo "āœ… Version bump validation successful" - echo "" - echo "Changes that would be committed:" - git diff --stat HEAD diff --git a/.github/workflows/create-version-bump-pr.yml b/.github/workflows/create-version-bump-pr.yml deleted file mode 100644 index 2fc663615b..0000000000 --- a/.github/workflows/create-version-bump-pr.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Create Version Bump PR with Beachball - -on: - push: - branches: - - main - paths: - - 'change/**' - workflow_dispatch: - inputs: - dry-run: - description: 'Run in dry-run mode (skip push and PR creation)' - type: boolean - default: false - -permissions: - contents: write - pull-requests: write - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - version-bump: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup the toolchain - uses: microsoft/react-native-test-app/.github/actions/setup-toolchain@4.4.5 - with: - platform: node - project-root: . - cache-key-prefix: version-bump - node-version: '24' - cache-npm-dependencies: yarn - - - name: Install dependencies - run: yarn install --immutable - - - name: Create version bump PR - id: beachball - uses: ./.github/actions/beachball-release - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore(release): bump package versions [skip ci]' - pr-title: 'chore(release): bump package versions' - dry-run: ${{ (github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.dry-run)) && 'yes' || 'no' }} - - - name: Output PR info - if: steps.beachball.outputs.published == 'yes' - run: | - echo "āœ… Version bump PR created!" - echo "PR Number: ${{ steps.beachball.outputs.pr-number }}" - echo "PR URL: ${{ steps.beachball.outputs.pr-url }}" - - - name: Dry-run info - if: (github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.dry-run)) - run: | - echo "šŸ” Workflow ran in dry-run mode" - echo "āœ… Validation successful - no PRs were created" diff --git a/.github/workflows/validate-changefiles.yml b/.github/workflows/validate-changefiles.yml deleted file mode 100644 index 6f8487c810..0000000000 --- a/.github/workflows/validate-changefiles.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Validate Change Files - -on: - pull_request: - paths: - - 'change/**' - -permissions: - contents: read - -jobs: - validate: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup the toolchain - uses: microsoft/react-native-test-app/.github/actions/setup-toolchain@4.4.5 - with: - platform: node - project-root: . - cache-key-prefix: validate-changefiles - node-version: '24' - cache-npm-dependencies: yarn - - - name: Install dependencies - run: yarn install --immutable - - - name: Check change files are valid - run: npx beachball check --verbose - - - name: Preview packages that would be published - run: | - echo "## šŸ“¦ Packages that would be published after this PR merges" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Run the check script in dry-run mode and capture output - # Node 24 has native TypeScript support - if node scripts/check-packages-need-publishing.ts --dry-run; then - echo "āœ… Package validation successful" >> $GITHUB_STEP_SUMMARY - else - echo "āŒ Package validation failed" >> $GITHUB_STEP_SUMMARY - fi - - - name: Run beachball bump (dry-run) - run: | - echo "šŸ”„ Running beachball bump to preview version changes..." - npx beachball bump --verbose - - - name: Preview NPM publish (dry-run) - run: | - echo "## šŸš€ NPM Publish Preview" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Running \`beachball publish\` in dry-run mode (--no-publish)..." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - # Run beachball publish with --no-publish flag (dry-run) - # This does everything except actually publish to NPM - npx beachball publish --no-bump --no-push --no-publish --access public -y --verbose 2>&1 | tee /tmp/publish-preview.log - - # Add relevant output to summary - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "āœ… Dry-run completed successfully" >> $GITHUB_STEP_SUMMARY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 84e66013c4..768a54a476 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -238,18 +238,19 @@ This repo manages semantic versioning and publishing using [Beachball](https://g #### Publishing Workflow -The repository uses an automated publishing workflow: +The repository uses an automated publishing workflow powered by a single Azure Pipeline (`.ado/azure-pipelines.publish.yml`): 1. **Change Files**: Contributors create change files using `yarn change` in their PRs -2. **Version Bump PR**: When change files are merged to `main`, GitHub Actions automatically creates/updates a version bump PR +2. **Version Bump PR**: When change files are merged to `main`, Azure Pipelines automatically creates/updates a version bump PR - This PR is updated automatically as more changes are merged - The PR shows all packages that will be published and their new versions 3. **Review and Merge**: Maintainers review the version bump PR and merge when ready -4. **Automatic Publishing**: After the version bump PR is merged, Azure Pipelines automatically publishes packages to NPM +4. **Automatic Publishing**: After the version bump PR is merged, Azure Pipelines automatically detects the version changes and publishes packages to NPM + - The pipeline intelligently skips publishing if all package versions already exist on NPM **Branch Support**: Only the `main` branch is configured for automatic publishing. Release branches are not supported in this workflow. -**For Maintainers**: The version bump PR is created by GitHub Actions using a fixed branch name (`beachball/version-bump/main`). This PR will be automatically updated as new change files are merged to main. Do not manually close or recreate this PR unless necessary. +**For Maintainers**: The version bump PR is created by Azure Pipelines using a fixed branch name (`beachball/version-bump/main`). This PR will be automatically updated as new change files are merged to main. Do not manually close or recreate this PR unless necessary. #### Testing changes