diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 5d3a5752b67..b90e171f5c6 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -76,6 +76,7 @@ jobs: name: desktop-mac-${{ matrix.arch }}-dmg path: apps/desktop/release/*.dmg retention-days: 30 + if-no-files-found: error - name: Upload ZIP artifact uses: actions/upload-artifact@v4 @@ -83,6 +84,15 @@ jobs: name: desktop-mac-${{ matrix.arch }}-zip path: apps/desktop/release/*.zip retention-days: 30 + if-no-files-found: error + + - name: Upload auto-update manifest + uses: actions/upload-artifact@v4 + with: + name: desktop-mac-update-manifest + path: apps/desktop/release/latest-mac.yml + retention-days: 30 + if-no-files-found: error # Create GitHub Release after builds complete release: diff --git a/apps/desktop/RELEASE.md b/apps/desktop/RELEASE.md index a4cda7b176b..4e69665efb1 100644 --- a/apps/desktop/RELEASE.md +++ b/apps/desktop/RELEASE.md @@ -1,154 +1,76 @@ # Desktop App Release Process -This document describes how to create a release for the Superset Desktop application. +## Quick Start -## Prerequisites - -- Ensure all changes are committed and pushed to the repository -- Ensure the build works locally: `bun run package` -- Update version in `package.json` if needed - -## Release Methods - -### Method 1: Tag-Based Release (Recommended) - -Create and push a git tag with the format `desktop-v*.*.*`: +From the monorepo root: ```bash -# Create a tag (e.g., desktop-v1.0.0) -git tag desktop-v1.0.0 - -# Push the tag to trigger the release workflow -git push origin desktop-v1.0.0 +./apps/desktop/create-release.sh +# Example: ./apps/desktop/create-release.sh desktop-v0.0.1 ``` -This will automatically: -1. Build the app for macOS (arm64), Windows (x64), and Linux (x64) -2. Create artifacts for each platform -3. Create a draft GitHub release with all binaries attached - -### Method 2: Manual Workflow Dispatch - -You can also trigger a release manually from GitHub Actions: +The script will: +1. Update `package.json` version +2. Create and push a `desktop-v` tag +3. Monitor the GitHub Actions build +4. Create a **draft release** for review -1. Go to Actions → Release Desktop App -2. Click "Run workflow" -3. Enter the version number (e.g., `1.0.0`) -4. Click "Run workflow" +To auto-publish instead of creating a draft: -This method is useful for testing the workflow or creating builds without creating a tag. - -## Workflow Overview - -The release workflow (`.github/workflows/release-desktop.yml`) performs the following: - -### Build Platform - -Builds are created for: -- **macOS**: arm64 (Apple Silicon) - produces `.dmg` and `.zip` +```bash +./apps/desktop/create-release.sh desktop-v0.0.1 --publish +``` -### Build Steps +To publish a draft: -1. Checkout code -2. Setup Bun -3. Install dependencies -4. Clean dev folder (`bun run clean:dev`) -5. Compile app with electron-vite (`bun run compile:app`) -6. Package with electron-builder (`bun run package`) -7. Upload artifacts +```bash +gh release edit desktop-v0.0.1 --draft=false +``` -### Release Creation +### Requirements -After the build completes (tag-based releases only): -1. Downloads all artifacts -2. Creates a draft GitHub release -3. Attaches all binaries to the release -4. Generates release notes from commits +- GitHub CLI (`gh`) installed and authenticated +- Clean git working directory -## Code Signing (Optional) +## Manual Release -To enable macOS code signing, add the following secrets to your GitHub repository: +If you prefer not to use the script: -```yaml -CSC_LINK: ${{ secrets.MAC_CERTIFICATE }} -CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} -APPLEID: ${{ secrets.APPLE_ID }} -APPLEIDPASS: ${{ secrets.APPLE_ID_PASSWORD }} +```bash +git tag desktop-v1.0.0 +git push origin desktop-v1.0.0 ``` -Then uncomment the environment variables in the workflow under "Build Electron app". - -## Publishing the Release +This creates a draft release. Publish it manually at GitHub Releases. -1. After the workflow completes, go to GitHub Releases -2. Find the draft release -3. Review the release notes and binaries -4. Edit the release description if needed -5. Click "Publish release" to make it public +## Auto-update -## Build Outputs +The app checks for updates at launch and every x hours using: -### macOS (arm64) -- `Superset--arm64.dmg` - DMG installer -- `Superset--arm64-mac.zip` - Zipped app bundle - -## Troubleshooting +- **Manifest**: `https://github.com/superset-sh/superset/releases/latest/download/latest-mac.yml` +- **Installer**: `https://github.com/superset-sh/superset/releases/latest/download/Superset-arm64.dmg` -### Build fails on macOS +The workflow creates stable-named copies (without version) so these URLs always point to the latest build. -- Ensure you're building for the correct architecture (arm64 is configured by default) -- Check that icon files exist at `src/resources/build/icons/icon.icns` -- Verify that dependencies are properly installed +## Code Signing -### Native module errors +macOS code signing uses these repository secrets: -- `node-pty` is configured as a native module in both `electron.vite.config.ts` and `electron-builder.ts` -- It's externalized during build and unpacked from ASAR -- If you add more native modules, update both configuration files - -### Missing icons error - -- The macOS build requires `icon.icns` in `src/resources/build/icons/` -- Ensure this file is committed to the repository +- `MAC_CERTIFICATE` / `MAC_CERTIFICATE_PASSWORD` +- `APPLE_ID` / `APPLE_ID_PASSWORD` / `APPLE_TEAM_ID` ## Local Testing -To test the build locally before releasing: - ```bash cd apps/desktop - -# Clean and compile bun run clean:dev bun run compile:app - -# Package the app bun run package ``` -The output will be in `apps/desktop/release/`. - -## Building for Intel Macs (x64) - -To also build for Intel Macs, update `electron-builder.ts`: - -```typescript -mac: { - target: [ - { - target: "default", - arch: ["arm64", "x64"], // Add x64 for Intel Macs - }, - ], -} -``` - -Note: This will increase build time significantly. +Output: `apps/desktop/release/` -## Adding Windows/Linux Builds - -Currently only macOS builds are supported in CI/CD. To add Windows or Linux: +## Troubleshooting -1. Add PNG icon files to `src/resources/build/icons/` (for Linux) -2. Update the workflow matrix in `.github/workflows/release-desktop.yml` -3. Update `electron-builder.ts` configuration as needed +- **Build fails**: Check `src/resources/build/icons/icon.icns` exists +- **Native module errors**: Ensure `node-pty` is in externals in both `electron.vite.config.ts` and `electron-builder.ts` diff --git a/apps/desktop/create-release.sh b/apps/desktop/create-release.sh index dc151411b9e..7acad30e77d 100755 --- a/apps/desktop/create-release.sh +++ b/apps/desktop/create-release.sh @@ -4,8 +4,9 @@ # Based on apps/desktop/RELEASE.md # # Usage: -# ./create-release.sh +# ./create-release.sh [--publish] # Example: ./create-release.sh 0.0.1 +# Example: ./create-release.sh 0.0.1 --publish # # This script will: # 1. Verify prerequisites (clean git, GitHub CLI authenticated) @@ -13,11 +14,12 @@ # 3. Update package.json version # 4. Create and push a git tag to trigger the release workflow # 5. Monitor the GitHub Actions workflow in real-time -# 6. Auto-publish the release when build completes +# 6. Leave release as draft (default) or auto-publish with --publish flag # # Features: # - Supports republishing: Running with same version will clean up and rebuild -# - Auto-publishes release (no manual publish step needed) +# - Draft by default for review before publishing +# - Use --publish flag to auto-publish when build completes # # Requirements: # - GitHub CLI (gh) installed and authenticated @@ -51,12 +53,32 @@ error() { exit 1 } -# Check if version argument is provided -if [ -z "$1" ]; then - error "Usage: $0 \nExample: $0 0.0.1" +# Parse arguments +VERSION="" +AUTO_PUBLISH=false + +for arg in "$@"; do + case $arg in + --publish) + AUTO_PUBLISH=true + ;; + -*) + error "Unknown option: $arg\nUsage: $0 [--publish]" + ;; + *) + if [ -z "$VERSION" ]; then + VERSION="$arg" + else + error "Unexpected argument: $arg\nUsage: $0 [--publish]" + fi + ;; + esac +done + +if [ -z "$VERSION" ]; then + error "Usage: $0 [--publish]\nExample: $0 0.0.1" fi -VERSION="$1" TAG_NAME="desktop-v${VERSION}" DESKTOP_DIR="apps/desktop" @@ -251,23 +273,38 @@ if [ -z "$RELEASE_FOUND" ]; then warn "Release not found yet. It may still be processing." echo " Check releases at: https://github.com/${REPO}/releases" else - # Publish the release - info "Publishing release..." - gh release edit "${TAG_NAME}" --draft=false - success "Release published!" - RELEASE_URL="https://github.com/${REPO}/releases/tag/${TAG_NAME}" LATEST_URL="https://github.com/${REPO}/releases/latest" - echo "" - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN}🎉 Release Published!${NC}" - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo "" - echo -e "${BLUE}Release URL:${NC} ${RELEASE_URL}" - echo -e "${BLUE}Latest URL:${NC} ${LATEST_URL}" - echo "" - echo -e "${BLUE}Direct downloads:${NC}" - echo " • ${LATEST_URL}/download/Superset-${VERSION}-arm64.dmg" - echo " • ${LATEST_URL}/download/Superset-${VERSION}-arm64-mac.zip" - echo "" + + if [ "$AUTO_PUBLISH" = true ]; then + # Publish the release + info "Publishing release..." + gh release edit "${TAG_NAME}" --draft=false + success "Release published!" + + echo "" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}🎉 Release Published!${NC}" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo -e "${BLUE}Release URL:${NC} ${RELEASE_URL}" + echo -e "${BLUE}Latest URL:${NC} ${LATEST_URL}" + echo "" + echo -e "${BLUE}Direct download:${NC}" + echo " • ${LATEST_URL}/download/Superset-arm64.dmg" + echo "" + else + success "Draft release created!" + + echo "" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}📝 Draft Release Ready for Review${NC}" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo -e "${BLUE}Review URL:${NC} ${RELEASE_URL}" + echo "" + echo "To publish:" + echo " gh release edit ${TAG_NAME} --draft=false" + echo "" + fi fi diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index d37e722218b..57ccb6a07cc 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -17,8 +17,12 @@ const config: Configuration = { copyright: `Copyright © ${currentYear} — ${author}`, electronVersion: pkg.devDependencies.electron.replace(/^\^/, ""), - // Disable auto-publish - handled by separate workflow step - publish: null, + // Generate latest-mac.yml for auto-update (workflow handles actual upload) + publish: { + provider: "github", + owner: "superset-sh", + repo: "superset", + }, // Directories directories: { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 10f550ccfe0..ed104274941 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -56,6 +56,7 @@ "dotenv": "^17.2.3", "electron-router-dom": "^2.1.0", "electron-store": "^11.0.2", + "electron-updater": "6", "execa": "^9.6.0", "express": "^5.1.0", "fast-glob": "^3.3.3", diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index b7d6ac94041..e51743ad0ab 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { app } from "electron"; import { makeAppSetup } from "lib/electron-app/factories/app/setup"; import { setupAgentHooks } from "./lib/agent-setup"; +import { setupAutoUpdater } from "./lib/auto-updater"; import { initDb } from "./lib/db"; import { registerStorageHandlers } from "./lib/storage-ipcs"; import { terminalManager } from "./lib/terminal-manager"; @@ -43,6 +44,7 @@ registerStorageHandlers(); } await makeAppSetup(() => MainWindow()); + setupAutoUpdater(); // Clean up all terminals when app is quitting app.on("before-quit", async () => { diff --git a/apps/desktop/src/main/lib/auto-updater.ts b/apps/desktop/src/main/lib/auto-updater.ts new file mode 100644 index 00000000000..afef2f1403f --- /dev/null +++ b/apps/desktop/src/main/lib/auto-updater.ts @@ -0,0 +1,61 @@ +import { app } from "electron"; +import { autoUpdater } from "electron-updater"; +import { ENVIRONMENT, PLATFORM } from "shared/constants"; + +const UPDATE_CHECK_INTERVAL_MS = 1000 * 60 * 60 * 1; // 1 hour +const UPDATE_FEED_URL = + "https://github.com/superset-sh/superset/releases/latest/download"; + +export function setupAutoUpdater(): void { + if (ENVIRONMENT.IS_DEV || !PLATFORM.IS_MAC) { + return; + } + + autoUpdater.autoDownload = true; + autoUpdater.autoInstallOnAppQuit = true; + autoUpdater.allowDowngrade = false; + + autoUpdater.setFeedURL({ + provider: "generic", + url: UPDATE_FEED_URL, + }); + + autoUpdater.on("error", (error) => { + console.error("[auto-updater] Error during update check:", error); + }); + + autoUpdater.on("update-available", (info) => { + console.info( + `[auto-updater] Update available: ${info.version}. Downloading...`, + ); + }); + + autoUpdater.on("update-not-available", () => { + console.info("[auto-updater] No updates available"); + }); + + autoUpdater.on("update-downloaded", (info) => { + console.info( + `[auto-updater] Update downloaded (${info.version}). Will install on quit.`, + ); + }); + + const checkForUpdates = () => + autoUpdater.checkForUpdatesAndNotify().catch((error) => { + console.error("[auto-updater] Failed to check for updates:", error); + }); + + const interval = setInterval(checkForUpdates, UPDATE_CHECK_INTERVAL_MS); + interval.unref(); + + if (app.isReady()) { + void checkForUpdates(); + } else { + app + .whenReady() + .then(() => checkForUpdates()) + .catch((error) => { + console.error("[auto-updater] Failed to start update checks:", error); + }); + } +} diff --git a/bun.lock b/bun.lock index 3be28b0e273..8c79bd6e3af 100644 --- a/bun.lock +++ b/bun.lock @@ -96,6 +96,7 @@ "dotenv": "^17.2.3", "electron-router-dom": "^2.1.0", "electron-store": "^11.0.2", + "electron-updater": "6", "execa": "^9.6.0", "express": "^5.1.0", "fast-glob": "^3.3.3", @@ -1501,6 +1502,8 @@ "electron-to-chromium": ["electron-to-chromium@1.5.262", "", {}, "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ=="], + "electron-updater": ["electron-updater@6.6.2", "", { "dependencies": { "builder-util-runtime": "9.3.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "^7.6.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw=="], + "electron-vite": ["electron-vite@4.0.1", "", { "dependencies": { "@babel/core": "^7.27.7", "@babel/plugin-transform-arrow-functions": "^7.27.1", "cac": "^6.7.14", "esbuild": "^0.25.5", "magic-string": "^0.30.17", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-QqacJbA8f1pmwUTqki1qLL5vIBaOQmeq13CZZefZ3r3vKVaIoC7cpoTgE+KPKxJDFTax+iFZV0VYvLVWPiQ8Aw=="], "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], @@ -1923,6 +1926,10 @@ "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -2661,6 +2668,8 @@ "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],