diff --git a/.claude/skills/electron-bump/SKILL.md b/.claude/skills/electron-bump/SKILL.md index 3b4282028b..1f798768da 100644 --- a/.claude/skills/electron-bump/SKILL.md +++ b/.claude/skills/electron-bump/SKILL.md @@ -1,6 +1,6 @@ --- name: electron-bump -description: Upgrade the Electron version in Rocket.Chat.Electron safely. Detects breaking changes between the current and target Electron release, produces a migration plan gated on user approval, applies the fixes (using GitNexus impact analysis to find every affected callsite), bumps coupled config (electron-builder bundle id, CI node-version, @types/node), runs lint + tests, and opens a ready PR from a fresh branch off master. Trigger when the user says "bump electron", "upgrade electron", "update electron to X", "/electron-bump", or asks to move to a newer Electron release. +description: Upgrade the Electron version in Rocket.Chat.Electron safely. Detects breaking changes between the current and target Electron release, produces a migration plan gated on user approval, applies the fixes (using GitNexus impact analysis to find every affected callsite), bumps coupled config (electron-builder bundle id, CI node-version, @types/node), runs lint + tests, and opens a ready PR from a fresh branch off dev. Trigger when the user says "bump electron", "upgrade electron", "update electron to X", "/electron-bump", or asks to move to a newer Electron release. --- # Electron Version Bump @@ -19,7 +19,7 @@ Automates a safe Electron upgrade for **this repo** (Rocket.Chat.Electron). Plan ## Hard rules -- Branch from **master**, never edit master directly. Use a worktree (see CLAUDE.md § Worktrees) so the user's working dir is untouched. +- Branch from **dev**, never edit dev directly. Use a worktree (see CLAUDE.md § Worktrees) so the user's working dir is untouched. - Plan-gated: do NOT apply any code/config edit until the user approves the migration plan. - This is an Orchestrator task: delegate research to `researcher`, edits to `builder-*`, test runs to `watcher`/`tester`. Don't solo the implementation. - Never commit/push without explicit user permission for the commit step (PR creation is the approved end-state of this skill, but confirm before `git push` if not already authorized this session). @@ -118,10 +118,10 @@ Write the plan to a **dedicated, non-colliding** file: `.localdev/workflow/elect ## Phase 4 — Branch & apply (after approval) -1. Create worktree off master: +1. Create worktree off dev: ```bash mkdir -p ../Rocket.Chat.Electron-worktrees - git worktree add ../Rocket.Chat.Electron-worktrees/electron- -b chore/electron- master + git worktree add ../Rocket.Chat.Electron-worktrees/electron- -b chore/electron- dev ``` Work in that worktree for the rest of the skill. 2. Bump the version: @@ -155,7 +155,7 @@ Definition of Done: version bumped everywhere the plan listed, lint green, tests 2. Show the user the diff summary, confirm the commit. 3. Commit with conventional message: `chore: update Electron from to ` — match the repo's historical style (see PRs #3285, #3179; the PR number isn't known at commit time, and squash-merge titles carry it anyway). Body lists breaking-change adaptations for a major bump; minimal for patch/minor. 4. Push the branch. -5. Open a **ready (non-draft)** PR: `gh pr create --base master --label build-artifacts`. Base is **master** (repo default). Apply the `build-artifacts` label here because an Electron bump changes packaging and reviewers must smoke-test the built installers — this is exactly the case the label is for. PR body: +5. Open a **ready (non-draft)** PR: `gh pr create --base dev --label build-artifacts`. Base is **dev** (repo default). Apply the `build-artifacts` label here because an Electron bump changes packaging and reviewers must smoke-test the built installers — this is exactly the case the label is for. PR body: - What changed: version delta, bundled Node/Chromium. - Breaking changes addressed (bullet list, frame as adaptation not regression). - Verification: lint + tests pass; CI will run cross-platform builds. diff --git a/.claude/skills/ship-release/SKILL.md b/.claude/skills/ship-release/SKILL.md index 5912fc6e78..f765e1d990 100644 --- a/.claude/skills/ship-release/SKILL.md +++ b/.claude/skills/ship-release/SKILL.md @@ -1,99 +1,225 @@ --- name: ship-release -description: Ship a Rocket.Chat.Electron release end-to-end. Collects merged fixes since the last tag, drafts release notes, bumps the version on a chore/release branch, opens the bump PR, and — after explicit user approval at each gate — merges, tags, monitors the build-release pipeline in the background, verifies the published release has the full platform asset matrix, and syncs the Jira release (fixVersion on every shipped issue, creating issues for work that has none). Handles stable, patch, and alpha releases plus alpha→stable promotion. Trigger when the user says "ship release X.Y.Z", "release 4.16.0", "cut a patch release", "promote alpha to stable", "/ship-release", or asks to sync/backfill a Jira release's fixVersions. +description: Ship a Rocket.Chat.Electron release end-to-end. Collects merged fixes since the last tag, drafts release notes, bumps the version on a chore/release branch, opens the bump PR, and — after explicit user approval at each gate — merges, tags, monitors the build-release pipeline in the background, and verifies the published release has the full platform asset matrix. Handles alpha (on dev), stable promotion (dev→master), and patch releases (release/X.Y.x). Trigger when the user says "ship release X.Y.Z", "release 4.16.0", "cut a patch release", "promote alpha to stable", or "/ship-release". --- # Ship Release -Drives a release from "fixes merged on master" to "published GitHub release with all platform assets verified". Every irreversible step (merge, tag push, release publish) is gated on explicit user approval. +Drives a release from "fixes merged on dev" to "published GitHub release +with all platform assets verified". Every irreversible step (merge, tag +push, release publish) is gated on explicit user approval. ## Invocation - `/ship-release 4.15.2` — explicit target version. -- `/ship-release` — infer next version: patch bump over the latest stable tag, or ask if ambiguous. -- Alpha: `/ship-release 4.16.0-alpha.0`. Promotion: `/ship-release 4.16.0` when latest tag is `4.16.0-alpha.N`. +- `/ship-release` — infer next version: patch bump over the latest stable + tag, or ask if ambiguous. +- Alpha: `/ship-release 4.17.0-alpha.1`. Stable promotion: + `/ship-release 4.17.0` when the latest tag is `4.17.0-alpha.N`. Patch: + `/ship-release 4.16.1` when the latest stable tag is `4.16.0`. + +## Release types at a glance + +| Type | Bump branch cut from | Bump PR targets | Merge method | Tag lands on | +|---|---|---|---|---| +| Alpha `X.Y.0-alpha.N` | fresh `origin/dev` | `dev` | squash | `dev` tip | +| Stable `X.Y.0` | fresh `origin/dev` | `dev`, then a release PR `dev`→`master` | bump: squash; release: **merge commit** (`gh pr merge --merge`) | `master` merge commit | +| Patch `X.Y.Z` | `release/X.Y.x` (cut from tag `X.Y.0` if it doesn't exist yet) | `release/X.Y.x` | squash | `release/X.Y.x` tip | ## Hard rules -- **NEVER merge, tag, or publish without explicit user approval at that gate.** Preparing a branch/PR is reversible; merge/tag/publish are not. -- Release branch is always `chore/release-` cut from **fresh `origin/master`** — never from dev, never from a stale local master. -- Tag only AFTER the bump PR is merged, and tag the **merge commit on master** — a tag pointing anywhere else ships the wrong tree. -- Tag name is the bare version (`4.15.1`, no `v` prefix) — `build-release.yml` triggers on any tag push, and the auto-updater feed derives from `package.json` version, which MUST match the tag. -- **Always tag with `yarn release:tag`** (`scripts/release-tag.ts`) — never hand-rolled `git tag` + `git push`. Its channel-aware guards (tag-exists, semver-greater-within-channel) are the safety net; a manual tag skips them. If it refuses, fix the cause — do not work around it. +- **NEVER merge, tag, or publish without explicit user approval at that + gate.** Preparing a branch/PR is reversible; merge/tag/publish are not. +- **Stable release PRs (`dev`→`master`) must be merged with a true merge + commit** (`gh pr merge --merge`), never squash. Squashing forks history + permanently — `master` stops being a subset of `dev`'s commit graph. + Every other bump PR (alpha on `dev`, patch on `release/X.Y.x`) still + squash-merges as usual. +- Tag only AFTER the relevant bump/release PR is merged, and tag the exact + commit that carries the target version — a tag pointing anywhere else + ships the wrong tree. +- Tag name is the bare version (`4.15.1`, no `v` prefix) — `build-release.yml` + triggers only on semver tag pushes, and the auto-updater feed derives from + `package.json` version, which MUST match the tag. - Bump PRs squash-merge (history convention: `chore: bump version to X.Y.Z (#NNNN)`). -- All three platform jobs (ubuntu / macos / windows) must be green before the release counts as buildable. No partial releases. -- A release without the full asset matrix (below) is NOT done — report exactly which assets are missing. -- Monitor CI in the background (`run_in_background` Bash or `watcher`) — never block the session polling in foreground. +- All three platform jobs (ubuntu / macos / windows) must be green before + the release counts as buildable. No partial releases. +- A release without the full asset matrix (below) is NOT done — report + exactly which assets are missing. +- Monitor CI in the background (`run_in_background` Bash or `watcher`) — + never block the session polling in foreground. +- Tags always go through `yarn release:tag` (`scripts/release-tag.ts`), + never a bare `git tag` push. The script is channel-aware: it verifies HEAD + is an ancestor of the allowed ref for the tag's channel (alpha → + `origin/dev`; stable/patch → `origin/master` or the matching + `origin/release/*`) before tagging. An intentional escape hatch, + `--allow-unverified-ref`, bypasses the ancestor check — only use it with + explicit user confirmation that the ref is correct. ## Phase 0 — Resolve version & scope -1. Fresh state: `git fetch origin master --tags`. -2. Latest tags, semver-sorted (not by creation date — an alpha or an older version created later can otherwise look newest): `git tag --sort=-v:refname | head -5`. Current version: `node -p "require('./package.json').version"`. -3. Resolve TARGET from the arg, or propose: patch bump over the latest **stable** tag (`scripts/release-tag.ts`'s channel logic — exclude alpha/beta/rc tags when picking the stable baseline). If latest tag is an alpha of the same version, this is a **promotion** (drop the `-alpha.N` suffix). -4. Collect what ships: `git log ..origin/master --oneline --no-merges`. Filter out chore/version-bump commits. -5. If master has nothing new since the last tag → STOP and tell the user there is nothing to release. +1. Fresh state: `git fetch origin dev master --tags` (also fetch the + relevant `release/X.Y.x` for a patch). +2. Latest tags, semver-sorted (not by creation date — an alpha or an older + version created later can otherwise look newest): + `git tag --sort=-v:refname | head -5`. +3. Resolve TARGET and its type from the arg, or propose one: + - No pre-release suffix and the latest tag on that `X.Y` line is an + alpha → **stable promotion**. + - Next `X.Y.Z+1` after an existing stable tag `X.Y.Z` → **patch**. + - Next `X.(Y+1).0-alpha.1` after the latest stable tag → **alpha**. +4. Collect what ships: + - Alpha/stable: `git log ..origin/dev --oneline --no-merges`. + - Patch: `git log ..origin/release/X.Y.x --oneline --no-merges` + plus the cherry-pick candidates still on `dev` only. + Filter out chore/version-bump commits. +5. If the relevant branch has nothing new since the last tag → STOP and + tell the user there is nothing to release. ## Phase 1 — Release notes draft -1. Map each shipped commit to its PR (`(#NNNN)` suffix) and pull titles: `gh pr view NNNN --json title,labels`. -2. Draft notes grouped as: 🐛 Fixes / ✨ Improvements / 🔧 Internal. Straightforward language, what changed and why — no invented metrics. -3. **Customer-facing framing rule applies**: partial-scope fixes are "hardening" / "did not cover path X", never "was broken" / "regression". -4. Show the draft to the user. Notes get applied to the GitHub release in Phase 5. +1. Map each shipped commit to its PR (`(#NNNN)` suffix) and pull titles: + `gh pr view NNNN --json title,labels`. +2. Draft notes grouped as: 🐛 Fixes / ✨ Improvements / 🔧 Internal. + Straightforward language, what changed and why — no invented metrics. +3. **Customer-facing framing rule applies**: partial-scope fixes are + "hardening" / "did not cover path X", never "was broken" / "regression". +4. Show the draft to the user. Notes get applied to the GitHub release in + Phase 5. ## Phase 2 — Bump branch & PR -1. Create the release worktree and record its path — every command below runs inside it (`cd` into it, or `git -C `), never in the user's own checkout: +### Alpha + +1. Create the release worktree off fresh `origin/dev` and record its path — + every command below runs inside it: ```sh - git worktree add ../Rocket.Chat.Electron-worktrees/release- -b chore/release- origin/master + git worktree add ../Rocket.Chat.Electron-worktrees/release- -b chore/release- origin/dev RELEASE_WT=$(pwd)/../Rocket.Chat.Electron-worktrees/release- cd "$RELEASE_WT" ``` -2. Bump `"version"` in `package.json` (root, ~line 9). Nothing else — no lockfile change needed for a version bump. -3. **GATE: show the diff and STOP for explicit user approval before the first commit + push of `chore/release-`.** -4. Commit: `chore: bump version to ` and push the branch (still inside `$RELEASE_WT`). -5. Open PR to **master** titled `chore: bump version to `, body = the shipped-changes list from Phase 1. No `build-artifacts` label (release build comes from the tag, not the PR). -6. Wait for `validate-pr` checks (lint + tests on all 3 platforms). -7. **GATE: show PR URL + checks status. STOP until the user says merge.** +2. Bump `"version"` in `package.json` and `mac.bundleVersion` in + `electron-builder.json` (see `docs/release-process.md` for the + `bundleVersion` format/increment rule). +3. **GATE: show the diff and STOP for explicit user approval** before the + first commit + push. +4. Commit `chore: bump version to `, push, open a PR to **`dev`**. +5. Wait for `validate-pr` checks. **GATE: show PR URL + checks status. STOP + until the user says merge.** +6. Squash-merge: `gh pr merge --squash`. + +### Stable (promotion) + +1. Same worktree setup as alpha, off fresh `origin/dev`. +2. Bump `"version"` in `package.json` to the bare version (drop the + pre-release suffix, e.g. `4.17.0-alpha.6` → `4.17.0`). +3. **GATE**, commit, push, open a bump PR to **`dev`**. Wait for checks. + **GATE: STOP until the user says merge.** Squash-merge. +4. `git -C "$RELEASE_WT" fetch origin dev` and confirm the merge commit is + HEAD of `origin/dev` with `package.json` at TARGET. +5. Open the **release PR**: `dev` → `master` + (`gh pr create --base master --head dev --title "chore: release "`), + body = the shipped-changes list from Phase 1. **GATE: show PR URL + + checks status. STOP until the user explicitly approves the promotion + merge** — this is the point where history becomes irreversible. + +### Patch + +1. Ensure the patch line exists, cut from the stable tag it patches: + ```sh + git fetch origin --tags + git ls-remote --heads origin release/ # check if it already exists + # if missing: + git worktree add ../Rocket.Chat.Electron-worktrees/release- -b release/ + git push origin release/ + ``` +2. Cherry-pick the target fixes from `dev` onto the release branch (in a + worktree checked out to `release/`): + ```sh + git cherry-pick [...] + ``` + **GATE: show the cherry-picked commits and STOP for approval** before + pushing. +3. Bump `"version"` in `package.json` to `X.Y.Z`, commit, push a bump PR + targeting **`release/`**. Wait for checks. **GATE: STOP until the + user says merge.** Squash-merge. ## Phase 3 — Merge & tag -All commands in this phase run inside `$RELEASE_WT` (`git -C "$RELEASE_WT" ...` or stay `cd`'d in) — never in the user's own checkout. +All commands in this phase run inside `$RELEASE_WT` (`git -C "$RELEASE_WT" ...` +or stay `cd`'d in) — never in the user's own checkout. -1. Squash-merge: `gh pr merge --squash`. -2. `git -C "$RELEASE_WT" fetch origin master` and confirm the merge commit is HEAD of `origin/master` and its `package.json` has TARGET. -3. **GATE: confirm with the user before pushing the tag** (tag push = build + release creation; deleting a tag after builds start is messy). -4. Move the release worktree HEAD onto the master merge commit, then tag with **`yarn release:tag`** — always the repo script, never hand-rolled `git tag`/`git push`. The worktree is still on `chore/release-` (the pre-merge bump commit); tagging there ships the wrong tree, so detach onto the squashed merge commit first: +### Alpha +1. `git -C "$RELEASE_WT" fetch origin dev` and confirm the squash-merge + commit is HEAD of `origin/dev` with `package.json` at TARGET. +2. **GATE: confirm with the user before pushing the tag.** +3. Detach onto the `dev` tip and tag: ```sh - MERGE_SHA=$(git -C "$RELEASE_WT" rev-parse origin/master) - git -C "$RELEASE_WT" checkout "$MERGE_SHA" # detached HEAD at the merge commit + MERGE_SHA=$(git -C "$RELEASE_WT" rev-parse origin/dev) + git -C "$RELEASE_WT" checkout "$MERGE_SHA" node -p "require('$RELEASE_WT/package.json').version" # MUST print TARGET - cd "$RELEASE_WT" && yarn install # required — see below - yarn release:tag --yes + (cd "$RELEASE_WT" && yarn release:tag) ``` - `scripts/release-tag.ts` reads the version from `package.json`, fetches the refs allowed for that version's channel, then **fails closed (exit 1)** on: +### Stable - | Guard | Override | - | ------------------------------------------------------ | ---------------------------- | - | Invalid semver in `package.json` | none | - | HEAD not an ancestor of an allowed ref for its channel | `--allow-unverified-ref` | - | Tag already exists | none — not even `--force` | - | Version not greater than latest tag **in its channel** | `--force` | +1. **After** the Phase 2 release PR is approved by the user, merge it with a + **true merge commit — never squash**: + ```sh + gh pr merge --merge + ``` +2. `git -C "$RELEASE_WT" fetch origin master` and confirm the merge commit + is HEAD of `origin/master` and its `package.json` has TARGET. +3. **GATE: confirm with the user before pushing the tag.** +4. Detach onto the `master` merge commit and tag: + ```sh + MERGE_SHA=$(git -C "$RELEASE_WT" rev-parse origin/master) + git -C "$RELEASE_WT" checkout "$MERGE_SHA" + node -p "require('$RELEASE_WT/package.json').version" # MUST print TARGET + (cd "$RELEASE_WT" && yarn release:tag) + ``` - Prerelease tags (alpha/beta/rc) must have HEAD as an ancestor of `origin/dev` or an `origin/release/*` branch; stable tags must have HEAD as an ancestor of `origin/master` or an `origin/release/*` branch. Channel detection (stable / alpha / beta / candidate) compares only within a channel, so an alpha never blocks a stable or vice versa. The ref-ancestor guard is what makes step 4's "detach onto the merge commit" enforced rather than merely documented — a hand-rolled `git tag` skips every one of these. +### Patch - - **`--yes` skips the confirmation prompt** (also auto-skipped when `CI=true`), so an agent can run this unattended. Step 3 is already the human gate. Without the flag it prompts `Proceed? (y/N)` over `readline`, which needs a real TTY — piping `echo y` is unreliable. - - **`yarn install` first.** A fresh worktree has no `node_modules`, and without it the script dies with `Couldn't find the node_modules state file (findPackageLocation)`. Install — do not work around it by tagging by hand. - - **If the script refuses, that is a real finding** — report the guard that fired and fix the cause. Do not bypass it with manual git commands, and do not reach for `--force`/`--allow-unverified-ref` without the user explicitly agreeing. - - After it reports success, verify the tag landed on the right commit: - ```sh - git -C "$RELEASE_WT" fetch origin --tags - test "$(git -C "$RELEASE_WT" rev-list -1 )" = "$MERGE_SHA" \ - && echo "tag OK" || echo "TAG POINTS AT THE WRONG COMMIT — do not proceed" - ``` +1. `git -C "$RELEASE_WT" fetch origin release/` and confirm the + squash-merge commit is HEAD of `origin/release/` with + `package.json` at TARGET. +2. **GATE: confirm with the user before pushing the tag.** +3. Detach onto the release-branch tip and tag: + ```sh + MERGE_SHA=$(git -C "$RELEASE_WT" rev-parse origin/release/) + git -C "$RELEASE_WT" checkout "$MERGE_SHA" + node -p "require('$RELEASE_WT/package.json').version" # MUST print TARGET + (cd "$RELEASE_WT" && yarn release:tag) + ``` -5. Note: the master push (bump merge) also triggers `build-release.yml` — that run is a master build, NOT the release run. The release run is the one with `head_branch == ` (the tag ref). Find it: `gh run list --workflow=build-release.yml --limit 5 --json databaseId,headBranch,status`. +### All types + +`yarn release:tag` (`scripts/release-tag.ts`) reads the version from +`package.json`, runs the channel-aware ancestor guard, refuses if the tag +already exists or isn't greater than the latest tag in-channel, then tags +the current HEAD as the bare version and pushes it. It prompts +`Proceed? (y/N)` — pipe `y` for non-interactive (`echo y | yarn release:tag`). + +- **node_modules required**: a fresh worktree has none, so `yarn release:tag` + fails with `Couldn't find the node_modules state file (findPackageLocation)`. + Run `yarn install` in the worktree first, or replicate the script's exact + guard by hand (fail closed) if a fast tag is unavoidable: + ```sh + cd "$RELEASE_WT" + if git rev-parse -q --verify "refs/tags/" >/dev/null; then + echo "TAG EXISTS — abort" >&2 + exit 1 + fi + git tag -- + test "$(git rev-list -1 )" = "$MERGE_SHA" || { echo "tag does not point at merge SHA — abort" >&2; exit 1; } + git push origin refs/tags/ + ``` +- The tag push is the **only** trigger for `build-release.yml` — branch + pushes to `dev`/`master`/`release/*` no longer start a release build. + Find the run: `gh run list --workflow=build-release.yml --limit 5 --json databaseId,headBranch,status` + (the release run's `headBranch` is the tag ref itself). ## Phase 4 — Monitor pipeline @@ -107,110 +233,38 @@ All commands in this phase run inside `$RELEASE_WT` (`git -C "$RELEASE_WT" ...` 1. `gh release view --json name,isDraft,url,assets`. 2. Assert the full asset matrix — missing entries = release NOT done: - | Platform | Expected assets | - | -------- | ---------------------------------------------------------------------------------------------------------- | - | macOS | `-mac.dmg` (+`.blockmap`), `-mac.pkg`, `-mac.zip`, `-mas.pkg`, `latest-mac.yml` | - | Windows | x64/ia32/arm64 × (`.exe` +`.blockmap`, `.msi`, `.appx`), universal `-win.exe` (+`.blockmap`), `latest.yml` | - | Linux | `.deb`, `.rpm`, `.snap`, `.AppImage`, `.tar.gz`, `latest-linux.yml` | + | Platform | Expected assets | + |---|---| + | macOS | `-mac.dmg` (+`.blockmap`), `-mac.pkg`, `-mac.zip`, `-mas.pkg`, `latest-mac.yml` | + | Windows | x64/ia32/arm64 × (`.exe` +`.blockmap`, `.msi`, `.appx`), universal `-win.exe` (+`.blockmap`), `latest.yml` | + | Linux | `.deb`, `.rpm`, `.snap`, `.AppImage`, `.tar.gz`, `latest-linux.yml` | (4.15.1 reference: 27 assets total.) - 3. Apply the Phase 1 release notes: `gh release edit --notes-file `. 4. Alphas: mark prerelease (`gh release edit --prerelease`) **while still a draft** — do this before the publish gate, never after, so the alpha is never briefly visible to stable clients. 5. If the release is a draft: **GATE — ask before publishing** (`gh release edit --draft=false`). Publishing exposes the update feed (`latest*.yml`) to every installed client — this is the point of no return for auto-update. -## Phase 6 — Jira release sync - -Every shipped PR must be traceable to a Jira issue carrying `fixVersion = [Electron] `, so the release report answers "what went into this version". Do this **after** the GitHub release is verified, using the Phase 1 PR list as the work inventory. - -### Credentials - -The Atlassian MCP tools (`createJiraIssue`, `editJiraIssue`, `searchJiraIssuesUsingJql`) handle most of it. For raw REST — transitions, version metadata — use the `jira` CLI's stored token; it has no `api` subcommand, so call REST directly: - -```sh -TOKEN=$(security find-generic-password -s "jira-cli" -w) -LOGIN=$(grep -i "^login:" ~/.config/.jira/.config.yml | awk '{print $2}') -curl -s -u "$LOGIN:$TOKEN" -H "Accept: application/json" -``` - -Route curl through `ctx_execute` — a PreToolUse hook redirects curl/wget out of plain Bash. - -### Steps - -1. **Resolve the version.** Name format is `[Electron] X.Y.Z` — NOT bare `X.Y.Z`, and JQL on the wrong name silently returns zero issues rather than erroring. Confirm before trusting an empty result: - - ```sh - curl -s -u "$LOGIN:$TOKEN" "https://rocketchat.atlassian.net/rest/api/3/project/CORE/version?maxResults=100&query=Electron" - ``` - - Releases are usually pre-created by the team. If absent, ask — creating one needs project-admin rights (see step 6). - -2. **Inventory.** For each PR in the Phase 1 list, find its Jira issue: a `CORE-NNNN` in the PR title/body/branch, or search by feature keywords (`searchJiraIssuesUsingJql`, component `Electron`). Broad JQL sweeps blow the MCP token limit — restrict `fields` to `summary,status,issuetype,fixVersions` and parse the saved file with `ctx_execute` when it still overflows. - -3. **Existing issues** — add the fixVersion, leaving other fields alone. The write REPLACES the whole array, so read first and send the union; passing only the new version silently drops any release the issue already shipped in: - - ```text - # 1. read what is already there (getJiraIssue, fields: ["fixVersions"]) - # 2. send existing + the new one, deduplicated by name - editJiraIssue(issueIdOrKey: "CORE-NNNN", - fields: {"fixVersions": [{"name": ""}, {"name": "[Electron] "}]}) - ``` - - An issue with no prior `fixVersions` collapses to just the new entry — that is the common case, but confirm it rather than assume it. - -4. **Uncovered work** — create an issue per feature area (not per PR; related PRs group into one). Ask the user for granularity if the split isn't obvious. Set `components: [{"name": "Electron"}]` and the fixVersion at creation: - - ```text - createJiraIssue(projectKey: "CORE", issueTypeName: "Task"|"Bug", - additional_fields: {"components": [{"name":"Electron"}], - "fixVersions": [{"name": "[Electron] "}]}) - ``` - - Each description: what shipped, why, PR links, and any QA/verification note. Include chores, deps, docs, i18n and CI — the release report is the audit trail, and "no user-visible change" is itself worth recording. Don't invent metrics; quote only numbers the PR actually reports. - -5. **Transition to Done.** Transition id **111** → Done on the CORE workflow; resolution is set automatically and is NOT a settable field on it — `jira issue move Done --resolution Done` fails with a bare `400`. Confirm ids first (workflows change): - - ```sh - curl -s -u "$LOGIN:$TOKEN" "https://rocketchat.atlassian.net/rest/api/3/issue//transitions?expand=transitions.fields" - curl -s -u "$LOGIN:$TOKEN" -X POST -H "Content-Type: application/json" \ - -d '{"transition":{"id":"111"}}' \ - "https://rocketchat.atlassian.net/rest/api/3/issue//transitions" # expect HTTP 204 - ``` - - Ask before flipping issues that QA still holds (`in QA`, `Ready for QA`) — that status is QA's tracking, not yours. - -6. **Mark the version released** — needs **global or project-admin rights**, which the desktop maintainer account does NOT have. The PUT returns an empty body and silently no-ops (a fuller payload surfaces the real _"You must have global or project administrator rights in order to modify versions"_). Never report this as done off the response — GET the version back and check `released`. When it fails, hand it to a CORE project admin and say so explicitly. - -7. **Verify and report** — never trust the write responses alone: - ```sh - curl -s -u "$LOGIN:$TOKEN" -G \ - --data-urlencode 'jql=project=CORE AND fixVersion= ORDER BY key ASC' \ - --data-urlencode 'fields=summary,status,resolution,issuetype' \ - "https://rocketchat.atlassian.net/rest/api/3/search/jql" - ``` - Cross-check every Phase 1 PR against the issue set and report leftovers. Auditing by scraping `/pull/` links out of descriptions gives false positives — older tickets describe fixes in prose without links, so map those by hand before claiming a gap. - -Keeping this current per-release is far cheaper than reconstructing it from `git log` months later. - -## Phase 7 — Wrap up +## Phase 6 — Wrap up -1. Report: release URL, asset count, platforms green, Jira release URL + issue count. +1. Report: release URL, asset count, platforms green. 2. Cleanup: remove the release worktree (`git worktree remove ../Rocket.Chat.Electron-worktrees/release-`). -3. Optional (ask): comment the release URL on shipped PRs. +3. **Stable only**: confirm `dev`'s `package.json` still equals TARGET (the + version invariant) — it should, since the bump happened on `dev` before + promotion. +4. **Patch only**: remind the user that if this fix was authored directly on + the release branch (not cherry-picked from `dev`), it must be + forward-ported to `dev` via a small cherry-pick PR — the one exception to + the never-back-merge rule. +5. Optional (ask): transition linked Jira tickets to Done (desktop tickets: assignee Jean, component Electron) and comment the release URL on shipped PRs. ## Failure modes -| Symptom | Likely cause | Action | -| -------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| Tag run missing from `gh run list` | Tag pushed before merge, or push rejected | Verify tag exists on remote and points at master HEAD | -| Windows job fails at signing/MSI | KMS CNG provider conflict (two-phase signing) | Read `--log-failed`; usually re-run, not code | -| macOS job stuck >1h at notarize | Apple notarization queue | Wait; stall threshold 2h before escalating | -| Release exists but assets partial | One platform job failed after others published | Fix/re-run failed job; electron-builder appends to same release | -| `latest*.yml` version ≠ tag | package.json bump missed before tag | Critical — auto-updater breaks; delete release+tag, redo from Phase 2 | -| Jira release reads empty | JQL used bare `X.Y.Z`; real name is `[Electron] X.Y.Z` | Resolve the name/id from the project version list — a wrong name returns zero, not an error | -| `jira issue move ... Done` → `400` | `--resolution` is not settable on transition 111 | Drop the flag; POST the transition, resolution is set by the workflow | -| Version stays unreleased after PUT | Account lacks project-admin rights (empty-body no-op) | GET the version to confirm; hand the toggle to a CORE project admin | -| `release:tag` → `findPackageLocation` | Fresh worktree has no `node_modules` | `yarn install` in the worktree — never tag by hand instead | -| `release:tag` hangs at `Proceed? (y/N)` | Ran without `--yes`; `readline` needs a TTY | Re-run with `--yes` (step 3 is already the human gate) | -| `release:tag` → "HEAD is not contained in any allowed ref" | Tagging the pre-merge bump commit, not the squashed merge/dev commit | Detach onto the branch the channel expects (Phase 3 step 4) — this guard is the point, do not override blindly | -| `release:tag` refuses the version | Guard fired: tag exists, or not greater in-channel | Real finding — report the guard; fix the version, do not bypass with manual git | +| Symptom | Likely cause | Action | +|---|---|---| +| Tag run missing from `gh run list` | Tag pushed before merge, or push rejected | Verify tag exists on remote and points at the correct branch's HEAD | +| Windows job fails at signing/MSI | KMS CNG provider conflict (two-phase signing) | Read `--log-failed`; usually re-run, not code | +| macOS job stuck >1h at notarize | Apple notarization queue | Wait; stall threshold 2h before escalating | +| Release exists but assets partial | One platform job failed after others published | Fix/re-run failed job; electron-builder appends to same release | +| `latest*.yml` version ≠ tag | package.json bump missed before tag | Critical — auto-updater breaks; delete release+tag, redo from Phase 2 | +| `yarn release:tag` guard rejects HEAD | Tagging from the wrong branch for the channel (e.g. tagging a stable off `dev` directly, or an alpha off a `release/*` branch) | Re-verify you're on the correct branch/commit; only use `--allow-unverified-ref` with explicit user confirmation | +| Release PR (`dev`→`master`) accidentally squashed | Wrong merge method selected in the merge dialog/CLI | Irreversible — history has forked; escalate to the user immediately, do not attempt to "fix" it by force-pushing `master` | diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 71100be550..e7a1a3300c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,6 +29,17 @@ To check your files, run: yarn lint ``` +## Branching Model + +Pull requests target `dev`, the default branch. `master` holds only +released code — it only moves forward via a `dev`→`master` release merge. +`release/X.Y.x` branches are patch lines for a shipped stable version, +maintained by cherry-picking fixes from `dev`; only backport PRs for an +already-released version should target one of these. + +See `docs/development-and-release-flow.md` for the full picture of how a +change moves from PR to release. + ## Contributor License Agreement Please review and sign our [CLA]. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1518f72892..d19190e998 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,9 @@ prefixes: - "feat:" for new features; - "fix:" for bug fixes. + +INSTRUCTION: Target the `dev` branch, unless this PR is backporting a fix +to a `release/X.Y.x` patch line. --> diff --git a/AGENTS.md b/AGENTS.md index 11bc4dcae6..87d2004f1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,27 @@ yarn workspaces:build # Build all workspaces `rm -rf workspaces/desktop-release-action/dist/dist` — the action only needs `workspaces/desktop-release-action/dist/index.js`. +## Branching Model + +- `dev` is the default branch. ALL feature and fix PRs target `dev` and are + squash-merged. +- `master` holds only released code. It advances only via a `dev`→`master` + release PR merged with a true merge commit (`gh pr merge --merge`) — + NEVER squash a release PR; squashing forks history permanently. +- `release/X.Y.x` branches are patch lines for a shipped stable version. + Fixes land on `dev` first and are cherry-picked onto the release branch. + A hotfix authored directly on a release branch must be forward-ported to + `dev` immediately via a cherry-pick PR. +- Never back-merge `master` or a `release/X.Y.x` branch into `dev`. +- Tags are created only via `yarn release:tag` (channel-aware guard). + Release builds trigger on semver tag pushes only and always produce a + draft release for a human to review and publish. +- Version invariant: `package.json` on `dev` always equals the newest tag + cut from `dev`'s own line (the first alpha of a new cycle bumps straight + to `X.(Y+1).0-alpha.1`). +- Details: `docs/development-and-release-flow.md` (conceptual overview), + `docs/release-process.md` (exact commands). + ## Patches And Builds - Do not confuse the two patch systems: @@ -33,6 +54,9 @@ yarn workspaces:build # Build all workspaces (configured in `package.json`). - `patch-package`: `patches/`, currently for `@kayahr/jest-electron-runner`. - Never add `@ewsjs/xhr` patches to `patches/`; that creates CI conflicts. +- The `desktop-release-action`'s dev/snapshot code paths (`releaseDevelopment` + / `releaseSnapshot`) are intentionally dead — do not rebuild its `dist/` + bundle just to remove them. - Windows builds must include all architectures: `x64`, `ia32`, and `arm64`. - Code signing uses Google Cloud KMS in two phases: 1. Build packages without signing (empty env vars). @@ -209,8 +233,8 @@ node qa/scripts/export-qase-csv.mjs qa/ - Never commit or push without explicit user permission — "fix this" does NOT mean "commit it". -- Never commit directly to `master` or `dev` — create a branch, test, open a - PR. +- Never commit directly to `master`, `dev`, or `release/X.Y.x` — create a + branch, test, open a PR. - Read-only git operations (status, diff, log) are always fine. - Show what will be committed before committing. @@ -220,7 +244,7 @@ Use worktrees to avoid disrupting another working directory: ```bash mkdir -p ../Rocket.Chat.Electron-worktrees -git worktree add ../Rocket.Chat.Electron-worktrees/feature-name -b new-branch master +git worktree add ../Rocket.Chat.Electron-worktrees/feature-name -b new-branch dev ``` ### Working Principles @@ -266,7 +290,7 @@ This project is indexed by GitNexus as **Rocket.Chat.Electron**. Use the GitNexu ## Always Do - **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. -- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "master"})`. +- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "dev"})`. - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. - When exploring unfamiliar code, use `query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`. diff --git a/README.md b/README.md index 2a4bedf470..771704c077 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rocket.Chat Desktop App -[![Build Status](https://github.com/RocketChat/Rocket.Chat.Electron/actions/workflows/build-release.yml/badge.svg)](https://github.com/RocketChat/Rocket.Chat.Electron/actions) +[![Latest release](https://img.shields.io/github/v/release/RocketChat/Rocket.Chat.Electron)](https://github.com/RocketChat/Rocket.Chat.Electron/releases/latest) ![Project Dependencies](https://img.shields.io/librariesio/github/RocketChat/Rocket.Chat.Electron) [![GitHub All Releases](https://img.shields.io/github/downloads/RocketChat/Rocket.Chat.Electron/total.svg)](https://github.com/RocketChat/Rocket.Chat.Electron/releases/latest) ![GitHub](https://img.shields.io/github/license/RocketChat/Rocket.Chat.Electron.svg) @@ -155,6 +155,21 @@ command on. Ready for distribution file will be outputted to `dist` directory. All packaging actions are handled by [electron-builder][]. It has a lot of [customization options][]. +## Development and Releases + +All pull requests target `dev`; `master` contains only released code, and +`release/X.Y.x` branches carry patch releases for a shipped version. +Releases are tag-driven — pushing a semver tag triggers CI to build every +platform and draft a GitHub release, which a human then reviews and +publishes. + +- [docs/development-and-release-flow.md][] — conceptual overview of the + branch model, versioning, and CI/CD. +- [docs/release-process.md][] — operational runbook with the exact commands + for cutting alpha, stable, and patch releases. +- [.github/CONTRIBUTING.md][] — contribution guidelines, including which + branch to target. + ## Default servers The `servers.json` file will define what servers the client will connect to and @@ -265,3 +280,6 @@ Released under the MIT license. [Jest electron runner]: https://github.com/kayahr/jest-electron-runner [electron-builder]: https://github.com/electron-userland/electron-builder [customization options]: https://www.electron.build/configuration +[docs/development-and-release-flow.md]: docs/development-and-release-flow.md +[docs/release-process.md]: docs/release-process.md +[.github/CONTRIBUTING.md]: .github/CONTRIBUTING.md diff --git a/docs/COVERAGE.md b/docs/COVERAGE.md index 0466489a4d..8823c74866 100644 --- a/docs/COVERAGE.md +++ b/docs/COVERAGE.md @@ -7,7 +7,7 @@ Running log of test-coverage milestones for Rocket.Chat.Electron. Append a new r - `yarn test:coverage` runs the full Jest suite with `--coverage` and writes reports to `coverage/` (`lcov.info`, `coverage-summary.json`, plus a `text-summary` to the console). - Two Jest projects run under `@kayahr/jest-electron-runner`: renderer (electron DOM env) and main (node env). Coverage is collected across both via the root-level `collectCoverageFrom` in `jest.config.js`. - The authoritative number is `coverage/coverage-summary.json` → `total.lines.pct` (the console `text-summary` matches it). -- CI (`.github/workflows/validate-pr.yml`) runs `yarn test:coverage` on every PR to `master`/`dev` and uploads to **Codecov** (`unit` flag). Coverage is **informational** — there is intentionally no hard `coverageThreshold` gate, matching the main Rocket.Chat monorepo's house style (`codecov.yml`: `patch: off`, project `target: auto / threshold: 1%`). +- CI (`.github/workflows/validate-pr.yml`) runs `yarn test:coverage` on every PR to `dev`, `master`, or a `release/*` line and uploads to **Codecov** (`unit` flag). Coverage is **informational** — there is intentionally no hard `coverageThreshold` gate, matching the main Rocket.Chat monorepo's house style (`codecov.yml`: `patch: off`, project `target: auto / threshold: 1%`). ## How to update this log diff --git a/docs/alpha-release-process.md b/docs/alpha-release-process.md deleted file mode 100644 index b4d8c3a70a..0000000000 --- a/docs/alpha-release-process.md +++ /dev/null @@ -1,136 +0,0 @@ -# Alpha Release Process - -This document describes how to create alpha releases for QA testing and early customer access. - -## Overview - -The Rocket.Chat Desktop app supports three release channels: -- **Stable** (`latest`) - Production releases for all users -- **Beta** - Pre-release testing with broader audience -- **Alpha** - Early testing for QA and select customers - -## How Channels Work - -| Channel | Version Format | Who Gets It | Update File | -|---------|---------------|-------------|-------------| -| Stable | `4.12.0` | All users (default) | `latest.yml` | -| Beta | `4.12.0-beta.1` | Beta opt-in users | `beta.yml` | -| Alpha | `4.12.0-alpha.1` | Alpha opt-in users | `alpha.yml` | - -**Channel hierarchy**: Alpha users receive alpha, beta, AND stable updates. Beta users receive beta AND stable. Stable users only receive stable. - -## Creating an Alpha Release - -### 1. Create Release Branch - -```bash -git checkout master -git pull -git checkout -b release/4.12.0-alpha.1 -``` - -### 2. Update Version - -Edit `package.json`: -```json -{ - "version": "4.12.0-alpha.1" -} -``` - -### 3. Commit and Push - -```bash -git add package.json -git commit -m "chore: bump version to 4.12.0-alpha.1" -git push origin release/4.12.0-alpha.1 -``` - -### 4. Create and Push Tag - -```bash -git tag 4.12.0-alpha.1 -git push origin 4.12.0-alpha.1 -``` - -### 5. CI Builds Automatically - -The GitHub Actions workflow triggers on tag push and: -- Builds for all platforms (Windows, macOS, Linux) -- Generates `alpha.yml`, `alpha-mac.yml`, `alpha-linux.yml` metadata -- Creates a draft GitHub release marked as **Pre-release** -- Publishes Linux snap to the `edge` channel - -### 6. Publish the Release - -1. Go to GitHub Releases -2. Find the draft release for your version -3. Review the release notes -4. Click "Publish release" - -## How Users Opt Into Alpha - -### Option A: Developer Mode (Recommended for QA) - -1. Open Settings in the app -2. Enable **Developer Mode** -3. Open **About** dialog (Help > About) -4. Select **Alpha (Experimental)** from the Update Channel dropdown -5. Click **Check for Updates** - -The setting persists - users don't need to select it again. - -### Option B: Configuration File (For Managed Deployments) - -Create `update.json` in the user data directory: - -| Platform | Location | -|----------|----------| -| Windows | `%APPDATA%\Rocket.Chat\update.json` | -| macOS | `~/Library/Application Support/Rocket.Chat/update.json` | -| Linux | `~/.config/Rocket.Chat/update.json` | - -Content: -```json -{ - "channel": "alpha" -} -``` - -For enterprise deployments where you want to force the setting: -```json -{ - "channel": "alpha", - "forced": true -} -``` - -## Version Numbering Guidelines - -- **Alpha**: `4.12.0-alpha.1`, `4.12.0-alpha.2`, etc. -- **Beta**: `4.12.0-beta.1`, `4.12.0-beta.2`, etc. -- **Stable**: `4.12.0` - -When promoting: -- Alpha `4.12.0-alpha.5` → Beta `4.12.0-beta.1` -- Beta `4.12.0-beta.3` → Stable `4.12.0` - -## Safety Guarantees - -- Stable users **never** see alpha releases (they check `latest.yml`, not `alpha.yml`) -- Users must explicitly opt into alpha channel -- Alpha releases are marked as "Pre-release" on GitHub -- Users can switch back to stable at any time - -## Troubleshooting - -### Alpha update not showing - -1. Verify the release is published (not draft) -2. Check that `alpha.yml` exists in the release assets -3. Ensure user has selected "Alpha" channel -4. Check for updates manually via About dialog - -### Checking current channel - -In Developer Mode, open About dialog - the current channel is shown in the dropdown. diff --git a/docs/development-and-release-flow.md b/docs/development-and-release-flow.md new file mode 100644 index 0000000000..b45971d4a1 --- /dev/null +++ b/docs/development-and-release-flow.md @@ -0,0 +1,135 @@ +# Development and Release Flow + +This document explains how code moves through Rocket.Chat Desktop, from a +feature branch to a published release. It is written for anyone +contributing to the project, not only release managers. For the exact +commands used to cut a release, see `docs/release-process.md`; this document +covers the shape of the model and the reasoning behind it. + +## Overview + +The project uses three kinds of branches with distinct roles. `master` +holds only released code — every commit on it corresponds to something that +has shipped or is about to ship. `dev` is where all development converges: +every feature and fix is merged there first, and alpha releases are tagged +directly from it. `release/X.Y.x` branches exist only after a stable +version has shipped, to carry patches for that version without pulling in +unrelated work that has since landed on `dev`. This separation keeps +`master` a stable, auditable history of what was actually released, while +`dev` stays free to move fast. + +## Branch roles + +| Branch | Purpose | Who merges into it | Merge method | +|---|---|---|---| +| `dev` | Default branch; integration point for all feature and fix PRs; source of alpha tags | Any contributor via PR review | Squash | +| `master` | Released code only; moves forward exclusively through a `dev`→`master` release merge | Release manager, at promotion time | True merge commit | +| `release/X.Y.x` | Patch line for a shipped stable version; receives cherry-picked fixes from `dev` | Release manager, when preparing a patch | Squash | +| Feature/fix branches | Short-lived, one change per branch, opened against `dev` | The author, via PR | Squash (into `dev`) | + +## Lifecycle of a change + +A typical change follows this path: + +1. A contributor branches off `dev`, opens a PR, and it is squash-merged + into `dev` after review. +2. `dev` accumulates changes between releases. Periodically, a version bump + is merged and tagged as an alpha (`X.Y.0-alpha.N`) directly on `dev`, so + QA and early adopters can test the accumulating changes. +3. When the release is ready to ship, the version is bumped one more time + on `dev` to drop the pre-release suffix, and then a release PR merges + `dev` into `master` with a true merge commit. The stable tag + (`X.Y.0`) is placed on that merge commit. +4. If a defect is found in a shipped stable version, the fix is merged into + `dev` as usual, then cherry-picked onto the corresponding + `release/X.Y.x` branch (created from the `X.Y.0` tag, if it doesn't + already exist) and released as a patch (`X.Y.Z`). + +```mermaid +--- +config: + gitGraph: + mainBranchName: "master" +--- +gitGraph + commit id: "released code" + branch dev + checkout dev + commit id: "feature work 1" + commit id: "feature work 2" + commit id: "bump to 4.16.0-alpha.1" tag: "4.16.0-alpha.1" + commit id: "feature work 3" + commit id: "bump to 4.16.0-alpha.2" tag: "4.16.0-alpha.2" + commit id: "bump to 4.16.0" + checkout master + merge dev id: "release 4.16.0" tag: "4.16.0" + checkout dev + commit id: "next cycle work" + checkout master + branch release/4.16.x + checkout release/4.16.x + commit id: "cherry-pick fix" + commit id: "bump to 4.16.1" tag: "4.16.1" +``` + +The diagram omits the individual squash commits that make up "feature +work" — in practice each labeled commit above is itself the product of one +or more squash-merged PRs. + +## CI/CD + +| Stage | Trigger | What happens | +|---|---|---| +| PR checks | Any PR opened against `dev`, `master`, or `release/*` | `validate-pr` runs lint and the full test suite; a `build-artifacts` label additionally builds installers for manual smoke-testing | +| Release build | A semver tag push (`X.Y.Z` or `X.Y.Z-alpha.N`, etc.) | `build-release` builds every platform's installers and creates a **draft** GitHub release | +| Publish | Manual | A human reviews the draft release and its assets, then publishes it | + +Release builds never run on a branch push — only on a tag. This keeps +`dev`, `master`, and `release/*` free of accidental builds, and guarantees +that nothing reaches users without both a deliberate tag and a deliberate +publish step. + +Published releases are consumed by the app's auto-updater through three +channels: + +| Channel | Who receives it | +|---|---| +| `latest` (stable) | All users by default | +| `beta` | Users who opt into beta updates | +| `alpha` | Users who opt into alpha updates (also receive beta and stable) | + +## Versioning + +Versions follow semver (`MAJOR.MINOR.PATCH`, with an optional +`-alpha.N`/`-beta.N` pre-release suffix). Two conventions keep the branches +in sync with each other: + +- **The `dev` version invariant**: `package.json` on `dev` always equals + the newest tag cut from `dev`'s own line. Right after a stable promotion, + `dev` stays at the version that was just released — it does not jump + ahead until the next cycle starts. +- **Alpha numbering starts at `.1`**: the first alpha of a new cycle is + `X.(Y+1).0-alpha.1`, never a bare `X.(Y+1).0` — that plain version number + is reserved for the eventual stable release of that cycle. + +## Rules that keep the model consistent + +- **Never back-merge.** `master` and `release/X.Y.x` branches never merge + back into `dev`. Bumping `dev` before every promotion, and cherry-picking + fixes downward from `dev` to patch lines, keeps `master` a pure superset + of `dev`'s history. The single exception: a hotfix authored directly on a + `release/X.Y.x` branch must be forward-ported to `dev` immediately, via a + small cherry-pick PR, so it isn't lost on the next promotion. +- **Never squash the promotion PR.** The `dev`→`master` release merge must + be a true merge commit. Squashing it would fork history permanently and + make every future diff between the branches unreadable. +- **Always tag through `yarn release:tag`.** The script enforces that a tag + is placed on a commit that actually belongs to the correct branch for its + channel, instead of a bare `git tag` push that has no such guard. + +## Further reading + +- `docs/release-process.md` — the operational runbook with exact commands + for cutting alpha, stable, and patch releases. +- `.github/CONTRIBUTING.md` — the Branching Model section for contributors + opening a PR. diff --git a/docs/pre-release-process.md b/docs/pre-release-process.md deleted file mode 100644 index 5e96476af0..0000000000 --- a/docs/pre-release-process.md +++ /dev/null @@ -1,225 +0,0 @@ -# Alpha and Beta Release Process - -This document describes how to create alpha and beta releases for QA testing and early customer access. - -## Overview - -The Rocket.Chat Desktop app supports three release channels: - -- **Stable** (`latest`) - Production releases for all users -- **Beta** - Pre-release testing with broader audience -- **Alpha** - Early testing for QA and select customers - -## How Channels Work - -| Channel | Version Format | Who Gets It | Update File | -| ------- | ---------------- | ------------------- | ------------ | -| Stable | `4.12.0` | All users (default) | `latest.yml` | -| Beta | `4.12.0-beta.1` | Beta opt-in users | `beta.yml` | -| Alpha | `4.12.0-alpha.1` | Alpha opt-in users | `alpha.yml` | - -**Channel hierarchy**: Alpha users receive alpha, beta, AND stable updates. Beta users receive beta AND stable. Stable users only receive stable. - -## Creating an Alpha Release - -### 1. Update Version on dev branch - -```bash -git checkout dev -git pull origin dev -``` - -Edit `package.json`: - -```json -{ - "version": "4.12.0-alpha.1" -} -``` - -Also increment `bundleVersion` in `electron-builder.json`. - -### 2. Commit and Push - -```bash -git add package.json electron-builder.json -git commit -m "chore: bump version to 4.12.0-alpha.1" -git push origin dev -``` - -### 3. Create and Push Tag - -```bash -git tag 4.12.0-alpha.1 -git push origin 4.12.0-alpha.1 -``` - -Or use the release-tag script: - -```bash -npx ts-node scripts/release-tag.ts -``` - -### 4. CI Builds Automatically - -The GitHub Actions workflow triggers on tag push and: - -- Builds for all platforms (Windows, macOS, Linux) -- Generates `alpha.yml`, `alpha-mac.yml`, `alpha-linux.yml` metadata -- Creates a draft GitHub release marked as **Pre-release** -- Publishes Linux snap to the `edge` channel - -### 5. Publish the Release - -1. Go to GitHub Releases -2. Find the draft release for your version -3. Review the release notes -4. Click "Publish release" - -## Creating a Beta Release - -Beta releases follow the same process as alpha, but use `-beta.X` suffix: - -```bash -git checkout dev -# Edit package.json to "4.12.0-beta.1" -git commit -am "chore: bump version to 4.12.0-beta.1" -git push origin dev -git tag 4.12.0-beta.1 -git push origin 4.12.0-beta.1 -``` - -## How Users Opt Into Alpha/Beta Channels - -### Option A: Via the App UI (Recommended) - -1. Open **Settings** in the app (gear icon) -2. Enable **Developer Mode** (scroll down to find it) -3. Go to **Help > About** (or **Rocket.Chat > About** on macOS) -4. You will see an **Update Channel** dropdown -5. Select the desired channel: - - **Stable** - Production releases only - - **Beta** - Beta and stable releases - - **Alpha (Experimental)** - Alpha, beta, and stable releases -6. Click **Check for Updates** - -The setting is persisted automatically and survives app restarts. - -### Option B: Configuration File (For Managed Deployments) - -Create `update.json` in the user data directory: - -| Platform | Location | -| -------- | ------------------------------------------------------- | -| Windows | `%APPDATA%\Rocket.Chat\update.json` | -| macOS | `~/Library/Application Support/Rocket.Chat/update.json` | -| Linux | `~/.config/Rocket.Chat/update.json` | - -Content for alpha channel: - -```json -{ - "channel": "alpha" -} -``` - -Content for beta channel: - -```json -{ - "channel": "beta" -} -``` - -For enterprise deployments where you want to force the setting (users cannot change it): - -```json -{ - "channel": "beta", - "forced": true -} -``` - -## Switching Channels - -### Switching to a pre-release channel (stable → alpha/beta) - -1. Open Settings > Enable Developer Mode -2. Open About dialog -3. Select the desired channel from dropdown -4. Click "Check for Updates" -5. The next pre-release version will be offered - -### Switching back to stable (alpha/beta → stable) - -1. Open About dialog -2. Select "Stable" from the Update Channel dropdown -3. Click "Check for Updates" - -**Important**: Switching to stable does NOT automatically downgrade the app. What happens: - -- If you're on `4.12.0-alpha.2` and switch to stable channel: - - - You will receive the next **stable** release (e.g., `4.12.0`) - - Semver considers `4.12.0` greater than `4.12.0-alpha.2`, so the stable release will be offered as an update - - You won't receive further alpha/beta releases until you switch back - -- If you need to immediately downgrade: - - Uninstall the current version - - Download and install the stable version from GitHub releases - -## Version Numbering Guidelines - -- **Alpha**: `4.12.0-alpha.1`, `4.12.0-alpha.2`, etc. -- **Beta**: `4.12.0-beta.1`, `4.12.0-beta.2`, etc. -- **Stable**: `4.12.0` - -When promoting: - -- Alpha `4.12.0-alpha.5` → Beta `4.12.0-beta.1` -- Beta `4.12.0-beta.3` → Stable `4.12.0` - -Typical release progression: - -```text -4.12.0-alpha.1 → 4.12.0-alpha.2 → 4.12.0-beta.1 → 4.12.0-beta.2 → 4.12.0 -``` - -## Safety Guarantees - -- Stable users **never** see alpha/beta releases (they check `latest.yml` only) -- Users must explicitly enable Developer Mode and select alpha/beta channel -- Alpha and beta releases are marked as "Pre-release" on GitHub -- Channel selection is persisted and survives restarts -- Users can switch channels at any time via the About dialog - -## Troubleshooting - -### Update not showing after channel switch - -1. Verify the release is published (not draft) on GitHub -2. Check that the corresponding `.yml` file exists in the release assets: - - Alpha: `alpha.yml`, `alpha-mac.yml`, `alpha-linux.yml` - - Beta: `beta.yml`, `beta-mac.yml`, `beta-linux.yml` -3. Click "Check for Updates" in the About dialog -4. Restart the app and try again - -### Channel dropdown not visible - -1. Make sure **Developer Mode** is enabled in Settings -2. Close and reopen the About dialog -3. Restart the app completely - -### Checking current channel - -With Developer Mode enabled, open the About dialog - the current channel is shown in the dropdown selector. - -### Where settings are stored - -The channel preference is stored in the app's config file: - -- **Windows**: `%APPDATA%\Rocket.Chat\config.json` -- **macOS**: `~/Library/Application Support/Rocket.Chat/config.json` -- **Linux**: `~/.config/Rocket.Chat/config.json` - -Look for the `updateChannel` key (values: `latest`, `beta`, or `alpha`). diff --git a/docs/release-process.md b/docs/release-process.md new file mode 100644 index 0000000000..d790a9264f --- /dev/null +++ b/docs/release-process.md @@ -0,0 +1,297 @@ +# Release Process + +This document describes how Rocket.Chat Desktop moves code from a merged PR +to a published release, for every channel: alpha, stable, and patch. It +replaces the former `docs/alpha-release-process.md` and +`docs/pre-release-process.md`. + +For the conceptual overview, see `development-and-release-flow.md`. + +## Branch model + +| Event | Where | Mechanics | +|---|---|---| +| Feature/fix PR | → `dev` (default branch) | squash-merge | +| Alpha `X.Y.0-alpha.N` | on `dev` | bump PR on `dev` → tag the `dev` tip via `yarn release:tag` | +| Stable `X.Y.0` | `dev` → `master` | bump PR on `dev` first, then a release PR `dev`→`master` merged with a **true merge commit** (`gh pr merge --merge`, never squash), tag the merge commit | +| Patch `X.Y.Z` | `release/X.Y.x` | branch cut from the stable tag `X.Y.0`; fixes land on `dev` first and are cherry-picked down; bump + tag on the release branch | +| Back-merges | **never** (one exception below) | bump-on-dev-first keeps `master` a pure superset of `dev` | + +Do not use `release/-alpha.N` (or any alpha) branch naming — +`release/` is reserved exclusively for patch lines (`release/4.16.x`, etc.). +Alpha work stays on `dev`; it never gets its own long-lived branch. + +## How channels work + +| Channel | Version Format | Who Gets It | Update File | +|---------|---------------|-------------|-------------| +| Stable | `4.12.0` | All users (default) | `latest.yml` | +| Beta | `4.12.0-beta.1` | Beta opt-in users | `beta.yml` | +| Alpha | `4.12.0-alpha.1` | Alpha opt-in users | `alpha.yml` | + +**Channel hierarchy**: Alpha users receive alpha, beta, AND stable updates. +Beta users receive beta AND stable. Stable users only receive stable. + +## The dev version invariant + +`package.json`'s `"version"` on `dev` always equals the newest tag cut from +`dev`'s line: + +- Right after promoting `4.16.0` to `master`, `dev` stays at `4.16.0` + (it was just bumped there before the promotion PR). +- The first alpha of the next cycle bumps `dev` straight to + `4.17.0-alpha.1` — start numbering at `.1`, never a phantom `.0` (there + is no bare `4.17.0` tag until that cycle stabilizes). + +## Creating an alpha release (on `dev`) + +Alphas are cut directly from `dev` — no dedicated branch. + +1. Update version on `dev`: + + ```bash + git checkout dev + git pull origin dev + git checkout -b chore/release-4.17.0-alpha.1 + ``` + + Edit `package.json`: + + ```json + { + "version": "4.17.0-alpha.1" + } + ``` + + Also increment `mac.bundleVersion` in `electron-builder.json` — it is + independent from `package.json`'s `version` and Apple requires each + submission's `CFBundleVersion` to strictly increase over the last one. + Format: `YYMM` + a single-digit build counter that resets to `0` at the + start of each month (e.g. the first build shipped in August 2026 is + `26080`, the second same-month build is `26081`). Check the current + value and the date of its last bump + (`git log -p --follow -- electron-builder.json`) before incrementing — + never guess an arbitrary increment. + +2. Commit and open a PR to `dev`: + + ```bash + git add package.json electron-builder.json + git commit -m "chore: bump version to 4.17.0-alpha.1" + git push origin chore/release-4.17.0-alpha.1 + ``` + + Squash-merge the PR into `dev`. + +3. Tag the `dev` tip: + + ```bash + git checkout dev && git pull origin dev + yarn release:tag + ``` + + Always tag through `yarn release:tag` — never a bare `git tag` push. The + script reads the version from `package.json`, verifies the channel-aware + guard (an alpha tag must point at a commit that is an ancestor of + `origin/dev`), refuses if the tag already exists or isn't greater than + the latest tag in-channel, then tags HEAD and pushes. + +4. CI builds automatically: pushing a semver tag is the **only** trigger for + release builds (`build-release.yml` no longer runs on branch pushes). The + workflow builds for all platforms, generates `alpha.yml`, + `alpha-mac.yml`, `alpha-linux.yml` metadata, and creates a **draft** + GitHub release marked as Pre-release. + +5. Publish the release: open the draft on GitHub Releases, review the notes, + click "Publish release". Nothing is visible to any client until a human + publishes it. + +## Promoting to a stable release (`dev` → `master`) + +1. **Bump PR on `dev` first.** Drop the pre-release suffix in + `package.json` (e.g. `4.17.0-alpha.6` → `4.17.0`), open a PR targeting + `dev`, squash-merge it. This keeps the version invariant intact and + ensures `master` never carries a version `dev` hasn't already reached. +2. **Release PR `dev` → `master`.** Open a PR from `dev` into `master`. + Merge it with `gh pr merge --merge` — a **true merge commit**, never + squash. Squashing a release PR forks history permanently: `master` would + stop being a subset of `dev`'s commit graph, and every future diff + between the branches becomes unreadable. +3. **Tag the merge commit on `master`**, via `yarn release:tag` (the + channel-aware guard verifies a stable tag points at a commit that is an + ancestor of `origin/master` or a `release/*` branch). +4. CI builds and drafts the release exactly as in the alpha flow; publish + after asset verification. + +## Patch releases (`release/X.Y.x`) + +1. Ensure the patch line branch exists, cut from the stable tag it patches: + + ```bash + git checkout -b release/4.16.x 4.16.0 + git push origin release/4.16.x + ``` + + If the branch already exists (an earlier patch), skip this step. + +2. Fixes are authored and merged on `dev` first, then cherry-picked onto the + release branch: + + ```bash + git checkout release/4.16.x + git cherry-pick + ``` + +3. Bump PR targets the release branch (`package.json` → `4.16.1`), reviewed + and merged there. +4. Tag on the release branch via `yarn release:tag` (the guard accepts a + stable/patch tag whose commit is an ancestor of any `origin/release/*` + branch, in addition to `origin/master`). +5. CI builds and drafts the release the same way; publish after verification. + +## The never-back-merge rule + +`master` never merges back into `dev`, and a `release/X.Y.x` branch never +merges back into `dev` either. Bumping `dev` before promoting to `master` +(and cherry-picking fixes down to patch lines from `dev`, not the reverse) +keeps `master` a pure superset of `dev`'s history — there is nothing on +`master` that needs to flow back. + +**Single exception**: a hotfix authored directly on a `release/X.Y.x` +branch (rather than on `dev` and cherry-picked down) MUST be forward-ported +to `dev` immediately, via a small cherry-pick PR. Otherwise the fix is +silently lost the next time `dev` promotes to `master`. + +## How users opt into alpha/beta channels + +### Option A: Via the App UI (recommended) + +1. Open **Settings** in the app (gear icon). +2. Enable **Developer Mode** (scroll down to find it). +3. Go to **Help > About** (or **Rocket.Chat > About** on macOS). +4. You will see an **Update Channel** dropdown. Select the desired channel: + - **Stable** - Production releases only + - **Beta** - Beta and stable releases + - **Alpha (Experimental)** - Alpha, beta, and stable releases +5. Click **Check for Updates**. + +The setting is persisted automatically and survives app restarts. + +### Option B: Configuration file (for managed deployments) + +Create `update.json` in the user data directory: + +| Platform | Location | +|----------|----------| +| Windows | `%APPDATA%\Rocket.Chat\update.json` | +| macOS | `~/Library/Application Support/Rocket.Chat/update.json` | +| Linux | `~/.config/Rocket.Chat/update.json` | + +Content for alpha channel: + +```json +{ + "channel": "alpha" +} +``` + +Content for beta channel: + +```json +{ + "channel": "beta" +} +``` + +For enterprise deployments where you want to force the setting (users +cannot change it): + +```json +{ + "channel": "beta", + "forced": true +} +``` + +## Switching channels + +### Switching to a pre-release channel (stable → alpha/beta) + +1. Open Settings > Enable Developer Mode. +2. Open About dialog. +3. Select the desired channel from the dropdown. +4. Click "Check for Updates". The next pre-release version will be offered. + +### Switching back to stable (alpha/beta → stable) + +1. Open About dialog. +2. Select "Stable" from the Update Channel dropdown. +3. Click "Check for Updates". + +**Important**: Switching to stable does NOT automatically downgrade the +app. What happens: + +- If you're on `4.12.0-alpha.2` and switch to the stable channel, you will + receive the next **stable** release (e.g., `4.12.0`); semver considers + `4.12.0` greater than `4.12.0-alpha.2`, so it is offered as an update. You + won't receive further alpha/beta releases until you switch back. +- If you need to immediately downgrade: uninstall the current version, then + download and install the stable version from GitHub releases. + +## Version numbering guidelines + +- **Alpha**: `4.12.0-alpha.1`, `4.12.0-alpha.2`, etc. +- **Beta**: `4.12.0-beta.1`, `4.12.0-beta.2`, etc. +- **Stable**: `4.12.0` +- **Patch**: `4.12.1`, `4.12.2`, etc., on `release/4.12.x`. + +Typical release progression: + +```text +4.12.0-alpha.1 → 4.12.0-alpha.2 → 4.12.0-beta.1 → 4.12.0-beta.2 → 4.12.0 → 4.12.1 +``` + +## Safety guarantees + +- Stable users **never** see alpha/beta releases (they check `latest.yml` + only). +- Users must explicitly enable Developer Mode and select the alpha/beta + channel. +- Alpha and beta releases are marked as "Pre-release" on GitHub. +- All release builds trigger **only** on semver tag pushes (never branch + pushes) and always produce a **draft** release — a human reviews and + publishes it explicitly. Nothing reaches any client before that. +- Channel selection is persisted and survives restarts. +- Users can switch channels at any time via the About dialog. + +## Troubleshooting + +### Update not showing after channel switch + +1. Verify the release is published (not draft) on GitHub. +2. Check that the corresponding `.yml` file exists in the release assets: + - Alpha: `alpha.yml`, `alpha-mac.yml`, `alpha-linux.yml` + - Beta: `beta.yml`, `beta-mac.yml`, `beta-linux.yml` +3. Click "Check for Updates" in the About dialog. +4. Restart the app and try again. + +### Channel dropdown not visible + +1. Make sure **Developer Mode** is enabled in Settings. +2. Close and reopen the About dialog. +3. Restart the app completely. + +### Checking current channel + +With Developer Mode enabled, open the About dialog - the current channel is +shown in the dropdown selector. + +### Where settings are stored + +The channel preference is stored in the app's config file: + +- **Windows**: `%APPDATA%\Rocket.Chat\config.json` +- **macOS**: `~/Library/Application Support/Rocket.Chat/config.json` +- **Linux**: `~/.config/Rocket.Chat/config.json` + +Look for the `updateChannel` key (values: `latest`, `beta`, or `alpha`).