From 00f638d77c70b4e1ebe7e8d18d99deabcd26e042 Mon Sep 17 00:00:00 2001 From: DragonnZhang <731557579@qq.com> Date: Mon, 8 Jun 2026 17:04:51 +0800 Subject: [PATCH 1/3] ci: validate desktop release version --- .github/workflows/desktop-release.yml | 59 +++++++- package.json | 1 + scripts/check-release-version.ts | 196 ++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 scripts/check-release-version.ts diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 72ab45081..992d053ce 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -1,12 +1,12 @@ name: Desktop Release -run-name: Desktop release ${{ inputs.tag }} +run-name: Desktop release ${{ inputs.version }} on: workflow_dispatch: inputs: - tag: - description: "Release tag to create or update, for example v0.1.0" + version: + description: "Desktop app version to release, for example 0.0.2 or v0.0.2" required: true type: string release_name: @@ -42,7 +42,7 @@ permissions: contents: read concurrency: - group: desktop-release-${{ inputs.tag }} + group: desktop-release-${{ inputs.version }} cancel-in-progress: false env: @@ -51,10 +51,40 @@ env: QWEN_CODE_VERSION: ${{ inputs.qwen_code_version }} jobs: + release_metadata: + name: Validate Release Version + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + tag: ${{ steps.release-version.outputs.tag }} + version: ${{ steps.release-version.outputs.version }} + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Validate release version + id: release-version + env: + INPUT_VERSION: ${{ inputs.version }} + run: bun run check-release-version --version "$INPUT_VERSION" + build: name: Build ${{ matrix.name }} runs-on: ${{ matrix.os }} timeout-minutes: 90 + needs: release_metadata + env: + RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} + RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} strategy: fail-fast: false matrix: @@ -87,6 +117,9 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Confirm release version + run: bun run check-release-version --version "$RELEASE_VERSION" + - name: Build desktop installer run: ${{ matrix.command }} env: @@ -115,10 +148,15 @@ jobs: name: Publish GitHub Release runs-on: ubuntu-latest timeout-minutes: 20 - needs: build + needs: + - build + - release_metadata if: ${{ inputs.dry_run == false }} permissions: contents: write + env: + RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} + RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} steps: - name: Download installer artifacts @@ -133,7 +171,6 @@ jobs: RELEASE_DRAFT: ${{ inputs.draft }} RELEASE_NAME: ${{ inputs.release_name }} RELEASE_PRERELEASE: ${{ inputs.prerelease }} - RELEASE_TAG: ${{ inputs.tag }} RELEASE_TARGET: ${{ github.sha }} UPLOAD_CLOBBER: ${{ inputs.clobber }} run: | @@ -181,8 +218,13 @@ jobs: name: Dry Run Summary runs-on: ubuntu-latest timeout-minutes: 10 - needs: build + needs: + - build + - release_metadata if: ${{ inputs.dry_run }} + env: + RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} + RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} steps: - name: Download installer artifacts @@ -208,6 +250,9 @@ jobs: { echo "## Desktop release dry run" echo + echo "Version: $RELEASE_VERSION" + echo "Release tag: $RELEASE_TAG" + echo echo "Built ${#assets[@]} asset(s). No GitHub Release was created or updated." echo echo "| Asset | Size |" diff --git a/package.json b/package.json index cbad1d43c..becb45740 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "docs:dev": "cd apps/online-docs && npm install && npx mintlify dev", "build": "bun run scripts/build.ts", "release": "bun run scripts/release.ts", + "check-release-version": "bun run scripts/check-release-version.ts", "check-version": "bun run scripts/check-version.ts", "oss:sync": "bun run scripts/oss-sync.ts", "prepare": "husky" diff --git a/scripts/check-release-version.ts b/scripts/check-release-version.ts new file mode 100644 index 000000000..fccb9ca27 --- /dev/null +++ b/scripts/check-release-version.ts @@ -0,0 +1,196 @@ +import { appendFileSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { valid } from 'semver'; + +interface PackageVersionSource { + label: string; + path: string; +} + +interface ParsedArgs { + githubOutput?: string; + githubSummary?: string; + version?: string; +} + +const repoRoot = join(import.meta.dir, '..'); +const packageVersionSources: PackageVersionSource[] = [ + { label: 'root package', path: 'package.json' }, + { label: 'Electron app package', path: 'apps/electron/package.json' }, + { label: 'shared package', path: 'packages/shared/package.json' }, +]; + +function parseArgs(argv: string[]): ParsedArgs { + const args: ParsedArgs = { + githubOutput: process.env.GITHUB_OUTPUT || undefined, + githubSummary: process.env.GITHUB_STEP_SUMMARY || undefined, + version: process.env.RELEASE_VERSION || undefined, + }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + const next = argv[i + 1]; + + if (arg === '--version' || arg === '-v') { + if (!next) throw new Error(`${arg} requires a value.`); + args.version = next; + i += 1; + continue; + } + + if (arg === '--github-output') { + if (!next) throw new Error(`${arg} requires a value.`); + args.githubOutput = next; + i += 1; + continue; + } + + if (arg === '--github-summary') { + if (!next) throw new Error(`${arg} requires a value.`); + args.githubSummary = next; + i += 1; + continue; + } + + throw new Error(`Unknown argument: ${arg}`); + } + + return args; +} + +function normalizeReleaseVersion(input: string): { + tag: string; + version: string; +} { + const raw = input.trim(); + if (!raw) { + throw new Error('Release version is required.'); + } + + const refPrefix = 'refs/tags/'; + const tag = raw.startsWith(refPrefix) ? raw.slice(refPrefix.length) : raw; + const candidate = tag.startsWith('v') ? tag.slice(1) : tag; + const version = valid(candidate); + + if (!version) { + throw new Error( + `Invalid release version "${input}". Use SemVer like 0.0.2 or v0.0.2.`, + ); + } + + if (version.includes('+')) { + throw new Error( + `Release version "${input}" includes build metadata, which is not supported for desktop releases.`, + ); + } + + return { + tag: `v${version}`, + version, + }; +} + +function readPackageVersion(path: string): string { + const absolutePath = join(repoRoot, path); + const packageJson = JSON.parse(readFileSync(absolutePath, 'utf-8')) as { + version?: unknown; + }; + + if (typeof packageJson.version !== 'string' || !packageJson.version.trim()) { + throw new Error(`${path} does not define a package version.`); + } + + return packageJson.version.trim(); +} + +function appendGithubOutput( + outputPath: string | undefined, + outputs: Record, +): void { + if (!outputPath) return; + + const lines = Object.entries(outputs).map(([key, value]) => `${key}=${value}`); + appendFileSync(outputPath, `${lines.join('\n')}\n`); +} + +function appendGithubSummary( + summaryPath: string | undefined, + params: { + mismatches: { actual: string; source: PackageVersionSource }[]; + packageVersions: { source: PackageVersionSource; version: string }[]; + tag: string; + version: string; + }, +): void { + if (!summaryPath) return; + + const lines = [ + '## Desktop release version', + '', + `Version: ${params.version}`, + `Release tag: ${params.tag}`, + '', + '| Source | Version |', + '| --- | --- |', + ...params.packageVersions.map( + ({ source, version }) => `| ${source.path} | ${version} |`, + ), + ]; + + if (params.mismatches.length > 0) { + lines.push('', 'Version mismatch detected. Update source versions first.'); + } + + appendFileSync(summaryPath, `${lines.join('\n')}\n`); +} + +function main(): void { + const args = parseArgs(process.argv.slice(2)); + if (!args.version) { + throw new Error( + 'Release version is required. Pass --version or RELEASE_VERSION.', + ); + } + + const { tag, version } = normalizeReleaseVersion(args.version); + const packageVersions = packageVersionSources.map((source) => ({ + source, + version: readPackageVersion(source.path), + })); + const mismatches = packageVersions + .filter((entry) => entry.version !== version) + .map((entry) => ({ + actual: entry.version, + source: entry.source, + })); + + appendGithubSummary(args.githubSummary, { + mismatches, + packageVersions, + tag, + version, + }); + + if (mismatches.length > 0) { + const details = mismatches + .map(({ actual, source }) => ` - ${source.path}: ${actual}`) + .join('\n'); + throw new Error( + [ + `Release version mismatch. Requested ${version}, but source versions differ:`, + details, + 'Update package.json, apps/electron/package.json, and packages/shared/package.json before releasing.', + ].join('\n'), + ); + } + + appendGithubOutput(args.githubOutput, { tag, version }); + console.log(`Release version OK: ${version} (${tag})`); +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +} From 1b7175c1abde1b8eed286e76a4ec87838baa158c Mon Sep 17 00:00:00 2001 From: DragonnZhang <731557579@qq.com> Date: Mon, 8 Jun 2026 17:25:42 +0800 Subject: [PATCH 2/3] chore: add desktop version bump script --- package.json | 1 + scripts/bump-desktop-version.ts | 115 +++++++++++++++++++++++++++++ scripts/check-release-version.ts | 68 ++--------------- scripts/desktop-release-version.ts | 87 ++++++++++++++++++++++ 4 files changed, 211 insertions(+), 60 deletions(-) create mode 100644 scripts/bump-desktop-version.ts create mode 100644 scripts/desktop-release-version.ts diff --git a/package.json b/package.json index becb45740..3c71dec0f 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "docs:dev": "cd apps/online-docs && npm install && npx mintlify dev", "build": "bun run scripts/build.ts", "release": "bun run scripts/release.ts", + "bump-desktop-version": "bun run scripts/bump-desktop-version.ts", "check-release-version": "bun run scripts/check-release-version.ts", "check-version": "bun run scripts/check-version.ts", "oss:sync": "bun run scripts/oss-sync.ts", diff --git a/scripts/bump-desktop-version.ts b/scripts/bump-desktop-version.ts new file mode 100644 index 000000000..b4a316ed7 --- /dev/null +++ b/scripts/bump-desktop-version.ts @@ -0,0 +1,115 @@ +import { + desktopReleasePackageSources, + normalizeReleaseVersion, + readPackageVersion, + writePackageVersion, +} from './desktop-release-version.ts'; + +interface ParsedArgs { + dryRun: boolean; + version?: string; +} + +interface VersionChange { + currentVersion: string; + path: string; +} + +function parseArgs(argv: string[]): ParsedArgs { + const args: ParsedArgs = { dryRun: false }; + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + const next = argv[i + 1]; + + if (arg === '--dry-run') { + args.dryRun = true; + continue; + } + + if (arg === '--version' || arg === '-v') { + if (!next) throw new Error(`${arg} requires a value.`); + if (args.version) throw new Error('Only one release version is allowed.'); + args.version = next; + i += 1; + continue; + } + + if (arg.startsWith('-')) { + throw new Error(`Unknown argument: ${arg}`); + } + + if (args.version) { + throw new Error('Only one release version is allowed.'); + } + args.version = arg; + } + + return args; +} + +function printUsage(): void { + console.error( + [ + 'Usage: bun run bump-desktop-version ', + ' bun run bump-desktop-version --dry-run ', + '', + 'Examples:', + ' bun run bump-desktop-version 0.0.2', + ' bun run bump-desktop-version v0.0.2', + ].join('\n'), + ); +} + +function main(): void { + const args = parseArgs(process.argv.slice(2)); + if (!args.version) { + printUsage(); + throw new Error('Release version is required.'); + } + + const { tag, version } = normalizeReleaseVersion(args.version); + const changes: VersionChange[] = desktopReleasePackageSources.map( + (source) => ({ + currentVersion: readPackageVersion(source.path), + path: source.path, + }), + ); + const pendingChanges = changes.filter( + (change) => change.currentVersion !== version, + ); + + if (args.dryRun) { + console.log(`Desktop version dry run: ${version} (${tag})`); + for (const change of changes) { + const suffix = + change.currentVersion === version + ? 'already set' + : `${change.currentVersion} -> ${version}`; + console.log(` - ${change.path}: ${suffix}`); + } + console.log('No files were changed.'); + return; + } + + for (const change of pendingChanges) { + writePackageVersion(change.path, version); + } + + if (pendingChanges.length === 0) { + console.log(`Desktop version already set to ${version} (${tag})`); + return; + } + + console.log(`Updated desktop version to ${version} (${tag})`); + for (const change of pendingChanges) { + console.log(` - ${change.path}: ${change.currentVersion} -> ${version}`); + } +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +} diff --git a/scripts/check-release-version.ts b/scripts/check-release-version.ts index fccb9ca27..50232ebbb 100644 --- a/scripts/check-release-version.ts +++ b/scripts/check-release-version.ts @@ -1,11 +1,11 @@ -import { appendFileSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { valid } from 'semver'; +import { appendFileSync } from 'node:fs'; -interface PackageVersionSource { - label: string; - path: string; -} +import { + desktopReleasePackageSources, + normalizeReleaseVersion, + readPackageVersion, + type PackageVersionSource, +} from './desktop-release-version.ts'; interface ParsedArgs { githubOutput?: string; @@ -13,13 +13,6 @@ interface ParsedArgs { version?: string; } -const repoRoot = join(import.meta.dir, '..'); -const packageVersionSources: PackageVersionSource[] = [ - { label: 'root package', path: 'package.json' }, - { label: 'Electron app package', path: 'apps/electron/package.json' }, - { label: 'shared package', path: 'packages/shared/package.json' }, -]; - function parseArgs(argv: string[]): ParsedArgs { const args: ParsedArgs = { githubOutput: process.env.GITHUB_OUTPUT || undefined, @@ -58,51 +51,6 @@ function parseArgs(argv: string[]): ParsedArgs { return args; } -function normalizeReleaseVersion(input: string): { - tag: string; - version: string; -} { - const raw = input.trim(); - if (!raw) { - throw new Error('Release version is required.'); - } - - const refPrefix = 'refs/tags/'; - const tag = raw.startsWith(refPrefix) ? raw.slice(refPrefix.length) : raw; - const candidate = tag.startsWith('v') ? tag.slice(1) : tag; - const version = valid(candidate); - - if (!version) { - throw new Error( - `Invalid release version "${input}". Use SemVer like 0.0.2 or v0.0.2.`, - ); - } - - if (version.includes('+')) { - throw new Error( - `Release version "${input}" includes build metadata, which is not supported for desktop releases.`, - ); - } - - return { - tag: `v${version}`, - version, - }; -} - -function readPackageVersion(path: string): string { - const absolutePath = join(repoRoot, path); - const packageJson = JSON.parse(readFileSync(absolutePath, 'utf-8')) as { - version?: unknown; - }; - - if (typeof packageJson.version !== 'string' || !packageJson.version.trim()) { - throw new Error(`${path} does not define a package version.`); - } - - return packageJson.version.trim(); -} - function appendGithubOutput( outputPath: string | undefined, outputs: Record, @@ -153,7 +101,7 @@ function main(): void { } const { tag, version } = normalizeReleaseVersion(args.version); - const packageVersions = packageVersionSources.map((source) => ({ + const packageVersions = desktopReleasePackageSources.map((source) => ({ source, version: readPackageVersion(source.path), })); diff --git a/scripts/desktop-release-version.ts b/scripts/desktop-release-version.ts new file mode 100644 index 000000000..6e0b4fd91 --- /dev/null +++ b/scripts/desktop-release-version.ts @@ -0,0 +1,87 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { valid } from 'semver'; + +export interface PackageVersionSource { + label: string; + path: string; +} + +export interface NormalizedReleaseVersion { + tag: string; + version: string; +} + +type PackageJson = Record; + +const repoRoot = join(import.meta.dir, '..'); + +export const desktopReleasePackageSources: PackageVersionSource[] = [ + { label: 'root package', path: 'package.json' }, + { label: 'Electron app package', path: 'apps/electron/package.json' }, + { label: 'shared package', path: 'packages/shared/package.json' }, +]; + +export function normalizeReleaseVersion( + input: string, +): NormalizedReleaseVersion { + const raw = input.trim(); + if (!raw) { + throw new Error('Release version is required.'); + } + + const refPrefix = 'refs/tags/'; + const tag = raw.startsWith(refPrefix) ? raw.slice(refPrefix.length) : raw; + const candidate = tag.startsWith('v') ? tag.slice(1) : tag; + const version = valid(candidate); + + if (!version) { + throw new Error( + `Invalid release version "${input}". Use SemVer like 0.0.2 or v0.0.2.`, + ); + } + + if (version.includes('+')) { + throw new Error( + `Release version "${input}" includes build metadata, which is not supported for desktop releases.`, + ); + } + + return { + tag: `v${version}`, + version, + }; +} + +export function readPackageJson(path: string): PackageJson { + const absolutePath = join(repoRoot, path); + const parsed = JSON.parse(readFileSync(absolutePath, 'utf-8')) as unknown; + + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error(`${path} is not a valid package.json object.`); + } + + return parsed as PackageJson; +} + +export function readPackageVersion(path: string): string { + const packageJson = readPackageJson(path); + + if ( + typeof packageJson.version !== 'string' || + !packageJson.version.trim() + ) { + throw new Error(`${path} does not define a package version.`); + } + + return packageJson.version.trim(); +} + +export function writePackageVersion(path: string, version: string): void { + const packageJson = readPackageJson(path); + packageJson.version = version; + writeFileSync( + join(repoRoot, path), + `${JSON.stringify(packageJson, null, 2)}\n`, + ); +} From 3e2a76bab37035746f5e3a5724b771061b896be9 Mon Sep 17 00:00:00 2001 From: DragonnZhang <731557579@qq.com> Date: Mon, 8 Jun 2026 17:41:09 +0800 Subject: [PATCH 3/3] ci: automate desktop release version sync --- .github/workflows/desktop-release.yml | 119 +++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 992d053ce..39059aaad 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -52,16 +52,22 @@ env: jobs: release_metadata: - name: Validate Release Version + name: Prepare Release Source runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: write outputs: + release_branch: ${{ steps.release-branch.outputs.branch }} + release_ref: ${{ steps.release-branch.outputs.ref }} tag: ${{ steps.release-version.outputs.tag }} version: ${{ steps.release-version.outputs.version }} steps: - name: Check out source uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Bun uses: oven-sh/setup-bun@v2 @@ -71,12 +77,62 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Configure Git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Require main for publishing + if: ${{ inputs.dry_run == false }} + env: + SOURCE_REF: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ "$SOURCE_REF" != "main" ]; then + echo "::error::Desktop releases with dry_run=false must be run from main. Current ref: $SOURCE_REF" + exit 1 + fi + + - name: Bump desktop version + env: + INPUT_VERSION: ${{ inputs.version }} + run: bun run bump-desktop-version "$INPUT_VERSION" + - name: Validate release version id: release-version env: INPUT_VERSION: ${{ inputs.version }} run: bun run check-release-version --version "$INPUT_VERSION" + - name: Create release branch + id: release-branch + env: + IS_DRY_RUN: ${{ inputs.dry_run }} + RELEASE_TAG: ${{ steps.release-version.outputs.tag }} + run: | + set -euo pipefail + + branch="release/desktop-${RELEASE_TAG}" + git switch -c "$branch" + git add package.json apps/electron/package.json packages/shared/package.json + + if git diff --staged --quiet; then + echo "No desktop version changes to commit." + else + git commit -m "chore(release): desktop ${RELEASE_TAG}" + fi + + echo "branch=$branch" >> "$GITHUB_OUTPUT" + + if [ "$IS_DRY_RUN" = "false" ]; then + git push --set-upstream origin "$branch" + echo "ref=$branch" >> "$GITHUB_OUTPUT" + else + echo "Dry run enabled. Skipping release branch push." + echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT" + fi + build: name: Build ${{ matrix.name }} runs-on: ${{ matrix.os }} @@ -102,6 +158,8 @@ jobs: steps: - name: Check out source uses: actions/checkout@v4 + with: + ref: ${{ needs.release_metadata.outputs.release_ref }} - name: Set up Bun uses: oven-sh/setup-bun@v2 @@ -117,6 +175,9 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Bump desktop version + run: bun run bump-desktop-version "$RELEASE_VERSION" + - name: Confirm release version run: bun run check-release-version --version "$RELEASE_VERSION" @@ -171,7 +232,7 @@ jobs: RELEASE_DRAFT: ${{ inputs.draft }} RELEASE_NAME: ${{ inputs.release_name }} RELEASE_PRERELEASE: ${{ inputs.prerelease }} - RELEASE_TARGET: ${{ github.sha }} + RELEASE_TARGET: ${{ needs.release_metadata.outputs.release_ref }} UPLOAD_CLOBBER: ${{ inputs.clobber }} run: | set -euo pipefail @@ -214,6 +275,60 @@ jobs: gh release create "${create_args[@]}" fi + sync-version: + name: Sync Release Version to Main + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: + - publish + - release_metadata + if: ${{ inputs.dry_run == false }} + permissions: + contents: write + pull-requests: write + + steps: + - name: Create version sync PR + id: version-pr + env: + GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }} + RELEASE_BRANCH: ${{ needs.release_metadata.outputs.release_branch }} + RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} + run: | + set -euo pipefail + + pr_url="$(gh pr list \ + --repo "$GITHUB_REPOSITORY" \ + --head "$RELEASE_BRANCH" \ + --base main \ + --json url \ + --jq '.[0].url')" + + if [ -z "$pr_url" ]; then + pr_url="$(gh pr create \ + --repo "$GITHUB_REPOSITORY" \ + --base main \ + --head "$RELEASE_BRANCH" \ + --title "chore(release): desktop ${RELEASE_TAG}" \ + --body "Automated desktop release PR for ${RELEASE_TAG}. Syncs desktop package versions on main.")" + fi + + echo "url=$pr_url" >> "$GITHUB_OUTPUT" + + - name: Enable auto-merge + env: + GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }} + PR_URL: ${{ steps.version-pr.outputs.url }} + RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} + run: | + set -euo pipefail + + gh pr merge "$PR_URL" \ + --squash \ + --auto \ + --delete-branch \ + --subject "chore(release): desktop ${RELEASE_TAG} [skip ci]" + dry-run-summary: name: Dry Run Summary runs-on: ubuntu-latest