From 205ba60b354e39c7303bf0e7c7dfd4750dec1ad1 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Thu, 13 Aug 2026 17:07:42 +0800 Subject: [PATCH 1/2] fix(desktop): preserve Electron to Tauri upgrades --- .../create-electron-bridge-manifest.mjs | 40 ++++--- .github/workflows/desktop-build.yml | 42 +++++++- .github/workflows/desktop-release.yml | 16 +++ ...desktop-electron-to-tauri-update-bridge.md | 64 ++--------- packages/desktop-shell/package-lock.json | 4 +- packages/desktop-shell/package.json | 2 +- .../desktop-shell/scripts/test-release.js | 102 +++++++++++++++++- packages/desktop-shell/src-tauri/Cargo.lock | 2 +- packages/desktop-shell/src-tauri/Cargo.toml | 2 +- .../desktop-shell/src-tauri/tauri.conf.json | 5 +- .../src-tauri/windows/electron-migration.nsh | 14 +++ 11 files changed, 215 insertions(+), 78 deletions(-) create mode 100644 packages/desktop-shell/src-tauri/windows/electron-migration.nsh diff --git a/.github/scripts/create-electron-bridge-manifest.mjs b/.github/scripts/create-electron-bridge-manifest.mjs index e143d4bcc..90ddc5ef4 100644 --- a/.github/scripts/create-electron-bridge-manifest.mjs +++ b/.github/scripts/create-electron-bridge-manifest.mjs @@ -6,15 +6,24 @@ import path from 'node:path'; const options = parseArguments(process.argv.slice(2)); const assets = fs.readdirSync(options.assets).sort(); -const names = [ - 'Qwen-Code-Desktop-arm64.zip', - 'Qwen-Code-Desktop-x64.zip', - 'Qwen-Code-Desktop-arm64.dmg', - 'Qwen-Code-Desktop-x64.dmg', -]; -const artifacts = names.map((name) => readArtifact(assets, name)); +const patterns = { + macos: [ + /[-_]arm64\.zip$/i, + /[-_]x64\.zip$/i, + /[-_]arm64\.dmg$/i, + /[-_]x64\.dmg$/i, + ], + windows: [/-setup\.exe$/i], + linux: [/\.AppImage$/i], +}; +const selectedPatterns = patterns[options.platform]; +if (!selectedPatterns) { + throw new Error(`Invalid --platform: ${options.platform}`); +} +const artifacts = selectedPatterns.map((pattern) => + readArtifact(selectArtifact(assets, pattern)), +); const primary = artifacts[0]; - const lines = [ `version: ${options.version}`, 'files:', @@ -29,10 +38,17 @@ const lines = [ ]; fs.writeFileSync(options.output, `${lines.join('\n')}\n`); -function readArtifact(assets, name) { - if (!assets.includes(name)) { - throw new Error(`Missing Electron bridge artifact: ${name}`); +function selectArtifact(assets, pattern) { + const matches = assets.filter((asset) => pattern.test(asset)); + if (matches.length !== 1) { + throw new Error( + `Expected one Electron bridge artifact matching ${pattern}, found ${matches.length}: ${matches.join(', ')}`, + ); } + return matches[0]; +} + +function readArtifact(name) { const file = path.join(options.assets, name); return { name, @@ -52,7 +68,7 @@ function parseArguments(args) { if (!name || value === undefined) throw new Error('Invalid arguments.'); values[name] = value; } - for (const required of ['assets', 'version', 'output']) { + for (const required of ['assets', 'platform', 'version', 'output']) { if (!values[required]) throw new Error(`Missing --${required}`); } if ( diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 4f11383f9..9c2b1eefc 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -12,6 +12,9 @@ on: tag: required: true type: 'string' + electron_bridge: + required: true + type: 'boolean' publish: required: true type: 'boolean' @@ -34,9 +37,11 @@ jobs: - name: 'macOS Apple Silicon' os: 'macos-15' target: 'aarch64-apple-darwin' + legacy_arch: 'arm64' - name: 'macOS Intel' os: 'macos-15-intel' target: 'x86_64-apple-darwin' + legacy_arch: 'x64' - name: 'Windows x64' os: 'windows-2025' target: 'x86_64-pc-windows-msvc' @@ -234,6 +239,23 @@ jobs: $signature = Get-AuthenticodeSignature $installer.FullName if ($signature.Status -ne 'Valid') { throw "Invalid Authenticode signature: $($signature.Status)" } + - name: 'Create Electron bridge archive' + if: "runner.os == 'macOS' && inputs.electron_bridge" + shell: 'bash' + env: + LEGACY_ARCH: '${{ matrix.legacy_arch }}' + RELEASE_VERSION: '${{ inputs.version }}' + run: | + set -euo pipefail + app="$(find packages/desktop-shell/src-tauri/target/${{ matrix.target }}/release/bundle/macos -maxdepth 1 -name 'OpenWork.app' -print -quit)" + if [[ -z "$app" ]]; then + echo '::error::The OpenWork macOS app bundle was not produced.' + exit 1 + fi + destination="packages/desktop-shell/src-tauri/target/${{ matrix.target }}/release/bundle/electron-bridge" + mkdir -p "$destination" + ditto -c -k --sequesterRsrc --keepParent "$app" "$destination/OpenWork_${RELEASE_VERSION}_${LEGACY_ARCH}.zip" + - name: 'Smoke packaged application (macOS)' if: "runner.os == 'macOS'" shell: 'bash' @@ -259,6 +281,9 @@ jobs: - name: 'Collect verified artifacts' shell: 'bash' + env: + LEGACY_ARCH: '${{ matrix.legacy_arch }}' + RELEASE_VERSION: '${{ inputs.version }}' run: | set -euo pipefail destination="$RUNNER_TEMP/openwork-desktop-artifacts" @@ -270,7 +295,8 @@ jobs: case "$name" in *.app.tar.gz.sig) name="${name%.app.tar.gz.sig}-${{ matrix.target }}.app.tar.gz.sig" ;; *.app.tar.gz) name="${name%.app.tar.gz}-${{ matrix.target }}.app.tar.gz" ;; - *.dmg) name="OpenWork-${{ matrix.target }}.dmg" ;; + *.dmg) name="OpenWork_${RELEASE_VERSION}_${LEGACY_ARCH}.dmg" ;; + OpenWork_*.zip) ;; *) continue ;; esac elif [[ "$RUNNER_OS" == 'Windows' ]]; then @@ -279,7 +305,7 @@ jobs: case "$name" in *.AppImage|*.AppImage.sig|*.deb|*.deb.sig) ;; *) continue ;; esac fi cp "$artifact" "$destination/${name// /-}" - done < <(find "$bundle_root" -mindepth 2 -maxdepth 2 -type f \( -name '*.dmg' -o -name '*.AppImage' -o -name '*.deb' -o -name '*.exe' -o -name '*.app.tar.gz' -o -name '*.sig' \) -print0) + done < <(find "$bundle_root" -mindepth 2 -maxdepth 2 -type f \( -name '*.dmg' -o -name '*.AppImage' -o -name '*.deb' -o -name '*.exe' -o -name '*.zip' -o -name '*.app.tar.gz' -o -name '*.sig' \) -print0) if [[ -z "$(find "$destination" -type f -print -quit)" ]]; then echo '::error::No desktop artifacts were produced.' exit 1 @@ -316,23 +342,33 @@ jobs: - name: 'Generate updater manifest and checksums' shell: 'bash' env: + ELECTRON_BRIDGE: '${{ inputs.electron_bridge }}' RELEASE_TAG: '${{ inputs.tag }}' RELEASE_VERSION: '${{ inputs.version }}' run: | set -euo pipefail node .github/scripts/create-desktop-update-manifest.mjs --assets release-assets --repository "$GITHUB_REPOSITORY" --tag "$RELEASE_TAG" --version "$RELEASE_VERSION" --output release-assets/latest.json + if [[ "$ELECTRON_BRIDGE" == 'true' ]]; then + for manifest in macos:latest-mac.yml windows:latest.yml linux:latest-linux.yml; do + platform="${manifest%%:*}" + output="${manifest#*:}" + node .github/scripts/create-electron-bridge-manifest.mjs --assets release-assets --platform "$platform" --version "$RELEASE_VERSION" --output "release-assets/$output" + done + fi (cd release-assets && sha256sum -- * > SHA256SUMS.txt) - name: 'Create GitHub release' env: GH_TOKEN: '${{ github.token }}' + ELECTRON_BRIDGE: '${{ inputs.electron_bridge }}' RELEASE_DRAFT: '${{ inputs.draft }}' RELEASE_NAME: '${{ inputs.release_name }}' RELEASE_PRERELEASE: '${{ inputs.prerelease }}' RELEASE_TAG: '${{ inputs.tag }}' run: | set -euo pipefail - args=("$RELEASE_TAG" release-assets/* --target "$GITHUB_SHA" --title "$RELEASE_NAME" --generate-notes --latest=false) + args=("$RELEASE_TAG" release-assets/* --target "$GITHUB_SHA" --title "$RELEASE_NAME" --generate-notes) + if [[ "$RELEASE_DRAFT" == 'false' && "$RELEASE_PRERELEASE" == 'false' && "$ELECTRON_BRIDGE" == 'true' ]]; then args+=(--latest); else args+=(--latest=false); fi if [[ "$RELEASE_DRAFT" == 'true' ]]; then args+=(--draft); fi if [[ "$RELEASE_PRERELEASE" == 'true' ]]; then args+=(--prerelease); fi gh release create "${args[@]}" diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index bab4f31ae..bd9c480ff 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -13,6 +13,11 @@ on: description: 'Release title. Defaults to openwork-v.' required: false type: 'string' + electron_bridge: + description: 'Publish Electron-compatible update manifests and payloads for macOS, Windows, and Linux.' + required: true + default: true + type: 'boolean' dry_run: description: 'Build installers without publishing a release.' required: true @@ -50,6 +55,7 @@ jobs: name: 'Validate version and source' shell: 'bash' env: + ELECTRON_BRIDGE: '${{ inputs.electron_bridge }}' INPUT_VERSION: '${{ inputs.version }}' INPUT_RELEASE_NAME: '${{ inputs.release_name }}' IS_DRAFT: '${{ inputs.draft }}' @@ -75,6 +81,14 @@ jobs: echo "::error::Published stable releases require an X.Y.Z version: $INPUT_VERSION" exit 1 fi + if [[ "$ELECTRON_BRIDGE" == "true" ]]; then + core="${version%%[-+]*}" + IFS='.' read -r major minor _ <<< "$core" + if [[ "$major" -eq 0 && "$minor" -lt 2 ]]; then + echo "::error::The Electron bridge starts at OpenWork 0.2.0: $INPUT_VERSION" + exit 1 + fi + fi if [[ "$INPUT_RELEASE_NAME" == *$'\n'* || "$INPUT_RELEASE_NAME" == *$'\r'* || ${#INPUT_RELEASE_NAME} -gt 200 ]]; then echo "::error::Release names must be a single line up to 200 characters." exit 1 @@ -97,6 +111,7 @@ jobs: version: '${{ needs.metadata.outputs.version }}' release_name: '${{ needs.metadata.outputs.release_name }}' tag: '${{ needs.metadata.outputs.tag }}' + electron_bridge: '${{ inputs.electron_bridge }}' publish: false draft: '${{ inputs.draft }}' prerelease: '${{ inputs.prerelease }}' @@ -112,6 +127,7 @@ jobs: version: '${{ needs.metadata.outputs.version }}' release_name: '${{ needs.metadata.outputs.release_name }}' tag: '${{ needs.metadata.outputs.tag }}' + electron_bridge: '${{ inputs.electron_bridge }}' publish: true draft: '${{ inputs.draft }}' prerelease: '${{ inputs.prerelease }}' diff --git a/docs/design/desktop-electron-to-tauri-update-bridge.md b/docs/design/desktop-electron-to-tauri-update-bridge.md index bc3a98d6c..c3c8c0c1a 100644 --- a/docs/design/desktop-electron-to-tauri-update-bridge.md +++ b/docs/design/desktop-electron-to-tauri-update-bridge.md @@ -2,65 +2,21 @@ ## Context -The last published desktop release, `desktop-v0.0.5`, is an Electron app named `Qwen Code Desktop` with bundle identifier `com.alibaba.qwen-code`. Its macOS updater reads `latest-mac.yml` from the fixed `desktop-latest` release and installs a ZIP archive. - -The new desktop shell is a Tauri app. It currently uses a different product name and bundle identifier and publishes `desktop-latest.json`, so the existing Electron app cannot discover or replace it. - -## Goals - -- Let signed macOS Electron `0.0.5` installations update directly to the first stable Tauri release. -- Preserve the existing macOS application identity so the updater replaces the installed app bundle. -- Keep Tauri's signed updater feed for all releases after the migration. -- Make the bridge opt-in and one-time; later releases must not need Electron build tooling. - -## Non-goals - -- Migrating Electron settings, sessions, or workspace state. The Tauri app may ask for a workspace on first launch. -- Bridging Windows or Linux Electron installations. -- Generating Electron differential blockmaps. Electron updater falls back to the checksum-verified full ZIP. +OpenWork Electron releases use GitHub's latest stable release and read `latest-mac.yml`, `latest.yml`, or `latest-linux.yml`. Tauri reads `latest.json` from the fixed `desktop-latest` release. A stable Tauri release must therefore publish both update formats, and the versioned release must remain GitHub Latest for legacy clients. ## Compatibility contract -The Tauri bundle uses the legacy macOS identity: - -- product name: `Qwen Code Desktop` -- bundle identifier: `com.alibaba.qwen-code` -- artifact prefix: `Qwen-Code-Desktop` -- signing identity: the existing Developer ID Application certificate - -The bridge release must be newer than `0.0.5`. It publishes two updater views over the same signed app bundles: - -1. `latest-mac.yml` points legacy Electron clients at `Qwen-Code-Desktop-arm64.zip` or `Qwen-Code-Desktop-x64.zip`. -2. `desktop-latest.json` points Tauri clients at the signed Tauri updater archives. - -The ZIP is created from the already signed and notarized `.app`; it is not rebuilt by Electron tooling. - -## Release flow - -`Desktop Release` gains an `electron_bridge` input, disabled by default. - -- All macOS builds continue to produce the Tauri app, DMG, updater archive, and updater signature. -- When `electron_bridge` is enabled, each macOS build also creates a legacy-compatible ZIP. -- The publish job generates `latest-mac.yml` from the two ZIPs and two DMGs. -- A stable bridge release uploads the legacy metadata and payloads to `desktop-latest` together with `desktop-latest.json`. -- Later stable releases leave `electron_bridge` disabled. Updating `desktop-latest.json` does not remove the bridge files, so Electron installations that return later can still cross to Tauri. - -Draft and prerelease runs may build and publish bridge artifacts for inspection, but they never update the stable feed. - -## Signing credentials - -The repository already stores the Electron-era Apple certificate and App Store Connect API key under `MAC_CSC_*` and `APPLE_NOTARY_*` secret names. The workflow accepts those names as fallbacks for the newer Tauri names, so the Developer ID identity remains unchanged. +OpenWork 0.2.0 keeps the Electron product name `OpenWork` and application identifier `com.alibaba.openwork`. With `electron_bridge` enabled, a release contains: -Tauri updater artifacts additionally require `TAURI_SIGNING_PRIVATE_KEY`; `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` is only needed for an encrypted private key. The private key must match the public key in the Tauri configuration before the first published Tauri release. +- `latest-mac.yml` plus versioned ZIP and DMG payloads for Apple Silicon and Intel; +- `latest.yml` plus the x64 NSIS installer for Windows; +- `latest-linux.yml` plus the x64 AppImage for Linux; +- `latest.json` and signed updater archives for Tauri clients. -## Validation +The macOS ZIPs are created from the signed and notarized Tauri app. Windows removes the matching per-user Electron installation through its registered uninstaller before Tauri writes files, preserving user data and avoiding duplicate uninstall entries. Linux AppImage updates replace the current AppImage directly. -Automated release-helper tests verify: +## Release usage -- the legacy application identity, -- exact bridge artifact selection, -- SHA-512 and size values in `latest-mac.yml`, -- failure when a required bridge artifact is missing, -- existing Tauri updater manifest and version synchronization behavior. +`Desktop Release` defaults `electron_bridge` to true. For a stable release, use `dry_run=false`, `draft=false`, and `prerelease=false`. A stable bridge release is marked GitHub Latest and updates the fixed Tauri feed. Keep the bridge enabled on later stable releases while Electron installations remain supported. Once support is intentionally retired, disable it; later Tauri-only releases use `--latest=false`, so the previous bridge release remains GitHub Latest for dormant Electron clients. -Before the stable release, install the signed `desktop-v0.0.5` arm64 and x64 builds, point them at an isolated bridge feed, and verify both `0.0.5 -> Tauri bridge` and `Tauri bridge -> newer Tauri` updates. +Before publishing, verify signed 0.1.4 clients on each platform can install the bridge and that the resulting Tauri app can then update to a newer Tauri release. Retire the bridge only after the legacy support window is explicitly closed. diff --git a/packages/desktop-shell/package-lock.json b/packages/desktop-shell/package-lock.json index bba030b18..373496b05 100644 --- a/packages/desktop-shell/package-lock.json +++ b/packages/desktop-shell/package-lock.json @@ -1,12 +1,12 @@ { "name": "@openwork/desktop-shell", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@openwork/desktop-shell", - "version": "0.1.0", + "version": "0.2.0", "devDependencies": { "@tauri-apps/cli": "^2.8.5" } diff --git a/packages/desktop-shell/package.json b/packages/desktop-shell/package.json index 19fed49ba..05f6cb53b 100644 --- a/packages/desktop-shell/package.json +++ b/packages/desktop-shell/package.json @@ -1,6 +1,6 @@ { "name": "@openwork/desktop-shell", - "version": "0.1.0", + "version": "0.2.0", "private": true, "type": "module", "scripts": { diff --git a/packages/desktop-shell/scripts/test-release.js b/packages/desktop-shell/scripts/test-release.js index 7871bf967..5512325da 100755 --- a/packages/desktop-shell/scripts/test-release.js +++ b/packages/desktop-shell/scripts/test-release.js @@ -1,7 +1,8 @@ #!/usr/bin/env node import assert from 'node:assert/strict'; -import { execFileSync } from 'node:child_process'; +import { execFileSync, spawnSync } from 'node:child_process'; +import crypto from 'node:crypto'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; @@ -12,6 +13,14 @@ const packageDir = path.resolve( '..', ); const versionScript = path.join(packageDir, 'scripts', 'version.js'); +const electronBridgeScript = path.join( + packageDir, + '..', + '..', + '.github', + 'scripts', + 'create-electron-bridge-manifest.mjs', +); const root = fs.mkdtempSync( path.join(os.tmpdir(), 'openwork-desktop-release-test-'), ); @@ -20,6 +29,7 @@ try { testDesktopConfiguration(); testMacosPermissions(); testReleaseWorkflow(); + testElectronBridgeManifest(path.join(root, 'electron-bridge')); testChecksumRefresh(path.join(root, 'checksums')); testVersionSynchronization(path.join(root, 'version')); console.log('OpenWork desktop release contract checks passed.'); @@ -36,7 +46,7 @@ function testDesktopConfiguration() { ); assert.equal(config.productName, 'OpenWork'); assert.equal(config.identifier, 'com.alibaba.openwork'); - assert.equal(config.version, '0.1.0'); + assert.equal(config.version, '0.2.0'); assert.equal(config.build.devUrl, 'http://127.0.0.1:1420'); assert.equal(config.build.frontendDist, '../bootstrap'); assert.equal(config.app?.withGlobalTauri, true); @@ -75,6 +85,22 @@ function testDesktopConfiguration() { scope: ['$HOME/.qwen/pets/**'], }); assert.equal(config.bundle?.createUpdaterArtifacts, false); + assert.equal( + config.bundle?.windows?.nsis?.installerHooks, + 'windows/electron-migration.nsh', + ); + const migrationHook = fs.readFileSync( + path.join(packageDir, 'src-tauri', 'windows', 'electron-migration.nsh'), + 'utf8', + ); + assert.match( + migrationHook, + /Software\\d6bd5575-5bf2-5dad-acfe-35e3bbeefd68/, + ); + assert.match( + migrationHook, + /ExecWait '\"\$R0\\Uninstall OpenWork\.exe\" \/currentuser \/S --updated _\?=\$R0'/, + ); assert.equal( config.bundle?.resources?.['../runtime/openwork'], 'runtime/openwork', @@ -132,6 +158,13 @@ function testReleaseWorkflow() { 'Verify bundled runtime', 'Smoke packaged application', 'create-desktop-update-manifest.mjs', + 'create-electron-bridge-manifest.mjs', + 'macos:latest-mac.yml', + 'windows:latest.yml', + 'linux:latest-linux.yml', + 'electron_bridge', + 'default: true', + 'args+=(--latest)', 'SHA256SUMS.txt', 'desktop-latest', 'Sign bundled runtime binaries (macOS)', @@ -161,6 +194,71 @@ function testReleaseWorkflow() { assert.doesNotMatch(workflow, /push --force|force-with-lease/); } +function testElectronBridgeManifest(directory) { + const assets = path.join(directory, 'assets'); + fs.mkdirSync(assets, { recursive: true }); + const artifacts = [ + 'OpenWork_0.2.0_arm64.zip', + 'OpenWork_0.2.0_x64.zip', + 'OpenWork_0.2.0_arm64.dmg', + 'OpenWork_0.2.0_x64.dmg', + 'OpenWork_0.2.0_x64-setup.exe', + 'OpenWork_0.2.0_amd64.AppImage', + ]; + for (const artifact of artifacts) { + fs.writeFileSync(path.join(assets, artifact), `contents:${artifact}`); + } + for (const [platform, filename, selected] of [ + ['macos', 'latest-mac.yml', artifacts.slice(0, 4)], + ['windows', 'latest.yml', artifacts.slice(4, 5)], + ['linux', 'latest-linux.yml', artifacts.slice(5, 6)], + ]) { + const output = path.join(directory, filename); + execFileSync(process.execPath, [ + electronBridgeScript, + '--assets', + assets, + '--platform', + platform, + '--version', + '0.2.0', + '--output', + output, + ]); + const manifest = fs.readFileSync(output, 'utf8'); + assert.match(manifest, /^version: 0\.2\.0$/m); + for (const artifact of selected) { + const contents = fs.readFileSync(path.join(assets, artifact)); + const sha512 = crypto + .createHash('sha512') + .update(contents) + .digest('base64'); + assert.ok(manifest.includes(`url: ${artifact}`)); + assert.ok(manifest.includes(`sha512: ${sha512}`)); + assert.ok(manifest.includes(`size: ${contents.length}`)); + } + } + + fs.rmSync(path.join(assets, artifacts[1])); + const failure = spawnSync( + process.execPath, + [ + electronBridgeScript, + '--assets', + assets, + '--platform', + 'macos', + '--version', + '0.2.0', + '--output', + path.join(directory, 'latest-mac.yml'), + ], + { encoding: 'utf8' }, + ); + assert.notEqual(failure.status, 0); + assert.match(failure.stderr, /Expected one Electron bridge artifact/); +} + function testChecksumRefresh(directory) { fs.mkdirSync(path.join(directory, 'nested'), { recursive: true }); fs.writeFileSync(path.join(directory, 'one.txt'), 'one'); diff --git a/packages/desktop-shell/src-tauri/Cargo.lock b/packages/desktop-shell/src-tauri/Cargo.lock index cc5b440bb..0bdcce697 100644 --- a/packages/desktop-shell/src-tauri/Cargo.lock +++ b/packages/desktop-shell/src-tauri/Cargo.lock @@ -2485,7 +2485,7 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openwork-desktop" -version = "0.1.0" +version = "0.2.0" dependencies = [ "base64 0.22.1", "command-group", diff --git a/packages/desktop-shell/src-tauri/Cargo.toml b/packages/desktop-shell/src-tauri/Cargo.toml index 040b70f2b..c3f9b1b4a 100644 --- a/packages/desktop-shell/src-tauri/Cargo.toml +++ b/packages/desktop-shell/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openwork-desktop" -version = "0.1.0" +version = "0.2.0" description = "OpenWork desktop shell — Tauri" authors = ["Model Studio AI"] license = "Apache-2.0" diff --git a/packages/desktop-shell/src-tauri/tauri.conf.json b/packages/desktop-shell/src-tauri/tauri.conf.json index df4391bea..44886e863 100644 --- a/packages/desktop-shell/src-tauri/tauri.conf.json +++ b/packages/desktop-shell/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenWork", - "version": "0.1.0", + "version": "0.2.0", "identifier": "com.alibaba.openwork", "build": { "beforeDevCommand": "node scripts/serve-bootstrap.js", @@ -58,7 +58,8 @@ }, "nsis": { "installMode": "currentUser", - "installerIcon": "icons/icon.ico" + "installerIcon": "icons/icon.ico", + "installerHooks": "windows/electron-migration.nsh" } } }, diff --git a/packages/desktop-shell/src-tauri/windows/electron-migration.nsh b/packages/desktop-shell/src-tauri/windows/electron-migration.nsh new file mode 100644 index 000000000..147b7a789 --- /dev/null +++ b/packages/desktop-shell/src-tauri/windows/electron-migration.nsh @@ -0,0 +1,14 @@ +!define ELECTRON_INSTALL_KEY "Software\d6bd5575-5bf2-5dad-acfe-35e3bbeefd68" +!define ELECTRON_UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\d6bd5575-5bf2-5dad-acfe-35e3bbeefd68" + +!macro NSIS_HOOK_PREINSTALL + ReadRegStr $R0 HKCU "${ELECTRON_INSTALL_KEY}" "InstallLocation" + ReadRegStr $R1 HKCU "${ELECTRON_UNINSTALL_KEY}" "DisplayName" + ${If} $R0 != "" + ${AndIf} $R1 == "OpenWork" + ExecWait '"$R0\Uninstall OpenWork.exe" /currentuser /S --updated _?=$R0' $R2 + ${If} $R2 != 0 + Abort "Could not remove the previous OpenWork installation." + ${EndIf} + ${EndIf} +!macroend From 5881de218c4740fafd0a96e5909bdf94906d7ecf Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Thu, 13 Aug 2026 17:28:59 +0800 Subject: [PATCH 2/2] test(desktop): satisfy bridge contract lint --- packages/desktop-shell/scripts/test-release.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-shell/scripts/test-release.js b/packages/desktop-shell/scripts/test-release.js index 5512325da..02efc575e 100755 --- a/packages/desktop-shell/scripts/test-release.js +++ b/packages/desktop-shell/scripts/test-release.js @@ -99,7 +99,7 @@ function testDesktopConfiguration() { ); assert.match( migrationHook, - /ExecWait '\"\$R0\\Uninstall OpenWork\.exe\" \/currentuser \/S --updated _\?=\$R0'/, + /ExecWait '"\$R0\\Uninstall OpenWork\.exe" \/currentuser \/S --updated _\?=\$R0'/, ); assert.equal( config.bundle?.resources?.['../runtime/openwork'],