Skip to content

Commit 5f76d14

Browse files
authored
feat: Enable alpha release channel support (#3182)
* feat: Enable alpha release channel support - Add semver check to prevent showing "downgrades" as updates - Mark prerelease versions (alpha/beta) as prerelease on GitHub - Change development branch from 'develop' to 'dev' in workflows - Add release-tag.ts script for creating release tags - Add alpha release process documentation * fix: correct git push command for tags in release-tag script Use refs/tags/ prefix instead of invalid 'git push origin tag --' syntax. * chore: Bump version numbers for release - Updated the bundle version in electron-builder.json from 26010 to 26011. - Incremented the application version in package.json from 4.11.1 to 4.12.0-alpha.1.
1 parent 3dadff9 commit 5f76d14

File tree

13 files changed

+337
-18
lines changed

13 files changed

+337
-18
lines changed

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
/node_modules
22
/app
33
/workspaces/*
4+
# The /scripts directory contains utility scripts that run outside the main app
5+
# and use different patterns (e.g., interactive prompts, direct console output)
6+
/scripts

.github/workflows/build-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
push:
77
branches:
88
- master
9-
- develop
9+
- dev
1010
tags:
1111
- '*'
1212
concurrency:

.github/workflows/powershell-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
push:
1414
branches:
1515
- master
16-
- develop
16+
- dev
1717
paths:
1818
- '**.ps1'
1919
- '**.psm1'

.github/workflows/pull-request-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
pull_request:
99
branches:
1010
- master
11-
- develop
11+
- dev
1212

1313
concurrency:
1414
group: ${{ github.workflow }}-${{ github.head_ref }}

.github/workflows/validate-pr.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
pull_request:
55
branches:
66
- master
7+
- dev
78

89
concurrency:
910
group: ${{ github.workflow }}-${{ github.head_ref }}

docs/alpha-release-process.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Alpha Release Process
2+
3+
This document describes how to create alpha releases for QA testing and early customer access.
4+
5+
## Overview
6+
7+
The Rocket.Chat Desktop app supports three release channels:
8+
- **Stable** (`latest`) - Production releases for all users
9+
- **Beta** - Pre-release testing with broader audience
10+
- **Alpha** - Early testing for QA and select customers
11+
12+
## How Channels Work
13+
14+
| Channel | Version Format | Who Gets It | Update File |
15+
|---------|---------------|-------------|-------------|
16+
| Stable | `4.12.0` | All users (default) | `latest.yml` |
17+
| Beta | `4.12.0-beta.1` | Beta opt-in users | `beta.yml` |
18+
| Alpha | `4.12.0-alpha.1` | Alpha opt-in users | `alpha.yml` |
19+
20+
**Channel hierarchy**: Alpha users receive alpha, beta, AND stable updates. Beta users receive beta AND stable. Stable users only receive stable.
21+
22+
## Creating an Alpha Release
23+
24+
### 1. Create Release Branch
25+
26+
```bash
27+
git checkout master
28+
git pull
29+
git checkout -b release/4.12.0-alpha.1
30+
```
31+
32+
### 2. Update Version
33+
34+
Edit `package.json`:
35+
```json
36+
{
37+
"version": "4.12.0-alpha.1"
38+
}
39+
```
40+
41+
### 3. Commit and Push
42+
43+
```bash
44+
git add package.json
45+
git commit -m "chore: bump version to 4.12.0-alpha.1"
46+
git push origin release/4.12.0-alpha.1
47+
```
48+
49+
### 4. Create and Push Tag
50+
51+
```bash
52+
git tag 4.12.0-alpha.1
53+
git push origin 4.12.0-alpha.1
54+
```
55+
56+
### 5. CI Builds Automatically
57+
58+
The GitHub Actions workflow triggers on tag push and:
59+
- Builds for all platforms (Windows, macOS, Linux)
60+
- Generates `alpha.yml`, `alpha-mac.yml`, `alpha-linux.yml` metadata
61+
- Creates a draft GitHub release marked as **Pre-release**
62+
- Publishes Linux snap to the `edge` channel
63+
64+
### 6. Publish the Release
65+
66+
1. Go to GitHub Releases
67+
2. Find the draft release for your version
68+
3. Review the release notes
69+
4. Click "Publish release"
70+
71+
## How Users Opt Into Alpha
72+
73+
### Option A: Developer Mode (Recommended for QA)
74+
75+
1. Open Settings in the app
76+
2. Enable **Developer Mode**
77+
3. Open **About** dialog (Help > About)
78+
4. Select **Alpha (Experimental)** from the Update Channel dropdown
79+
5. Click **Check for Updates**
80+
81+
The setting persists - users don't need to select it again.
82+
83+
### Option B: Configuration File (For Managed Deployments)
84+
85+
Create `update.json` in the user data directory:
86+
87+
| Platform | Location |
88+
|----------|----------|
89+
| Windows | `%APPDATA%\Rocket.Chat\update.json` |
90+
| macOS | `~/Library/Application Support/Rocket.Chat/update.json` |
91+
| Linux | `~/.config/Rocket.Chat/update.json` |
92+
93+
Content:
94+
```json
95+
{
96+
"channel": "alpha"
97+
}
98+
```
99+
100+
For enterprise deployments where you want to force the setting:
101+
```json
102+
{
103+
"channel": "alpha",
104+
"forced": true
105+
}
106+
```
107+
108+
## Version Numbering Guidelines
109+
110+
- **Alpha**: `4.12.0-alpha.1`, `4.12.0-alpha.2`, etc.
111+
- **Beta**: `4.12.0-beta.1`, `4.12.0-beta.2`, etc.
112+
- **Stable**: `4.12.0`
113+
114+
When promoting:
115+
- Alpha `4.12.0-alpha.5` → Beta `4.12.0-beta.1`
116+
- Beta `4.12.0-beta.3` → Stable `4.12.0`
117+
118+
## Safety Guarantees
119+
120+
- Stable users **never** see alpha releases (they check `latest.yml`, not `alpha.yml`)
121+
- Users must explicitly opt into alpha channel
122+
- Alpha releases are marked as "Pre-release" on GitHub
123+
- Users can switch back to stable at any time
124+
125+
## Troubleshooting
126+
127+
### Alpha update not showing
128+
129+
1. Verify the release is published (not draft)
130+
2. Check that `alpha.yml` exists in the release assets
131+
3. Ensure user has selected "Alpha" channel
132+
4. Check for updates manually via About dialog
133+
134+
### Checking current channel
135+
136+
In Developer Mode, open About dialog - the current channel is shown in the dropdown.

electron-builder.json

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"category": "public.app-category.productivity",
1212
"target": ["dmg", "pkg", "zip", "mas"],
1313
"icon": "build/icon.icns",
14-
"bundleVersion": "26010",
14+
"bundleVersion": "26011",
1515
"helperBundleId": "chat.rocket.electron.helper",
1616
"type": "distribution",
1717
"artifactName": "rocketchat-${version}-${os}.${ext}",
@@ -131,13 +131,8 @@
131131
"artifactName": "rocketchat-${version}-${os}-${arch}.${ext}"
132132
},
133133
"deb": {
134-
"fpm": [
135-
"--after-install=build/linux/postinst.sh"
136-
],
137-
"recommends": [
138-
"xdg-desktop-portal",
139-
"xdg-desktop-portal-gtk"
140-
]
134+
"fpm": ["--after-install=build/linux/postinst.sh"],
135+
"recommends": ["xdg-desktop-portal", "xdg-desktop-portal-gtk"]
141136
},
142137
"rpm": {
143138
"fpm": [

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"productName": "Rocket.Chat",
77
"name": "rocketchat",
88
"description": "Official OSX, Windows, and Linux Desktop Clients for Rocket.Chat",
9-
"version": "4.11.1",
9+
"version": "4.12.0-alpha.1",
1010
"author": "Rocket.Chat Support <support@rocket.chat>",
1111
"copyright": "© 2016-2026, Rocket.Chat",
1212
"homepage": "https://rocket.chat",

scripts/release-tag.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { execSync } from 'child_process';
2+
import { createInterface } from 'readline';
3+
import { parse, gt, prerelease, SemVer } from 'semver';
4+
import { readFileSync } from 'fs';
5+
import { join } from 'path';
6+
7+
const REPO_URL = 'https://github.com/RocketChat/Rocket.Chat.Electron';
8+
9+
const getVersion = (): string => {
10+
const packageJson = JSON.parse(
11+
readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
12+
);
13+
return packageJson.version;
14+
};
15+
16+
const getChannel = (version: SemVer): string => {
17+
const pre = prerelease(version);
18+
if (!pre || pre.length === 0) return 'stable';
19+
if (pre[0] === 'alpha') return 'alpha';
20+
if (pre[0] === 'beta') return 'beta';
21+
if (pre[0] === 'rc' || pre[0] === 'candidate') return 'candidate';
22+
return 'prerelease';
23+
};
24+
25+
const exec = (cmd: string): string | null => {
26+
try {
27+
return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
28+
} catch {
29+
return null;
30+
}
31+
};
32+
33+
const fetchTags = (): void => {
34+
console.log('Fetching tags from remote...');
35+
execSync('git fetch --tags', { stdio: 'inherit' });
36+
};
37+
38+
const normalizeTag = (tag: string): string => {
39+
// Strip leading 'v' if present for consistent comparison
40+
return tag.startsWith('v') ? tag.slice(1) : tag;
41+
};
42+
43+
const getExistingTags = (): string[] => {
44+
const output = exec('git tag -l');
45+
if (output === null) {
46+
console.error(' Warning: Failed to list git tags');
47+
return [];
48+
}
49+
if (!output) return [];
50+
// Return normalized tags (without 'v' prefix) for consistent comparison
51+
return output.split('\n').filter(Boolean).map(normalizeTag);
52+
};
53+
54+
const getLatestTagForChannel = (tags: string[], channel: string): SemVer | null => {
55+
const channelTags = tags
56+
.map((tag) => parse(tag))
57+
.filter((v): v is SemVer => v !== null)
58+
.filter((v) => getChannel(v) === channel)
59+
.sort((a, b) => (gt(a, b) ? -1 : 1));
60+
61+
return channelTags[0] || null;
62+
};
63+
64+
const prompt = (question: string): Promise<string> => {
65+
const rl = createInterface({
66+
input: process.stdin,
67+
output: process.stdout,
68+
});
69+
70+
return new Promise((resolve) => {
71+
rl.question(question, (answer) => {
72+
rl.close();
73+
resolve(answer.trim().toLowerCase());
74+
});
75+
});
76+
};
77+
78+
const main = async (): Promise<void> => {
79+
console.log('\n Release Tag Creator\n');
80+
81+
// 1. Read version from package.json
82+
const versionString = getVersion();
83+
const version = parse(versionString);
84+
85+
if (!version) {
86+
console.error(`Error: Invalid version in package.json: ${versionString}`);
87+
process.exit(1);
88+
}
89+
90+
// 2. Detect channel
91+
const channel = getChannel(version);
92+
93+
console.log(` Version: ${version.version}`);
94+
console.log(` Channel: ${channel}`);
95+
console.log(` Tag: ${version.version}`);
96+
console.log('');
97+
98+
// 3. Fetch tags
99+
fetchTags();
100+
101+
// 4. Check if tag already exists
102+
const existingTags = getExistingTags();
103+
104+
if (existingTags.includes(version.version)) {
105+
console.error(`\n Error: Tag ${version.version} already exists!`);
106+
console.error(` The version in package.json has already been released.`);
107+
console.error(` Please bump the version before creating a new release.\n`);
108+
process.exit(1);
109+
}
110+
111+
console.log(` Tag does not exist yet`);
112+
113+
// 5. Check if version is newer than latest in channel
114+
const latestInChannel = getLatestTagForChannel(existingTags, channel);
115+
116+
if (latestInChannel && !gt(version, latestInChannel)) {
117+
console.warn(`\n Warning: Version ${version.version} is not greater than`);
118+
console.warn(` the latest ${channel} release (${latestInChannel.version}).`);
119+
console.warn(` This may be intentional, but please verify.\n`);
120+
} else if (latestInChannel) {
121+
console.log(` Latest ${channel}: ${latestInChannel.version}`);
122+
} else {
123+
console.log(` First ${channel} release`);
124+
}
125+
126+
// 6. Show confirmation
127+
console.log('\n This will:');
128+
console.log(` 1. Create git tag: ${version.version}`);
129+
console.log(` 2. Push tag to origin`);
130+
console.log(` 3. Trigger GitHub Actions build-release workflow\n`);
131+
132+
const answer = await prompt(' Proceed? (y/N): ');
133+
134+
if (answer !== 'y' && answer !== 'yes') {
135+
console.log('\n Aborted.\n');
136+
process.exit(0);
137+
}
138+
139+
// 7. Create and push tag
140+
console.log(`\n Creating tag ${version.version}...`);
141+
try {
142+
execSync(`git tag -- ${version.version}`, { stdio: 'inherit' });
143+
} catch {
144+
console.error(` Error: Failed to create tag`);
145+
process.exit(1);
146+
}
147+
148+
console.log(` Pushing tag to origin...`);
149+
try {
150+
execSync(`git push origin refs/tags/${version.version}`, { stdio: 'inherit' });
151+
} catch {
152+
console.error(` Error: Failed to push tag`);
153+
console.error(` The local tag was created. You may need to push it manually.`);
154+
process.exit(1);
155+
}
156+
157+
// 8. Success message
158+
console.log(`\n Tag created and pushed successfully!\n`);
159+
console.log(` Monitor build: ${REPO_URL}/actions`);
160+
console.log(` Releases: ${REPO_URL}/releases\n`);
161+
};
162+
163+
main().catch((error) => {
164+
console.error('Unexpected error:', error);
165+
process.exit(1);
166+
});

0 commit comments

Comments
 (0)