diff --git a/.ado/README.md b/.ado/README.md index 0f57972f2c8..c53be1418b2 100644 --- a/.ado/README.md +++ b/.ado/README.md @@ -19,6 +19,30 @@ CI and PR share all build/test/pack logic through `build-template.yml`; the `buildEnvironment` parameter (`Continuous` vs `PullRequest`) gates the environment-specific steps. +## Supported branches + +CI, PR, Release, and the prepare-release bot all run against the same set of +branches, all driven from YAML. Keep the branch lists in `ci-pipeline.yml`, +`pr-pipeline.yml`, `release-pipeline.yml`, and `prepare-release-bot.yml` (its +`trigger` and the `targetBranch` dropdown) in sync with this table. + +| Branch | Status | +|--------|--------| +| `main` | Active development | +| `0.81-stable` | LTS — last release supporting both Fabric and Paper UI | +| `0.83-stable` | Latest release − 1 | +| `0.84-stable` | Current release | +| `0.85-stable` | Preview | +| `0.86-stable` | In upstream React Native, not yet integrated | +| `0.87-stable` | In upstream React Native, not yet integrated | + +`0.82-stable` is deprecated and removed from all pipelines. `0.80-stable` and +earlier are not built. + +Feed warm-up (`warm-feed-pipeline.yml`) is branch-agnostic (scheduled on `main`). +Branch *creation* is governed separately by the ruleset in +[`repo-rules/`](./repo-rules/README.md). + ## Template-repository wiring (per-entry `PipelineTemplates` alias) CI runs in the internal **ISS** project and extends the **Office** 1ES templates. diff --git a/.ado/ci-pipeline.yml b/.ado/ci-pipeline.yml index 4dc20a6db26..6a191c0f597 100644 --- a/.ado/ci-pipeline.yml +++ b/.ado/ci-pipeline.yml @@ -8,9 +8,9 @@ name: 0.0.$(Date:yyMM.d)$(Rev:rrr) trigger: branches: include: + # Supported branches — keep in sync with .ado/README.md - main - '0.81-stable' - - '0.82-stable' - '0.83-stable' - '0.84-stable' - '0.85-stable' diff --git a/.ado/integrate-rn.yaml b/.ado/integrate-rn.yaml index b9ec5384ada..8ad693f6588 100644 --- a/.ado/integrate-rn.yaml +++ b/.ado/integrate-rn.yaml @@ -26,7 +26,7 @@ jobs: - template: templates/prepare-js-env.yml - - script: git checkout -b integrate-${{ parameters.reactNativeVersion }} origin/main + - script: git checkout -b integrate/${{ parameters.reactNativeVersion }} origin/main displayName: Create a new branch - script: yarn integrate-rn ${{ parameters.reactNativeVersion }} --reportPath "$(Agent.TempDirectory)\integration-report.md" @@ -90,11 +90,11 @@ jobs: git commit --amend -F "$(Agent.TempDirectory)\commit-message.md" displayName: Slip changefiles into original commit - - script: git push origin integrate-${{ parameters.reactNativeVersion }} + - script: git push origin integrate/${{ parameters.reactNativeVersion }} displayName: Push changes - pwsh: | $headers = @{'Accept'='application/vnd.github.v3+json'; 'Authorization'='Token $(githubAuthToken)'} - $body = '{"head": "integrate-${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}' + $body = '{"head": "integrate/${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}' Invoke-WebRequest -Method Post -Headers $headers -Body $body https://api.github.com/repos/microsoft/react-native-windows/pulls displayName: Create PR diff --git a/.ado/pr-pipeline.yml b/.ado/pr-pipeline.yml index 04476e4230b..48220bc838c 100644 --- a/.ado/pr-pipeline.yml +++ b/.ado/pr-pipeline.yml @@ -8,9 +8,14 @@ name: $(Date:yyyyMMdd).$(Rev:r) trigger: none pr: + # Supported branches — keep in sync with .ado/README.md - main - - master - - "*-stable" + - '0.81-stable' + - '0.83-stable' + - '0.84-stable' + - '0.85-stable' + - '0.86-stable' + - '0.87-stable' variables: - group: platform-override-zero-permission-token diff --git a/.ado/prepare-release-bot.yml b/.ado/prepare-release-bot.yml index 4b6f2ea5a64..163d3fe5d47 100644 --- a/.ado/prepare-release-bot.yml +++ b/.ado/prepare-release-bot.yml @@ -1,10 +1,17 @@ name: $(Date:yyyyMMdd).$(Rev:r) -# Triggers are configured in the ADO pipeline UI: -# - CI triggers on pushes to main and *-stable branches -# - Scheduled triggers for daily runs -# - Manual runs with optional branch override -trigger: none +# CI trigger on the supported branches; also queued manually (targetBranch parameter). +trigger: + branches: + include: + # Supported branches — keep in sync with .ado/README.md + - main + - '0.81-stable' + - '0.83-stable' + - '0.84-stable' + - '0.85-stable' + - '0.86-stable' + - '0.87-stable' pr: none parameters: @@ -13,12 +20,15 @@ parameters: type: string default: (source branch) values: + # Supported branches — keep in sync with .ado/README.md - (source branch) - main - - 0.82-stable - 0.81-stable - - 0.80-stable - - 0.74-stable + - 0.83-stable + - 0.84-stable + - 0.85-stable + - 0.86-stable + - 0.87-stable jobs: - job: PrepareRelease diff --git a/.ado/release-pipeline.yml b/.ado/release-pipeline.yml index f8bcd0817a7..811d86b4edf 100644 --- a/.ado/release-pipeline.yml +++ b/.ado/release-pipeline.yml @@ -37,9 +37,9 @@ resources: trigger: branches: include: + # Supported branches — keep in sync with .ado/README.md - main - '0.81-stable' - - '0.82-stable' - '0.83-stable' - '0.84-stable' - '0.85-stable' diff --git a/.ado/repo-rules/README.md b/.ado/repo-rules/README.md new file mode 100644 index 00000000000..da63aea985a --- /dev/null +++ b/.ado/repo-rules/README.md @@ -0,0 +1,148 @@ +# Repository rules (rulesets) + +Infrastructure-as-code for the GitHub **repository rulesets** applied to +`microsoft/react-native-windows`. Each `*.json` file in this folder is the exact +request body for GitHub's create/update ruleset REST API, so the live policy +always has a reviewable history in git. Edit the JSON, get it reviewed, then +apply it with the commands below. + +## Files + +| File | Ruleset name | Purpose | +|------|--------------|---------| +| [`branch-namespace-ruleset.json`](./branch-namespace-ruleset.json) | Require namespaced branches | Stops new **top-level** branches. New branches must live under an approved namespace (personal work goes under `user//…`). | + +## What `branch-namespace-ruleset.json` does + +- Targets **all branches** (`~ALL`) *except* the approved patterns under + `conditions.ref_name.exclude`. +- Applies the **`creation`** rule ("Restrict creations"): a branch whose name is + not in the allow list cannot be created by anyone without bypass permission. +- **Creation only.** Existing branches and pushes to them are untouched — this + refuses only *new* non-conforming branch names. The ~90 legacy top-level + branches keep working; nobody can add more like them. +- `bypass_actors` is empty on purpose: **no one is exempt**, not even repo + admins. (An admin can still edit or delete the ruleset itself.) + +### Approved branch namespaces + +| Branch pattern | Who / what uses it | +|----------------|--------------------| +| `user//…` | Humans — the required home for personal and PR branches | +| `users/merlinbot/…` | 1ES PT auto-baselining bot | +| `dependabot/…` | Dependabot | +| `copilot/…` | Copilot coding agent | +| `chore/weekly-lock-refresh-*` | `compliance-yarn-lock.yml` weekly bot | +| `*-stable` | Release branches (e.g. `0.85-stable`) | +| `preview-*-test` | Preview test branches (e.g. `preview-0.84-test`) | +| `prepare-release/…` | Release preparation | +| `release/…`, `hotfix/…`, `archive/…` | Reserved by the [branch lifecycle policy](../../docs/branch-lifecycle-policy.md) (`archive/` is the documented way to preserve a branch) | +| `integrate/…` | React Native nightly integration (`integrate/nightly-*`) | +| `cherry-pick/…` | Cherry-pick branches | +| `revert-*` | GitHub "Revert" button on merged PRs | +| `gh-readonly-queue/…` | Merge queue (only if enabled) | +| `gh-pages` | Docs site publishing | + +### Pattern syntax & gotchas + +GitHub matches ref patterns with Ruby's `File.fnmatch` under the +`File::FNM_PATHNAME` flag. It has sharp edges — each point below was verified +against that engine: + +- **A single `*` never crosses `/`.** `foo/*` matches exactly one level + (`foo/bar`), never `foo/a/b`. Inside a single segment `*` is greedy, so + `*-stable` matches both `0.85-stable` and `0.58-vnext-stable`, and + `preview-*-test` requires the literal `-test` suffix (plain `preview-0.84` is + blocked). +- **A bare trailing `**` does *not* cross `/` either.** `foo/**` behaves like + `foo/*` and silently misses nested branches — never use it to mean "everything + under `foo/`". +- **Use `foo/**/*` for a whole namespace.** The `**/` piece matches *zero or + more* directories, so a single `foo/**/*` matches both single-level (`foo/bar`) + and deep (`foo/a/b/c`). That is why each folder namespace needs only this one + entry. +- **`~ALL` — not `refs/heads/**` — means "all branches".** The `include` uses the + special `~ALL` token; a trailing `**` cannot express it (see above). +- **Every `exclude` entry keeps the `refs/heads/` prefix**; patterns match the + full ref path, not the short branch name. +- **Reverts can contain slashes.** GitHub's "Revert" button creates + `revert--` and preserves slashes when the original was + namespaced (e.g. `revert-15633-user/WatsonFix/0.81`). That is why revert takes + two entries: `revert-*` (flat) and `revert-*/**/*` (nested). +- **Matching is case-sensitive.** `fix-*` does not match `Fix-lint`. +- **Unsupported syntax:** no negated character classes (`[^…]`), no brace/extglob + expansion (`{a,b}`, `File::FNM_EXTGLOB`), and backslash is not an escape + character. +- **Creation-only.** The rule blocks new non-conforming branch *names*; it never + touches existing branches or pushes to them. + +## Prerequisites + +1. **Admin on the repo.** Rulesets are an admin-only setting. Elevate through + just-in-time (JIT) admin access to get the **Admin** role on + `microsoft/react-native-windows`, and drop it when you're done. +2. **GitHub CLI**, authenticated as the elevated account: + ```powershell + gh --version + gh auth login # or set $env:GH_TOKEN + ``` + +## Apply (first time) + +```powershell +cd .ado/repo-rules +gh api --method POST repos/microsoft/react-native-windows/rulesets --input branch-namespace-ruleset.json +``` + +## Update an existing ruleset + +Look up the ruleset id, then `PUT` the edited file: + +```powershell +gh api repos/microsoft/react-native-windows/rulesets --jq '.[] | "\(.id): \(.name)"' +gh api --method PUT repos/microsoft/react-native-windows/rulesets/RULESET_ID --input branch-namespace-ruleset.json +``` + +## Change the allowed patterns + +Edit `conditions.ref_name.exclude` in the JSON, commit for review, then run the +**update** command above. Add a single `refs/heads/foo/**/*` entry per folder +namespace — it matches that namespace at any depth. Mind the +[pattern gotchas](#pattern-syntax--gotchas): keep the `refs/heads/` prefix and +never use a bare `foo/**` (it will not cross `/`). + +## Verify + +```powershell +# A non-namespaced name should be rejected: +git push upstream HEAD:policy-smoke-test + +# A namespaced name should succeed, then clean it up: +git push upstream HEAD:user//policy-smoke-test +git push upstream --delete user//policy-smoke-test +``` + +Or inspect which rules would apply to a candidate branch name (it need not exist): + +```powershell +gh api repos/microsoft/react-native-windows/rules/branches/policy-smoke-test +``` + +## Roll back + +```powershell +# Soft-disable: set "enforcement": "disabled" in the JSON and PUT it, or delete it: +gh api --method DELETE repos/microsoft/react-native-windows/rulesets/RULESET_ID +``` + +## Notes + +- To trial the policy without blocking anyone, set `"enforcement": "evaluate"` + first (violations are logged, not enforced), then switch to `"active"`. +- The allow list was derived from the repo's live branches plus the tooling that + creates them: Dependabot, Copilot, [`compliance-yarn-lock.yml`](../../.github/workflows/compliance-yarn-lock.yml), + [`integrate-rn`](../../packages/@rnw-scripts/integrate-rn), the release process, + and the protected prefixes in the [branch lifecycle policy](../../docs/branch-lifecycle-policy.md). +- If you later want an emergency bypass, add an entry to `bypass_actors` (for + example an `OrganizationAdmin` or a specific `Team`) rather than loosening the + patterns. diff --git a/.ado/repo-rules/branch-namespace-ruleset.json b/.ado/repo-rules/branch-namespace-ruleset.json new file mode 100644 index 00000000000..a4292093973 --- /dev/null +++ b/.ado/repo-rules/branch-namespace-ruleset.json @@ -0,0 +1,38 @@ +{ + "name": "Require namespaced branches", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "include": ["~ALL"], + "exclude": [ + "refs/heads/user/**/*", + + "refs/heads/users/merlinbot/**/*", + "refs/heads/dependabot/**/*", + "refs/heads/copilot/**/*", + "refs/heads/chore/weekly-lock-refresh-*", + + "refs/heads/*-stable", + "refs/heads/preview-*-test", + "refs/heads/prepare-release/**/*", + "refs/heads/release/**/*", + "refs/heads/hotfix/**/*", + + "refs/heads/integrate/**/*", + "refs/heads/cherry-pick/**/*", + "refs/heads/archive/**/*", + + "refs/heads/revert-*", + "refs/heads/revert-*/**/*", + "refs/heads/gh-readonly-queue/**/*", + + "refs/heads/gh-pages" + ] + } + }, + "rules": [ + { "type": "creation" } + ] +} diff --git a/.config/1espt/PipelineAutobaseliningConfig.yml b/.config/1espt/PipelineAutobaseliningConfig.yml index 848c72d924c..428e0c5e4b4 100644 --- a/.config/1espt/PipelineAutobaseliningConfig.yml +++ b/.config/1espt/PipelineAutobaseliningConfig.yml @@ -1,5 +1,5 @@ -## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. - +## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details. + pipelines: 163759: retail: