diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 1b0057f..f88cddc 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -75,8 +75,8 @@ jobs: name: desktop-${{ runner.os }} if-no-files-found: error path: | - packages/desktop/stage/out/*.AppImage - packages/desktop/stage/out/*.deb - packages/desktop/stage/out/*.dmg - packages/desktop/stage/out/*-mac.zip - packages/desktop/stage/out/*.exe + packages/desktop/stage/out/penguin-desktop-*.AppImage + packages/desktop/stage/out/penguin-desktop-*.deb + packages/desktop/stage/out/penguin-desktop-*.dmg + packages/desktop/stage/out/penguin-desktop-*.zip + packages/desktop/stage/out/penguin-desktop-*.exe diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cfc6302..3edc7da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -316,8 +316,8 @@ jobs: # Desktop installers built by the desktop job (three-OS matrix): collected here so # they are part of the Release's initial (immutable) asset set. Their checksums go - # in a separate SHA256SUMS.desktop — the OSS mirror script's canonical file list - # stays untouched (desktop installers are not mirrored yet). + # in a separate SHA256SUMS.desktop, which is also how the OSS mirror script verifies + # them (its manifest lists the installers by their version-less names). - name: Collect desktop artifacts uses: actions/download-artifact@v4 with: diff --git a/README.md b/README.md index 298fa2f..a7e6a2f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,10 @@ npm install -g @prismshadow/penguin-cli penguin web # start the service and open http://127.0.0.1:7364 ``` +### 🖥️ Desktop app + +The full Web experience as a standalone application: it embeds the server and opens already signed in — no terminal, no login page, no initial password to copy — and it works on the same `~/.penguin/data` root as a CLI install, so the two can be used interchangeably (a data root only ever runs one server; if a CLI-started instance is already up, the app attaches to it). Installers for macOS (dmg, Apple silicon / Intel), Windows and Linux (AppImage / deb) live on the [download page](https://penguin.ooo/download) — which serves the OSS-accelerated mirror when it is reachable — and are attached to each [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases). Current builds are unsigned: on first launch use right-click → Open on macOS, and "More info → Run anyway" past Windows SmartScreen. +
📴 Offline install (air-gapped machines) @@ -198,7 +202,7 @@ for await (const output of session.run([userText("Create hello.txt containing hi ## Roadmap - [ ] Public release of the benchmark suite -- [ ] Desktop app +- [x] Desktop app - [x] Windows support - [ ] Agent company and templates - [ ] Company-level self evolving diff --git a/README.zh.md b/README.zh.md index c55ae68..0b9ac1d 100644 --- a/README.zh.md +++ b/README.zh.md @@ -138,6 +138,10 @@ npm install -g @prismshadow/penguin-cli penguin web # 启动服务并打开 http://127.0.0.1:7364 ``` +### 🖥️ 桌面端应用 + +完整的 Web 体验打包为独立应用:内嵌服务端,打开即已登录——无需终端、无登录页、也不用抄初始密码——并与 CLI 安装共用同一个 `~/.penguin/data` 数据目录,两者可以混用(一个数据目录同一时刻只运行一个服务端;CLI 已启动实例时,应用会直接接入它)。macOS(dmg,Apple 芯片 / Intel)、Windows 与 Linux(AppImage / deb)的安装包可在[下载页](https://penguin.ooo/download)获取——国内自动走 OSS 镜像加速——也附于每个 [GitHub Release](https://github.com/Prism-Shadow/penguin-harness/releases)。当前构建暂未签名:macOS 首次启动请右键 →「打开」,Windows 在 SmartScreen 提示中选「仍要运行」。 +
📴 离线安装(无网环境) @@ -198,7 +202,7 @@ for await (const output of session.run([userText("Create hello.txt containing hi ## 路线图 - [ ] Benchmark 套件正式发布 -- [ ] 桌面端应用 +- [x] 桌面端应用 - [x] Windows 系统支持 - [ ] Agent 公司与模板 - [ ] 公司级自进化能力 diff --git a/packages/desktop/electron-builder.yml b/packages/desktop/electron-builder.yml index bacb4c7..5341894 100644 --- a/packages/desktop/electron-builder.yml +++ b/packages/desktop/electron-builder.yml @@ -26,6 +26,11 @@ mac: category: public.app-category.developer-tools # No Developer ID yet (M4): identity null skips codesign instead of failing. identity: null + # Version-less artifact names, following the CLI bundles (penguin-darwin-arm64.tar.gz): + # the version lives in the Release tag and SHA256SUMS.desktop, so the download page and + # the OSS mirror manifest can address every installer by a stable name — and GitHub's + # `releases/latest/download/` stays a working static link across releases. + artifactName: penguin-desktop-darwin-${arch}.${ext} target: - target: dmg arch: [arm64, x64] @@ -33,6 +38,7 @@ mac: arch: [arm64, x64] win: + artifactName: penguin-desktop-win32-${arch}.${ext} target: - target: nsis arch: [x64] @@ -50,6 +56,7 @@ nsis: linux: # The scoped package name is not a valid executable file name. executableName: penguin-harness + artifactName: penguin-desktop-linux-${arch}.${ext} category: Development maintainer: Prism Shadow target: diff --git a/packages/docs/src/components/nav.tsx b/packages/docs/src/components/nav.tsx index 9cb472b..c17fd3a 100644 --- a/packages/docs/src/components/nav.tsx +++ b/packages/docs/src/components/nav.tsx @@ -104,6 +104,9 @@ export function Nav({ {S.nav.blog} + + {S.nav.download} + {/* "Docs" is this SPA's own root — a router Link, and always the current page. */} {S.nav.docs} diff --git a/packages/docs/src/lib/strings-en.ts b/packages/docs/src/lib/strings-en.ts index 8733405..aaf156a 100644 --- a/packages/docs/src/lib/strings-en.ts +++ b/packages/docs/src/lib/strings-en.ts @@ -13,6 +13,7 @@ export const en: Strings = { contract: "CONTRACT.md", features: "Features", blog: "Blog", + download: "Download", docs: "Docs", github: "GitHub", openMenu: "Open navigation", diff --git a/packages/docs/src/lib/strings.ts b/packages/docs/src/lib/strings.ts index 5a7ff7a..3cb55cc 100644 --- a/packages/docs/src/lib/strings.ts +++ b/packages/docs/src/lib/strings.ts @@ -19,6 +19,7 @@ export const zh = { contract: "CONTRACT.md", features: "功能", blog: "博客", + download: "下载", docs: "文档", github: "GitHub", openMenu: "打开目录", diff --git a/packages/landing/scripts/postbuild.mjs b/packages/landing/scripts/postbuild.mjs index 1863ac1..457e9d4 100644 --- a/packages/landing/scripts/postbuild.mjs +++ b/packages/landing/scripts/postbuild.mjs @@ -16,7 +16,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { absoluteUrl, blogRoutes } from "./site-routes.mjs"; +import { absoluteUrl, blogRoutes, pageRoutes } from "./site-routes.mjs"; const dist = join(dirname(fileURLToPath(import.meta.url)), "..", "dist"); const shell = readFileSync(join(dist, "index.html"), "utf8"); @@ -43,7 +43,7 @@ function shellFor(route) { .replace(OG_URL, ``); } -const routes = blogRoutes(); +const routes = [...pageRoutes(), ...blogRoutes()]; for (const { route } of routes) { const dir = join(dist, route); mkdirSync(dir, { recursive: true }); @@ -52,6 +52,4 @@ for (const { route } of routes) { writeFileSync(join(dist, "404.html"), shell.replace(CANONICAL, "")); writeFileSync(join(dist, ".nojekyll"), ""); -console.log( - `[postbuild] wrote ${routes.length} blog route shells, dist/404.html and dist/.nojekyll`, -); +console.log(`[postbuild] wrote ${routes.length} route shells, dist/404.html and dist/.nojekyll`); diff --git a/packages/landing/scripts/site-routes.mjs b/packages/landing/scripts/site-routes.mjs index 05f790b..a26db95 100644 --- a/packages/landing/scripts/site-routes.mjs +++ b/packages/landing/scripts/site-routes.mjs @@ -59,6 +59,13 @@ function newestDate(dir, slug) { * that actually answers 200 — the one canonical tags and the sitemap should name. * The home page is excluded; Vite's own index.html already sits at the dist root. */ + +/** Non-blog landing SPA pages (currently the desktop download page). */ +export function pageRoutes() { + return [{ route: "/download/" }]; +} + +/** Blog routes, one per post plus the list page. */ export function blogRoutes() { return [ { route: "/blog/" }, diff --git a/packages/landing/src/components/footer.tsx b/packages/landing/src/components/footer.tsx index ddbc3ca..292a007 100644 --- a/packages/landing/src/components/footer.tsx +++ b/packages/landing/src/components/footer.tsx @@ -37,6 +37,11 @@ export function Footer() { {S.footer.blog} +
  • + + {S.nav.download} + +
  • diff --git a/packages/landing/src/components/nav.tsx b/packages/landing/src/components/nav.tsx index c858874..9227f6b 100644 --- a/packages/landing/src/components/nav.tsx +++ b/packages/landing/src/components/nav.tsx @@ -108,6 +108,14 @@ export function Nav() { > {S.nav.blog} + + {S.nav.download} + {/* Docs is a sibling SPA under /docs/ — a plain anchor, not a router Link. */} {S.nav.docs} @@ -139,6 +147,14 @@ export function Nav() { > {S.nav.blog} + setOpen(false)} + aria-current={activeItem === "download" ? "page" : undefined} + > + {S.nav.download} + setOpen(false)}> {S.nav.docs} diff --git a/packages/landing/src/lib/links.ts b/packages/landing/src/lib/links.ts index 7f9b1b0..bbe2493 100644 --- a/packages/landing/src/lib/links.ts +++ b/packages/landing/src/lib/links.ts @@ -80,3 +80,40 @@ export const blogAssetUrl = (name: string): string => `${COMMUNITY_RAW}/blog-ass /** API key consoles (same URLs the in-app Models page links to). */ export const DEEPSEEK_KEYS_URL = "https://platform.deepseek.com/api_keys"; export const OPENROUTER_KEYS_URL = "https://openrouter.ai/workspaces/default/keys"; + +/** + * Desktop app downloads. The installers carry version-less names (see + * packages/desktop/electron-builder.yml), which is what makes static links possible on a + * Pages-hosted site: GitHub's `releases/latest/download/` always serves the newest + * release, no client-side resolution needed. The OSS mirror stores immutable per-tag + * directories instead, so mirror links DO need the current tag — the download page reads + * it from the bucket's `latest.json` pointer (the same file install.sh resolves) and + * falls back to the GitHub links when that fetch fails (e.g. CORS not configured, or the + * mirror unreachable). + */ +export const OSS_ORIGIN = "https://penguin-harness-releases.oss-cn-beijing.aliyuncs.com"; +export const OSS_LATEST_JSON_URL = `${OSS_ORIGIN}/latest.json`; +export const GITHUB_LATEST_DOWNLOAD = `${REPO_URL}/releases/latest/download`; + +export interface DesktopInstaller { + /** Stable asset file name, identical on GitHub Releases and the OSS mirror. */ + file: string; + /** Language-neutral variant tag rendered on the button (arch / package format). */ + variant: string; +} + +/** Installers per platform card, in display order. */ +export const DESKTOP_INSTALLERS: Record<"mac" | "windows" | "linux", DesktopInstaller[]> = { + mac: [ + { file: "penguin-desktop-darwin-arm64.dmg", variant: "Apple Silicon" }, + { file: "penguin-desktop-darwin-x64.dmg", variant: "Intel" }, + ], + windows: [{ file: "penguin-desktop-win32-x64.exe", variant: "x64" }], + linux: [ + { file: "penguin-desktop-linux-x64.AppImage", variant: "AppImage" }, + { file: "penguin-desktop-linux-x64.deb", variant: "deb" }, + ], +}; + +/** Checksum list covering every desktop installer of a release. */ +export const DESKTOP_SHA256SUMS = "SHA256SUMS.desktop"; diff --git a/packages/landing/src/lib/nav-state.ts b/packages/landing/src/lib/nav-state.ts index 9d7d349..9f9bb09 100644 --- a/packages/landing/src/lib/nav-state.ts +++ b/packages/landing/src/lib/nav-state.ts @@ -13,7 +13,7 @@ export const SECTION_IDS = [ ] as const; export type SectionId = (typeof SECTION_IDS)[number]; -export type ActiveNavItem = SectionId | "blog" | null; +export type ActiveNavItem = SectionId | "blog" | "download" | null; function isSectionId(value: string): value is SectionId { return SECTION_IDS.some((id) => id === value); @@ -21,6 +21,7 @@ function isSectionId(value: string): value is SectionId { export function getActiveNavItem(pathname: string, hash: string): ActiveNavItem { if (pathname === "/blog" || pathname.startsWith("/blog/")) return "blog"; + if (pathname === "/download") return "download"; if (pathname !== "/") return null; const id = hash.startsWith("#") ? hash.slice(1) : hash; diff --git a/packages/landing/src/lib/strings-en.ts b/packages/landing/src/lib/strings-en.ts index 974bfd4..7d45895 100644 --- a/packages/landing/src/lib/strings-en.ts +++ b/packages/landing/src/lib/strings-en.ts @@ -25,6 +25,7 @@ export const en: Strings = { contract: "CONTRACT.md", features: "Features", blog: "Blog", + download: "Download", docs: "Docs", github: "GitHub", openMenu: "Open menu", @@ -78,6 +79,36 @@ export const en: Strings = { windows: "After unzipping you can also just double-click install.cmd.", }, offlineRelease: "Download offline packages from GitHub Releases", + desktopNote: + "Prefer a standalone app? The desktop app packages the Web interface and the server into a terminal-free installer (macOS / Windows / Linux), sharing the same local data as a CLI install.", + desktopPage: "Go to the desktop download page", + }, + + download: { + eyebrow: "Download", + title: "Download the desktop app", + subtitle: + "The full Web experience as a standalone application: it embeds the server and opens already signed in — no terminal, no login page; data lives in the same ~/.penguin/data root as a CLI install.", + recommended: "Your system", + platforms: { + mac: { name: "macOS", require: "macOS 11 or later, dmg disk image (pick your chip)" }, + windows: { name: "Windows", require: "Windows 10 or later (x64), NSIS installer" }, + linux: { + name: "Linux", + require: "x64 — AppImage runs in place, deb goes to your package manager", + }, + }, + statusOss: (version: string) => + `Connected to the OSS mirror (${version}) — downloads are served from the mirror.`, + statusGithub: "Downloads point at the latest GitHub Release.", + altGithub: "Download from GitHub instead", + altOss: "Use the OSS mirror instead", + checksums: "Checksums (SHA256SUMS.desktop)", + allReleases: "All releases", + unsignedNote: + 'Current builds are unsigned: on first launch use right-click → Open on macOS, and "More info → Run anyway" past Windows SmartScreen.', + cliHint: "Just need the CLI, or the Web UI in a browser?", + cliHintLink: "See the quick start", }, copy: { diff --git a/packages/landing/src/lib/strings.ts b/packages/landing/src/lib/strings.ts index 5e5e975..4c99af9 100644 --- a/packages/landing/src/lib/strings.ts +++ b/packages/landing/src/lib/strings.ts @@ -26,6 +26,7 @@ export const zh = { contract: "CONTRACT.md", features: "功能", blog: "博客", + download: "下载", docs: "文档", github: "GitHub", openMenu: "打开菜单", @@ -84,6 +85,32 @@ export const zh = { windows: "解压后也可直接双击 install.cmd 完成安装。", }, offlineRelease: "前往 GitHub Releases 下载离线安装包", + desktopNote: + "更喜欢独立应用?桌面端把 Web 界面与服务端打包成免终端的安装程序(macOS / Windows / Linux),与 CLI 安装共用同一份本地数据。", + desktopPage: "前往桌面端下载页", + }, + + download: { + eyebrow: "下载", + title: "下载桌面端", + subtitle: + "完整的 Web 体验打包为独立应用:内嵌服务端,打开即已登录——无需终端、无登录页;数据与 CLI 安装共用同一个 ~/.penguin/data 目录。", + recommended: "当前系统", + platforms: { + mac: { name: "macOS", require: "macOS 11 及以上,dmg 安装镜像(按芯片选择)" }, + windows: { name: "Windows", require: "Windows 10 及以上(x64),NSIS 安装程序" }, + linux: { name: "Linux", require: "x64,AppImage 免安装运行,或 deb 交给包管理器" }, + }, + statusOss: (version: string) => `已连接 OSS 国内镜像(${version}),点击即从镜像高速下载。`, + statusGithub: "下载指向 GitHub Releases 的最新版本。", + altGithub: "改从 GitHub 下载", + altOss: "改用 OSS 镜像下载", + checksums: "校验和(SHA256SUMS.desktop)", + allReleases: "全部版本", + unsignedNote: + "当前构建暂未签名:macOS 首次启动请右键 →「打开」;Windows 在 SmartScreen 提示中选「更多信息 → 仍要运行」。", + cliHint: "只需要命令行或浏览器里的 Web 界面?", + cliHintLink: "参见快速开始", }, copy: { diff --git a/packages/landing/src/pages/download.tsx b/packages/landing/src/pages/download.tsx new file mode 100644 index 0000000..4375bc8 --- /dev/null +++ b/packages/landing/src/pages/download.tsx @@ -0,0 +1,150 @@ +/** + * Desktop download page. The buttons are static GitHub `releases/latest/download/` + * links by default — always valid because installer names are version-less — and swap to + * the OSS mirror's immutable per-tag URLs once the bucket's `latest.json` pointer + * resolves (validated the same way the installers validate it; any failure, e.g. CORS + * not configured on the bucket or the mirror unreachable, silently keeps the GitHub + * links). Plain-anchor downloads are never CORS-gated — only this version lookup is. + */ +import { useEffect, useState } from "react"; +import { Link } from "react-router"; +import { S } from "../lib/strings"; +import { + DESKTOP_INSTALLERS, + DESKTOP_SHA256SUMS, + GITHUB_LATEST_DOWNLOAD, + OSS_LATEST_JSON_URL, + OSS_ORIGIN, + RELEASES_URL, +} from "../lib/links"; +import { Section } from "../components/section"; +import { DownloadIcon, ExternalLinkIcon } from "../components/icons"; + +type Platform = "mac" | "windows" | "linux"; +const PLATFORMS: Platform[] = ["mac", "windows", "linux"]; + +/** Coarse OS detection, used only to badge one card; absent or wrong detection changes nothing else. */ +function detectPlatform(): Platform | null { + const ua = navigator.userAgent; + if (/Windows/i.test(ua)) return "windows"; + if (/Macintosh|Mac OS X/i.test(ua)) return "mac"; + if (/Linux|X11/.test(ua) && !/Android/i.test(ua)) return "linux"; + return null; +} + +interface Mirror { + tag: string; + base: string; +} + +/** latest.json validated like the installer forwarders validate it: schema 1, safe v-tag, fixed bucket base. */ +function parseMirror(value: unknown): Mirror | null { + if (typeof value !== "object" || value === null) return null; + const manifest = value as { schemaVersion?: unknown; tag?: unknown; releaseBaseUrl?: unknown }; + if (manifest.schemaVersion !== 1 || typeof manifest.tag !== "string") return null; + if (!/^v[0-9A-Za-z][0-9A-Za-z._-]*$/.test(manifest.tag)) return null; + if (manifest.releaseBaseUrl !== `${OSS_ORIGIN}/releases/${manifest.tag}`) return null; + return { tag: manifest.tag, base: manifest.releaseBaseUrl }; +} + +export function DownloadPage() { + const [mirror, setMirror] = useState(null); + const [forceGithub, setForceGithub] = useState(false); + useEffect(() => { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 5000); + fetch(OSS_LATEST_JSON_URL, { signal: controller.signal }) + .then((res) => (res.ok ? res.json() : null)) + .then((body: unknown) => { + const parsed = parseMirror(body); + if (parsed) setMirror(parsed); + }) + .catch(() => {}) // The GitHub links already work; the mirror is a progressive upgrade. + .finally(() => clearTimeout(timer)); + return () => { + clearTimeout(timer); + controller.abort(); + }; + }, []); + + const detected = detectPlatform(); + const viaMirror = mirror !== null && !forceGithub; + const hrefFor = (file: string) => + viaMirror ? `${mirror.base}/${file}` : `${GITHUB_LATEST_DOWNLOAD}/${file}`; + + const textLink = + "inline-flex items-center gap-1 text-brand-700 underline decoration-brand-300 underline-offset-2 transition-colors hover:text-brand-600 dark:text-brand-300 dark:decoration-brand-700"; + + return ( +
    +
    + {PLATFORMS.map((platform) => ( +
    +
    +

    + {S.download.platforms[platform].name} +

    + {detected === platform && ( + + {S.download.recommended} + + )} +
    +

    + {S.download.platforms[platform].require} +

    +
    + {DESKTOP_INSTALLERS[platform].map(({ file, variant }) => ( + + + {variant} + + ))} +
    +
    + ))} +
    + +
    +

    + {viaMirror ? S.download.statusOss(mirror.tag) : S.download.statusGithub}{" "} + {mirror !== null && ( + + )} +

    +

    + + {S.download.checksums} + + · + + {S.download.allReleases} + + +

    +

    + {S.download.unsignedNote} +

    +

    + {S.download.cliHint}{" "} + + {S.download.cliHintLink} + +

    +
    +
    + ); +} diff --git a/packages/landing/src/router.tsx b/packages/landing/src/router.tsx index 51e4ad2..1d9ef87 100644 --- a/packages/landing/src/router.tsx +++ b/packages/landing/src/router.tsx @@ -13,6 +13,7 @@ import { NeonBackground } from "./components/neon-bg"; import { HomePage } from "./pages/home"; import { BlogListPage } from "./pages/blog-list"; import { BlogPostPage } from "./pages/blog-post"; +import { DownloadPage } from "./pages/download"; /** * Deep links into the docs SPA that missed a real file (e.g. an unknown slug) land on @@ -76,6 +77,7 @@ export function AppRouter() { } /> } /> } /> + } /> } /> {/* Outside Layout: a pure redirect, no nav/footer flash. */} diff --git a/packages/landing/src/sections/quickstart.tsx b/packages/landing/src/sections/quickstart.tsx index 4da5620..45dc3ab 100644 --- a/packages/landing/src/sections/quickstart.tsx +++ b/packages/landing/src/sections/quickstart.tsx @@ -5,6 +5,7 @@ * the string dictionaries. */ import { useState } from "react"; +import { Link } from "react-router"; import type { ReactNode } from "react"; import { S } from "../lib/strings"; import { @@ -199,6 +200,17 @@ export function Quickstart() {

    )} + {/* Below both install methods: the desktop app replaces this whole flow for + users who never want a terminal, so it stays visible on every switch. */} +

    + {S.install.desktopNote}{" "} + + {S.install.desktopPage} + +

    {mode === "web" ? ( diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index 4165731..741bd92 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -14,7 +14,12 @@ import { execSync } from "node:child_process"; import { cpSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { absoluteUrl, blogRoutes, docsRoutes } from "../packages/landing/scripts/site-routes.mjs"; +import { + absoluteUrl, + blogRoutes, + docsRoutes, + pageRoutes, +} from "../packages/landing/scripts/site-routes.mjs"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const base = process.env.BASE_PATH ?? "/"; @@ -40,7 +45,7 @@ cpSync(join(root, "packages", "docs", "dist"), docsTarget, { recursive: true }); // that has to name routes from both sites. It is also the only way a crawler learns // those routes — both pages ship an empty
    and build their navigation // in JS, so following links is not an option. -const routes = [{ route: "/" }, ...blogRoutes(), ...docsRoutes()]; +const routes = [{ route: "/" }, ...pageRoutes(), ...blogRoutes(), ...docsRoutes()]; const sitemap = [ '', '', diff --git a/scripts/publish-release-to-oss.sh b/scripts/publish-release-to-oss.sh index eb1030f..dd0e1be 100644 --- a/scripts/publish-release-to-oss.sh +++ b/scripts/publish-release-to-oss.sh @@ -71,6 +71,17 @@ penguin-darwin-arm64.tar.gz penguin-universal.tar.gz penguin-win32-x64.zip " +# Desktop installers carry version-less names (see packages/desktop/electron-builder.yml) +# and are verified as a set through SHA256SUMS.desktop instead of per-file .sha256 twins. +DESKTOP_INSTALLERS=" +penguin-desktop-darwin-arm64.dmg +penguin-desktop-darwin-arm64.zip +penguin-desktop-darwin-x64.dmg +penguin-desktop-darwin-x64.zip +penguin-desktop-linux-x64.AppImage +penguin-desktop-linux-x64.deb +penguin-desktop-win32-x64.exe +" FILES="$BUNDLES penguin-linux-x64.tar.gz.sha256 penguin-linux-arm64.tar.gz.sha256 @@ -79,6 +90,8 @@ penguin-darwin-arm64.tar.gz.sha256 penguin-universal.tar.gz.sha256 penguin-win32-x64.zip.sha256 SHA256SUMS +$DESKTOP_INSTALLERS +SHA256SUMS.desktop install.sh install.ps1 " @@ -94,6 +107,7 @@ for bundle in $BUNDLES; do (cd "$RELEASE_DIR" && sha256sum -c "$bundle.sha256") done (cd "$RELEASE_DIR" && sha256sum -c SHA256SUMS) +(cd "$RELEASE_DIR" && sha256sum -c SHA256SUMS.desktop) WORK_DIR="$(mktemp -d)" trap 'rm -rf "$WORK_DIR"' EXIT