mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-17 04:34:46 +00:00
fix(desktop): bridge Electron users on Windows and Linux (#9079)
* fix(desktop): bridge Electron users on Windows and Linux * test(desktop): satisfy bridge contract lint * fix(desktop): harden Electron bridge migration * chore(desktop): note sibling-script regex sync for bridge artifacts * fix(desktop): satisfy release manifest lint * fix(desktop): validate Electron uninstaller path before migration The migration ExecWait target is assembled from the HKCU InstallLocation registry value; require the uninstaller executable to exist before running it, and pin the conjunct in the release contract suite.
This commit is contained in:
parent
1095e0bbc4
commit
85a06bbec8
7 changed files with 213 additions and 109 deletions
|
|
@ -6,12 +6,21 @@ 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 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 names = selectedPatterns.map((pattern) => selectArtifact(assets, pattern));
|
||||
const artifacts = names.map((name) => readArtifact(assets, name));
|
||||
const primary = artifacts[0];
|
||||
|
||||
|
|
@ -29,10 +38,18 @@ 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}`);
|
||||
// Keep the selection regexes in sync with create-desktop-update-manifest.mjs.
|
||||
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(assets, name) {
|
||||
const file = path.join(options.assets, name);
|
||||
return {
|
||||
name,
|
||||
|
|
@ -52,7 +69,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 (
|
||||
|
|
|
|||
30
.github/workflows/desktop-release.yml
vendored
30
.github/workflows/desktop-release.yml
vendored
|
|
@ -15,7 +15,7 @@ on:
|
|||
default: 'main'
|
||||
type: 'string'
|
||||
electron_bridge:
|
||||
description: 'Publish the one-time macOS Electron-to-Tauri update bridge.'
|
||||
description: 'Publish the one-time Electron-to-Tauri update bridge for macOS, Windows, and Linux.'
|
||||
required: true
|
||||
default: false
|
||||
type: 'boolean'
|
||||
|
|
@ -581,10 +581,15 @@ jobs:
|
|||
--version "$RELEASE_VERSION" \
|
||||
--output desktop-latest.json
|
||||
if [ "$ELECTRON_BRIDGE" = 'true' ]; then
|
||||
node ../.github/scripts/create-electron-bridge-manifest.mjs \
|
||||
--assets . \
|
||||
--version "$RELEASE_VERSION" \
|
||||
--output latest-mac.yml
|
||||
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 . \
|
||||
--platform "$platform" \
|
||||
--version "$RELEASE_VERSION" \
|
||||
--output "$output"
|
||||
done
|
||||
fi
|
||||
sha256sum -- * > SHA256SUMS.txt
|
||||
|
||||
|
|
@ -629,12 +634,23 @@ jobs:
|
|||
set -euo pipefail
|
||||
feed_assets=(release-assets/desktop-latest.json)
|
||||
if [ "$ELECTRON_BRIDGE" = 'true' ]; then
|
||||
shopt -s nullglob
|
||||
windows_installers=(release-assets/*-setup.exe)
|
||||
linux_appimages=(release-assets/*.AppImage)
|
||||
if [ "${#windows_installers[@]}" -ne 1 ] || [ "${#linux_appimages[@]}" -ne 1 ]; then
|
||||
echo '::error::The Electron bridge requires one Windows installer and one Linux AppImage.'
|
||||
exit 1
|
||||
fi
|
||||
feed_assets+=(
|
||||
release-assets/latest-mac.yml
|
||||
release-assets/latest.yml
|
||||
release-assets/latest-linux.yml
|
||||
release-assets/Qwen-Code-Desktop-arm64.zip
|
||||
release-assets/Qwen-Code-Desktop-x64.zip
|
||||
release-assets/Qwen-Code-Desktop-arm64.dmg
|
||||
release-assets/Qwen-Code-Desktop-x64.dmg
|
||||
"${windows_installers[0]}"
|
||||
"${linux_appimages[0]}"
|
||||
)
|
||||
fi
|
||||
if gh release view "$FEED_TAG" >/dev/null 2>&1; then
|
||||
|
|
@ -648,6 +664,10 @@ jobs:
|
|||
fi
|
||||
newest="$(printf '%s\n%s\n' "$RELEASE_VERSION" "$current" | sort -V | tail -n 1)"
|
||||
if [ "$current" != "$RELEASE_VERSION" ] && [ "$newest" = "$current" ]; then
|
||||
if [ "$ELECTRON_BRIDGE" = 'true' ]; then
|
||||
echo "::error::Electron bridge $RELEASE_VERSION cannot replace newer stable feed $current."
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Desktop $RELEASE_VERSION will not replace newer stable feed $current."
|
||||
exit 0
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
The legacy Electron desktop reads `latest-mac.yml`, `latest.yml`, or `latest-linux.yml` from the fixed `desktop-latest` release. The Tauri desktop reads `desktop-latest.json` from the same release. A stable release can therefore expose both update formats over the same Tauri installers without building Electron again.
|
||||
|
||||
## Compatibility contract
|
||||
|
||||
The Tauri bundle uses the legacy macOS identity:
|
||||
The Tauri bundle keeps the legacy product name and application identifier. With `electron_bridge` enabled, the release workflow publishes:
|
||||
|
||||
- product name: `Qwen Code Desktop`
|
||||
- bundle identifier: `com.alibaba.qwen-code`
|
||||
- artifact prefix: `Qwen-Code-Desktop`
|
||||
- signing identity: the existing Developer ID Application certificate
|
||||
- `latest-mac.yml` plus 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;
|
||||
- `desktop-latest.json` for Tauri clients on all platforms.
|
||||
|
||||
The bridge release must be newer than `0.0.5`. It publishes two updater views over the same signed app bundles:
|
||||
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.
|
||||
|
||||
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.
|
||||
## Release usage
|
||||
|
||||
The ZIP is created from the already signed and notarized `.app`; it is not rebuilt by Electron tooling.
|
||||
Run `Desktop Release` for the next stable version with `electron_bridge=true`, `dry_run=false`, `draft=false`, and `prerelease=false`. The bridge is one-time: the fixed `desktop-latest` release retains the three Electron manifests and payloads when later Tauri-only releases update `desktop-latest.json`.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## Validation
|
||||
|
||||
Automated release-helper tests verify:
|
||||
|
||||
- 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.
|
||||
|
||||
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 each signed legacy client can install the bridge and that the resulting Tauri app can then update to a newer Tauri release. Do not remove the bridge assets from `desktop-latest` while legacy Electron installations remain supported.
|
||||
|
|
|
|||
|
|
@ -65,6 +65,6 @@ cargo test --manifest-path src-tauri/Cargo.toml
|
|||
|
||||
The `Desktop Release` workflow builds signed updater artifacts when `dry_run` is disabled. Published releases require the Tauri updater private key. macOS releases also require Apple signing and notarization credentials.
|
||||
|
||||
The first stable Tauri release may set `electron_bridge=true` to publish the macOS ZIPs and `latest-mac.yml` consumed by Electron `0.0.5`. Leave the input disabled for later releases; the fixed `desktop-latest` release retains the bridge assets while `desktop-latest.json` advances independently.
|
||||
The first stable Tauri release may set `electron_bridge=true` to publish the macOS ZIPs and DMGs, Windows NSIS installer, Linux AppImage, and their Electron `0.0.5` manifests. Leave the input disabled for later releases; the fixed `desktop-latest` release retains the bridge assets while `desktop-latest.json` advances independently.
|
||||
|
||||
The macOS workflow accepts either the Tauri-era `APPLE_*` certificate and notarization secrets or the existing `MAC_CSC_*` and `APPLE_NOTARY_*` secrets. `TAURI_SIGNING_PRIVATE_KEY` must match the public key in `src-tauri/tauri.conf.json`.
|
||||
|
|
|
|||
|
|
@ -224,6 +224,29 @@ function testLegacyApplicationIdentity() {
|
|||
);
|
||||
assert.equal(config.productName, 'Qwen Code Desktop');
|
||||
assert.equal(config.identifier, 'com.alibaba.qwen-code');
|
||||
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\\821b18a9-7c63-5bb4-9e20-51ba63d5ecc3/);
|
||||
assert.match(migrationHook, /!macro NSIS_HOOK_PREINSTALL/);
|
||||
assert.match(
|
||||
migrationHook,
|
||||
/StrCpy \$R1 \$R1 17\s*\n\s*\$\{If\} \$R0 != ""\s*\n\s*\$\{AndIf\} \$R1 == "Qwen Code Desktop"/,
|
||||
);
|
||||
assert.match(
|
||||
migrationHook,
|
||||
/\$\{AndIf\} \$\{FileExists\} "\$R0\\Uninstall Qwen Code Desktop\.exe"/,
|
||||
);
|
||||
assert.match(
|
||||
migrationHook,
|
||||
/ExecWait '"\$R0\\Uninstall Qwen Code Desktop\.exe" \/currentuser \/S --updated _\?=\$R0'/,
|
||||
);
|
||||
assert.match(migrationHook, /\$\{If\} \$R2 != 0\s*\n\s*Abort/);
|
||||
}
|
||||
|
||||
function testElectronBridgeWorkflow() {
|
||||
|
|
@ -233,6 +256,22 @@ function testElectronBridgeWorkflow() {
|
|||
);
|
||||
assert.match(workflow, /^ {6}electron_bridge:$/m);
|
||||
assert.match(workflow, /create-electron-bridge-manifest\.mjs/);
|
||||
assert.match(workflow, /macos:latest-mac\.yml/);
|
||||
assert.match(workflow, /windows:latest\.yml/);
|
||||
assert.match(workflow, /linux:latest-linux\.yml/);
|
||||
assert.match(
|
||||
workflow,
|
||||
/windows_installers=\(release-assets\/\*-setup\.exe\)/,
|
||||
);
|
||||
assert.match(workflow, /linux_appimages=\(release-assets\/\*\.AppImage\)/);
|
||||
assert.match(workflow, /^\s+release-assets\/latest\.yml$/m);
|
||||
assert.match(workflow, /^\s+release-assets\/latest-linux\.yml$/m);
|
||||
assert.match(workflow, /^\s+"\$\{windows_installers\[0\]\}"$/m);
|
||||
assert.match(workflow, /^\s+"\$\{linux_appimages\[0\]\}"$/m);
|
||||
assert.match(
|
||||
workflow,
|
||||
/if \[ "\$ELECTRON_BRIDGE" = 'true' \]; then\s+echo "::error::Electron bridge \$RELEASE_VERSION cannot replace newer stable feed \$current\."\s+exit 1/,
|
||||
);
|
||||
for (const artifact of [
|
||||
'Qwen-Code-Desktop-arm64.zip',
|
||||
'Qwen-Code-Desktop-x64.zip',
|
||||
|
|
@ -873,46 +912,89 @@ function testElectronBridgeManifest(directory) {
|
|||
for (const artifact of artifacts) {
|
||||
fs.writeFileSync(path.join(assets, artifact), `contents:${artifact}`);
|
||||
}
|
||||
const output = path.join(directory, 'latest-mac.yml');
|
||||
execFileSync(process.execPath, [
|
||||
electronBridgeScript,
|
||||
'--assets',
|
||||
assets,
|
||||
'--version',
|
||||
'0.1.0',
|
||||
'--output',
|
||||
output,
|
||||
]);
|
||||
const manifest = fs.readFileSync(output, 'utf8');
|
||||
assert.match(manifest, /^version: 0\.1\.0$/m);
|
||||
assert.match(
|
||||
manifest,
|
||||
/^releaseDate: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'$/m,
|
||||
artifacts.push(
|
||||
'Qwen-Code-Desktop_0.1.0_x64-setup.exe',
|
||||
'Qwen-Code-Desktop_0.1.0_amd64.AppImage',
|
||||
);
|
||||
for (const artifact of artifacts) {
|
||||
const contents = fs.readFileSync(path.join(assets, artifact));
|
||||
const sha512 = crypto
|
||||
.createHash('sha512')
|
||||
.update(contents)
|
||||
.digest('base64');
|
||||
for (const artifact of artifacts.slice(4)) {
|
||||
fs.writeFileSync(path.join(assets, artifact), `contents:${artifact}`);
|
||||
}
|
||||
const macOutput = path.join(directory, 'latest-mac.yml');
|
||||
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.1.0',
|
||||
'--output',
|
||||
output,
|
||||
]);
|
||||
const manifest = fs.readFileSync(output, 'utf8');
|
||||
assert.match(manifest, /^version: 0\.1\.0$/m);
|
||||
assert.match(
|
||||
manifest,
|
||||
new RegExp(
|
||||
`^ - url: ${artifact.replaceAll('.', '\\.')}\\n sha512: ${sha512.replaceAll('+', '\\+')}\\n size: ${contents.length}$`,
|
||||
'm',
|
||||
),
|
||||
/^releaseDate: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'$/m,
|
||||
);
|
||||
for (const artifact of selected) {
|
||||
const contents = fs.readFileSync(path.join(assets, artifact));
|
||||
const sha512 = crypto
|
||||
.createHash('sha512')
|
||||
.update(contents)
|
||||
.digest('base64');
|
||||
assert.match(
|
||||
manifest,
|
||||
new RegExp(
|
||||
`^ - url: ${artifact.replaceAll('.', '\\.')}\\n sha512: ${sha512.replaceAll('+', '\\+')}\\n size: ${contents.length}$`,
|
||||
'm',
|
||||
),
|
||||
);
|
||||
if (artifact === selected[0]) {
|
||||
assert.match(
|
||||
manifest,
|
||||
new RegExp(`^path: ${artifact.replaceAll('.', '\\.')}$`, 'm'),
|
||||
);
|
||||
assert.match(
|
||||
manifest,
|
||||
new RegExp(`^sha512: ${sha512.replaceAll('+', '\\+')}$`, 'm'),
|
||||
);
|
||||
}
|
||||
}
|
||||
assert.equal(
|
||||
(manifest.match(/^ {2}- url:/gm) ?? []).length,
|
||||
selected.length,
|
||||
);
|
||||
}
|
||||
const arm64Contents = fs.readFileSync(path.join(assets, artifacts[0]));
|
||||
const arm64Sha512 = crypto
|
||||
.createHash('sha512')
|
||||
.update(arm64Contents)
|
||||
.digest('base64');
|
||||
assert.match(manifest, /^path: Qwen-Code-Desktop-arm64\.zip$/m);
|
||||
assert.match(
|
||||
manifest,
|
||||
new RegExp(`^sha512: ${arm64Sha512.replaceAll('+', '\\+')}$`, 'm'),
|
||||
const duplicateWindowsArtifact = path.join(
|
||||
assets,
|
||||
'Qwen-Code-Desktop_0.1.0_arm64-setup.exe',
|
||||
);
|
||||
fs.writeFileSync(duplicateWindowsArtifact, 'duplicate');
|
||||
const ambiguousWindows = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
electronBridgeScript,
|
||||
'--assets',
|
||||
assets,
|
||||
'--platform',
|
||||
'windows',
|
||||
'--version',
|
||||
'0.1.0',
|
||||
'--output',
|
||||
path.join(directory, 'ambiguous-windows.yml'),
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.notEqual(ambiguousWindows.status, 0);
|
||||
assert.match(ambiguousWindows.stderr, /found 2/);
|
||||
fs.rmSync(duplicateWindowsArtifact);
|
||||
|
||||
fs.rmSync(path.join(assets, artifacts[1]));
|
||||
const failure = spawnSync(
|
||||
|
|
@ -921,15 +1003,17 @@ function testElectronBridgeManifest(directory) {
|
|||
electronBridgeScript,
|
||||
'--assets',
|
||||
assets,
|
||||
'--platform',
|
||||
'macos',
|
||||
'--version',
|
||||
'0.1.0',
|
||||
'--output',
|
||||
output,
|
||||
macOutput,
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.notEqual(failure.status, 0);
|
||||
assert.match(failure.stderr, /Missing Electron bridge artifact/);
|
||||
assert.match(failure.stderr, /Expected one Electron bridge artifact/);
|
||||
|
||||
const invalidVersion = spawnSync(
|
||||
process.execPath,
|
||||
|
|
@ -937,10 +1021,12 @@ function testElectronBridgeManifest(directory) {
|
|||
electronBridgeScript,
|
||||
'--assets',
|
||||
assets,
|
||||
'--platform',
|
||||
'macos',
|
||||
'--version',
|
||||
'0.1',
|
||||
'--output',
|
||||
output,
|
||||
macOutput,
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
|
|
@ -949,7 +1035,15 @@ function testElectronBridgeManifest(directory) {
|
|||
|
||||
const missingOutput = spawnSync(
|
||||
process.execPath,
|
||||
[electronBridgeScript, '--assets', assets, '--version', '0.1.0'],
|
||||
[
|
||||
electronBridgeScript,
|
||||
'--assets',
|
||||
assets,
|
||||
'--platform',
|
||||
'macos',
|
||||
'--version',
|
||||
'0.1.0',
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.notEqual(missingOutput.status, 0);
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@
|
|||
},
|
||||
"nsis": {
|
||||
"installMode": "currentUser",
|
||||
"installerIcon": "icons/icon.ico"
|
||||
"installerIcon": "icons/icon.ico",
|
||||
"installerHooks": "windows/electron-migration.nsh"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
!define ELECTRON_INSTALL_KEY "Software\821b18a9-7c63-5bb4-9e20-51ba63d5ecc3"
|
||||
!define ELECTRON_UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\821b18a9-7c63-5bb4-9e20-51ba63d5ecc3"
|
||||
|
||||
!macro NSIS_HOOK_PREINSTALL
|
||||
ReadRegStr $R0 HKCU "${ELECTRON_INSTALL_KEY}" "InstallLocation"
|
||||
ReadRegStr $R1 HKCU "${ELECTRON_UNINSTALL_KEY}" "DisplayName"
|
||||
StrCpy $R1 $R1 17
|
||||
${If} $R0 != ""
|
||||
${AndIf} $R1 == "Qwen Code Desktop"
|
||||
${AndIf} ${FileExists} "$R0\Uninstall Qwen Code Desktop.exe"
|
||||
ExecWait '"$R0\Uninstall Qwen Code Desktop.exe" /currentuser /S --updated _?=$R0' $R2
|
||||
${If} $R2 != 0
|
||||
Abort "Could not remove the previous Qwen Code Desktop installation."
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
!macroend
|
||||
Loading…
Add table
Add a link
Reference in a new issue