Skip to content

Commit a2edc0b

Browse files
authored
Merge pull request #5 from modelstudioai/dragon/add-desktop-release-workflow
[codex] Automate desktop release version sync
2 parents 0d14ab9 + 3e2a76b commit a2edc0b

5 files changed

Lines changed: 516 additions & 8 deletions

File tree

.github/workflows/desktop-release.yml

Lines changed: 168 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
name: Desktop Release
22

3-
run-name: Desktop release ${{ inputs.tag }}
3+
run-name: Desktop release ${{ inputs.version }}
44

55
on:
66
workflow_dispatch:
77
inputs:
8-
tag:
9-
description: "Release tag to create or update, for example v0.1.0"
8+
version:
9+
description: "Desktop app version to release, for example 0.0.2 or v0.0.2"
1010
required: true
1111
type: string
1212
release_name:
@@ -42,7 +42,7 @@ permissions:
4242
contents: read
4343

4444
concurrency:
45-
group: desktop-release-${{ inputs.tag }}
45+
group: desktop-release-${{ inputs.version }}
4646
cancel-in-progress: false
4747

4848
env:
@@ -51,10 +51,96 @@ env:
5151
QWEN_CODE_VERSION: ${{ inputs.qwen_code_version }}
5252

5353
jobs:
54+
release_metadata:
55+
name: Prepare Release Source
56+
runs-on: ubuntu-latest
57+
timeout-minutes: 10
58+
permissions:
59+
contents: write
60+
outputs:
61+
release_branch: ${{ steps.release-branch.outputs.branch }}
62+
release_ref: ${{ steps.release-branch.outputs.ref }}
63+
tag: ${{ steps.release-version.outputs.tag }}
64+
version: ${{ steps.release-version.outputs.version }}
65+
66+
steps:
67+
- name: Check out source
68+
uses: actions/checkout@v4
69+
with:
70+
fetch-depth: 0
71+
72+
- name: Set up Bun
73+
uses: oven-sh/setup-bun@v2
74+
with:
75+
bun-version: ${{ env.BUN_VERSION }}
76+
77+
- name: Install dependencies
78+
run: bun install --frozen-lockfile
79+
80+
- name: Configure Git user
81+
run: |
82+
git config user.name "github-actions[bot]"
83+
git config user.email "github-actions[bot]@users.noreply.github.com"
84+
85+
- name: Require main for publishing
86+
if: ${{ inputs.dry_run == false }}
87+
env:
88+
SOURCE_REF: ${{ github.ref_name }}
89+
run: |
90+
set -euo pipefail
91+
92+
if [ "$SOURCE_REF" != "main" ]; then
93+
echo "::error::Desktop releases with dry_run=false must be run from main. Current ref: $SOURCE_REF"
94+
exit 1
95+
fi
96+
97+
- name: Bump desktop version
98+
env:
99+
INPUT_VERSION: ${{ inputs.version }}
100+
run: bun run bump-desktop-version "$INPUT_VERSION"
101+
102+
- name: Validate release version
103+
id: release-version
104+
env:
105+
INPUT_VERSION: ${{ inputs.version }}
106+
run: bun run check-release-version --version "$INPUT_VERSION"
107+
108+
- name: Create release branch
109+
id: release-branch
110+
env:
111+
IS_DRY_RUN: ${{ inputs.dry_run }}
112+
RELEASE_TAG: ${{ steps.release-version.outputs.tag }}
113+
run: |
114+
set -euo pipefail
115+
116+
branch="release/desktop-${RELEASE_TAG}"
117+
git switch -c "$branch"
118+
git add package.json apps/electron/package.json packages/shared/package.json
119+
120+
if git diff --staged --quiet; then
121+
echo "No desktop version changes to commit."
122+
else
123+
git commit -m "chore(release): desktop ${RELEASE_TAG}"
124+
fi
125+
126+
echo "branch=$branch" >> "$GITHUB_OUTPUT"
127+
128+
if [ "$IS_DRY_RUN" = "false" ]; then
129+
git push --set-upstream origin "$branch"
130+
echo "ref=$branch" >> "$GITHUB_OUTPUT"
131+
else
132+
echo "Dry run enabled. Skipping release branch push."
133+
echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
134+
fi
135+
54136
build:
55137
name: Build ${{ matrix.name }}
56138
runs-on: ${{ matrix.os }}
57139
timeout-minutes: 90
140+
needs: release_metadata
141+
env:
142+
RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }}
143+
RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }}
58144
strategy:
59145
fail-fast: false
60146
matrix:
@@ -72,6 +158,8 @@ jobs:
72158
steps:
73159
- name: Check out source
74160
uses: actions/checkout@v4
161+
with:
162+
ref: ${{ needs.release_metadata.outputs.release_ref }}
75163

76164
- name: Set up Bun
77165
uses: oven-sh/setup-bun@v2
@@ -87,6 +175,12 @@ jobs:
87175
- name: Install dependencies
88176
run: bun install --frozen-lockfile
89177

178+
- name: Bump desktop version
179+
run: bun run bump-desktop-version "$RELEASE_VERSION"
180+
181+
- name: Confirm release version
182+
run: bun run check-release-version --version "$RELEASE_VERSION"
183+
90184
- name: Build desktop installer
91185
run: ${{ matrix.command }}
92186
env:
@@ -115,10 +209,15 @@ jobs:
115209
name: Publish GitHub Release
116210
runs-on: ubuntu-latest
117211
timeout-minutes: 20
118-
needs: build
212+
needs:
213+
- build
214+
- release_metadata
119215
if: ${{ inputs.dry_run == false }}
120216
permissions:
121217
contents: write
218+
env:
219+
RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }}
220+
RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }}
122221

123222
steps:
124223
- name: Download installer artifacts
@@ -133,8 +232,7 @@ jobs:
133232
RELEASE_DRAFT: ${{ inputs.draft }}
134233
RELEASE_NAME: ${{ inputs.release_name }}
135234
RELEASE_PRERELEASE: ${{ inputs.prerelease }}
136-
RELEASE_TAG: ${{ inputs.tag }}
137-
RELEASE_TARGET: ${{ github.sha }}
235+
RELEASE_TARGET: ${{ needs.release_metadata.outputs.release_ref }}
138236
UPLOAD_CLOBBER: ${{ inputs.clobber }}
139237
run: |
140238
set -euo pipefail
@@ -177,12 +275,71 @@ jobs:
177275
gh release create "${create_args[@]}"
178276
fi
179277
278+
sync-version:
279+
name: Sync Release Version to Main
280+
runs-on: ubuntu-latest
281+
timeout-minutes: 10
282+
needs:
283+
- publish
284+
- release_metadata
285+
if: ${{ inputs.dry_run == false }}
286+
permissions:
287+
contents: write
288+
pull-requests: write
289+
290+
steps:
291+
- name: Create version sync PR
292+
id: version-pr
293+
env:
294+
GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }}
295+
RELEASE_BRANCH: ${{ needs.release_metadata.outputs.release_branch }}
296+
RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }}
297+
run: |
298+
set -euo pipefail
299+
300+
pr_url="$(gh pr list \
301+
--repo "$GITHUB_REPOSITORY" \
302+
--head "$RELEASE_BRANCH" \
303+
--base main \
304+
--json url \
305+
--jq '.[0].url')"
306+
307+
if [ -z "$pr_url" ]; then
308+
pr_url="$(gh pr create \
309+
--repo "$GITHUB_REPOSITORY" \
310+
--base main \
311+
--head "$RELEASE_BRANCH" \
312+
--title "chore(release): desktop ${RELEASE_TAG}" \
313+
--body "Automated desktop release PR for ${RELEASE_TAG}. Syncs desktop package versions on main.")"
314+
fi
315+
316+
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
317+
318+
- name: Enable auto-merge
319+
env:
320+
GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }}
321+
PR_URL: ${{ steps.version-pr.outputs.url }}
322+
RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }}
323+
run: |
324+
set -euo pipefail
325+
326+
gh pr merge "$PR_URL" \
327+
--squash \
328+
--auto \
329+
--delete-branch \
330+
--subject "chore(release): desktop ${RELEASE_TAG} [skip ci]"
331+
180332
dry-run-summary:
181333
name: Dry Run Summary
182334
runs-on: ubuntu-latest
183335
timeout-minutes: 10
184-
needs: build
336+
needs:
337+
- build
338+
- release_metadata
185339
if: ${{ inputs.dry_run }}
340+
env:
341+
RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }}
342+
RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }}
186343

187344
steps:
188345
- name: Download installer artifacts
@@ -208,6 +365,9 @@ jobs:
208365
{
209366
echo "## Desktop release dry run"
210367
echo
368+
echo "Version: $RELEASE_VERSION"
369+
echo "Release tag: $RELEASE_TAG"
370+
echo
211371
echo "Built ${#assets[@]} asset(s). No GitHub Release was created or updated."
212372
echo
213373
echo "| Asset | Size |"

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@
110110
"docs:dev": "cd apps/online-docs && npm install && npx mintlify dev",
111111
"build": "bun run scripts/build.ts",
112112
"release": "bun run scripts/release.ts",
113+
"bump-desktop-version": "bun run scripts/bump-desktop-version.ts",
114+
"check-release-version": "bun run scripts/check-release-version.ts",
113115
"check-version": "bun run scripts/check-version.ts",
114116
"oss:sync": "bun run scripts/oss-sync.ts",
115117
"prepare": "husky"

scripts/bump-desktop-version.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
desktopReleasePackageSources,
3+
normalizeReleaseVersion,
4+
readPackageVersion,
5+
writePackageVersion,
6+
} from './desktop-release-version.ts';
7+
8+
interface ParsedArgs {
9+
dryRun: boolean;
10+
version?: string;
11+
}
12+
13+
interface VersionChange {
14+
currentVersion: string;
15+
path: string;
16+
}
17+
18+
function parseArgs(argv: string[]): ParsedArgs {
19+
const args: ParsedArgs = { dryRun: false };
20+
21+
for (let i = 0; i < argv.length; i += 1) {
22+
const arg = argv[i];
23+
const next = argv[i + 1];
24+
25+
if (arg === '--dry-run') {
26+
args.dryRun = true;
27+
continue;
28+
}
29+
30+
if (arg === '--version' || arg === '-v') {
31+
if (!next) throw new Error(`${arg} requires a value.`);
32+
if (args.version) throw new Error('Only one release version is allowed.');
33+
args.version = next;
34+
i += 1;
35+
continue;
36+
}
37+
38+
if (arg.startsWith('-')) {
39+
throw new Error(`Unknown argument: ${arg}`);
40+
}
41+
42+
if (args.version) {
43+
throw new Error('Only one release version is allowed.');
44+
}
45+
args.version = arg;
46+
}
47+
48+
return args;
49+
}
50+
51+
function printUsage(): void {
52+
console.error(
53+
[
54+
'Usage: bun run bump-desktop-version <version>',
55+
' bun run bump-desktop-version --dry-run <version>',
56+
'',
57+
'Examples:',
58+
' bun run bump-desktop-version 0.0.2',
59+
' bun run bump-desktop-version v0.0.2',
60+
].join('\n'),
61+
);
62+
}
63+
64+
function main(): void {
65+
const args = parseArgs(process.argv.slice(2));
66+
if (!args.version) {
67+
printUsage();
68+
throw new Error('Release version is required.');
69+
}
70+
71+
const { tag, version } = normalizeReleaseVersion(args.version);
72+
const changes: VersionChange[] = desktopReleasePackageSources.map(
73+
(source) => ({
74+
currentVersion: readPackageVersion(source.path),
75+
path: source.path,
76+
}),
77+
);
78+
const pendingChanges = changes.filter(
79+
(change) => change.currentVersion !== version,
80+
);
81+
82+
if (args.dryRun) {
83+
console.log(`Desktop version dry run: ${version} (${tag})`);
84+
for (const change of changes) {
85+
const suffix =
86+
change.currentVersion === version
87+
? 'already set'
88+
: `${change.currentVersion} -> ${version}`;
89+
console.log(` - ${change.path}: ${suffix}`);
90+
}
91+
console.log('No files were changed.');
92+
return;
93+
}
94+
95+
for (const change of pendingChanges) {
96+
writePackageVersion(change.path, version);
97+
}
98+
99+
if (pendingChanges.length === 0) {
100+
console.log(`Desktop version already set to ${version} (${tag})`);
101+
return;
102+
}
103+
104+
console.log(`Updated desktop version to ${version} (${tag})`);
105+
for (const change of pendingChanges) {
106+
console.log(` - ${change.path}: ${change.currentVersion} -> ${version}`);
107+
}
108+
}
109+
110+
try {
111+
main();
112+
} catch (error) {
113+
console.error(error instanceof Error ? error.message : error);
114+
process.exit(1);
115+
}

0 commit comments

Comments
 (0)