Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ concurrency:
env:
BUN_VERSION: '1.3.9'
CRAFT_BRAND: 'qwen-code'
DESKTOP_UPDATE_FEED_TAG: 'desktop-latest'

jobs:
release_metadata:
Expand Down Expand Up @@ -339,39 +340,42 @@ jobs:
working-directory: 'packages/desktop'
shell: 'bash'
env:
EXPECTED_REPOSITORY: '${{ github.repository }}'
EXPECTED_UPDATE_URL: 'https://github.com/${{ github.repository }}/releases/download/${{ env.DESKTOP_UPDATE_FEED_TAG }}'
run: |
set -euo pipefail

bun run electron:builder-config

actual_repository="$(node <<'NODE'
actual_update_url="$(node <<'NODE'
const fs = require('node:fs');
const yaml = require('js-yaml');

const config = yaml.load(
fs.readFileSync('apps/electron/electron-builder.generated.yml', 'utf8'),
);
const publish = config?.publish;
if (
!publish ||
publish.provider !== 'github' ||
!publish.owner ||
!publish.repo
) {
if (!publish || typeof publish.provider !== 'string') {
process.exit(1);
}

console.log(`${publish.owner}/${publish.repo}`);
if (publish.provider === 'github') {
if (!publish.owner || !publish.repo) process.exit(1);
console.log(`https://github.com/${publish.owner}/${publish.repo}/releases`);
} else if (publish.provider === 'generic') {
if (!publish.url) process.exit(1);
console.log(publish.url);
} else {
process.exit(1);
}
NODE
)"

if [ "$actual_repository" != "$EXPECTED_REPOSITORY" ]; then
echo "::error::Desktop update feed points to $actual_repository, expected $EXPECTED_REPOSITORY."
if [ "$actual_update_url" != "$EXPECTED_UPDATE_URL" ]; then
echo "::error::Desktop update feed points to $actual_update_url, expected $EXPECTED_UPDATE_URL."
exit 1
fi

echo "Desktop update feed: https://github.com/${actual_repository}/releases"
echo "Desktop update feed: ${actual_update_url}"

- name: 'Configure optional signing secrets'
shell: 'bash'
Expand Down Expand Up @@ -528,6 +532,7 @@ jobs:
RELEASE_NAME: '${{ inputs.release_name }}'
RELEASE_PRERELEASE: '${{ inputs.prerelease }}'
RELEASE_TARGET: '${{ needs.release_metadata.outputs.release_ref }}'
UPDATE_FEED_TAG: '${{ env.DESKTOP_UPDATE_FEED_TAG }}'
UPLOAD_CLOBBER: '${{ inputs.clobber }}'
run: |
set -euo pipefail
Expand Down Expand Up @@ -593,6 +598,26 @@ jobs:
gh release create "${create_args[@]}"
fi

feed_title="Qwen Code Desktop latest"
feed_notes="Auto-update feed for ${RELEASE_TAG}. See https://github.com/${GH_REPO}/releases/tag/${RELEASE_TAG}."
feed_upload_args=("$UPDATE_FEED_TAG" "${assets[@]}" --clobber)
if gh release view "$UPDATE_FEED_TAG" >/dev/null 2>&1; then
gh release edit "$UPDATE_FEED_TAG" \
--draft=false \
--prerelease=false \
--latest=false \
--target "$RELEASE_TARGET" \
--title "$feed_title" \
--notes "$feed_notes"
gh release upload "${feed_upload_args[@]}"
else
gh release create "$UPDATE_FEED_TAG" "${assets[@]}" \
--latest=false \
--target "$RELEASE_TARGET" \
--title "$feed_title" \
--notes "$feed_notes"
fi

sync-version:
name: 'Sync Release Version to Main'
runs-on: 'ubuntu-latest'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ Before this change, the desktop app already had most of the runtime surface:
Update source configuration belongs in `packages/shared/src/branding.ts` with the rest of desktop brand metadata. Each brand owns its release location:

- `openwork` uses `modelstudioai/openwork`.
- `qwen-code` uses `QwenLM/qwen-code`.
- `qwen-code` uses a fixed `QwenLM/qwen-code` `desktop-latest` release download URL so desktop updates do not depend on the repository-wide GitHub latest release.

The brand config exposes a GitHub update source with `provider`, `owner`, `repo`, and `releasePageUrl`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source for logs and fallback release-page actions.
The brand config exposes an update source plus `releasePageUrl`. GitHub sources use `provider`, `owner`, and `repo`; generic sources use `provider` and `url`. `scripts/electron-builder-config.ts` reads it and emits the `publish` block in `apps/electron/electron-builder.generated.yml`. Runtime code reads the same brand update source to decide whether packaged builds can check for updates.

## Release Flow

The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself.
The existing desktop release workflow remains responsible for uploading assets to GitHub Releases. `electron-builder` should generate updater metadata, but the workflow continues to publish assets itself. Qwen Code publishes versioned `desktop-v*` releases for history and also clobbers the fixed `desktop-latest` release used by the generic update feed.

Expected assets include platform installers and feed files:

Expand Down
27 changes: 18 additions & 9 deletions packages/desktop/packages/shared/src/branding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@
// Brand config type
// ---------------------------------------------------------------------------

type GitHubUpdateSource = {
provider: 'github';
owner: string;
repo: string;
releasePageUrl: string;
};

type GenericUpdateSource = {
provider: 'generic';
url: string;
releasePageUrl: string;
};

type UpdateSource = GitHubUpdateSource | GenericUpdateSource;

export interface BrandConfig {
/** Internal identifier */
id: string;
Expand All @@ -30,12 +45,7 @@ export interface BrandConfig {
/** Session viewer base URL */
viewerUrl: string;
/** Stable desktop auto-update source for packaged app builds. */
updates?: {
provider: 'github';
owner: string;
repo: string;
releasePageUrl: string;
};
updates?: UpdateSource;
/** Brand-owned external links shown in the Help menu */
helpMenuLinks: Array<{ labelKey: string; url: string; icon: string }>;
/** Brand-specific Electron resource paths, relative to apps/electron/ */
Expand Down Expand Up @@ -80,9 +90,8 @@ const QWEN_CODE_BRAND: BrandConfig = {
selfReferName: 'Qwen Code',
viewerUrl: 'https://agents.craft.do',
updates: {
provider: 'github',
owner: 'QwenLM',
repo: 'qwen-code',
provider: 'generic',
url: 'https://github.com/QwenLM/qwen-code/releases/download/desktop-latest',
releasePageUrl: 'https://github.com/QwenLM/qwen-code/releases',
},
helpMenuLinks: [
Expand Down
17 changes: 12 additions & 5 deletions packages/desktop/scripts/electron-builder-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,18 @@ linux.icon = BRAND.assets.linuxIcon;
linux.artifactName = artifactName;

if (BRAND.updates) {
config.publish = {
provider: BRAND.updates.provider,
owner: BRAND.updates.owner,
repo: BRAND.updates.repo,
};
if (BRAND.updates.provider === 'github') {
config.publish = {
provider: BRAND.updates.provider,
owner: BRAND.updates.owner,
repo: BRAND.updates.repo,
};
} else {
config.publish = {
provider: BRAND.updates.provider,
url: BRAND.updates.url,
};
}
} else {
delete config.publish;
}
Expand Down
Loading