mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat(desktop): build Windows arm64 alongside x64
Multi-arch Windows build in one electron-builder pass, with the website
self-healing across the rename. Asset names go from
`Paseo-Setup-${version}.exe` to `Paseo-Setup-${version}-{x64,arm64}.exe`.
The website now reads the actual installer filename off the latest
GitHub release, so legacy single-arch releases keep rendering a single
"Download" pill while dual-arch releases promote to "Intel / x64" +
"ARM64" automatically. No homepage code change needed when ARM ships.
This commit is contained in:
2
.github/workflows/desktop-release.yml
vendored
2
.github/workflows/desktop-release.yml
vendored
@@ -355,7 +355,7 @@ jobs:
|
|||||||
PASEO_DESKTOP_SMOKE: "1"
|
PASEO_DESKTOP_SMOKE: "1"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
build_args=(-- --publish never --win --x64)
|
build_args=(-- --publish never --win --x64 --arm64)
|
||||||
build_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
build_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
||||||
build_args+=("-c.publish.channel=$RELEASE_CHANNEL")
|
build_args+=("-c.publish.channel=$RELEASE_CHANNEL")
|
||||||
npm run build --workspace="$DESKTOP_WORKSPACE" "${build_args[@]}"
|
npm run build --workspace="$DESKTOP_WORKSPACE" "${build_args[@]}"
|
||||||
|
|||||||
@@ -49,13 +49,20 @@ linux:
|
|||||||
- rpm
|
- rpm
|
||||||
- tar.gz
|
- tar.gz
|
||||||
win:
|
win:
|
||||||
|
artifactName: "Paseo-Setup-${version}-${arch}.${ext}"
|
||||||
icon: assets/icon.ico
|
icon: assets/icon.ico
|
||||||
extraResources:
|
extraResources:
|
||||||
- from: bin/paseo.cmd
|
- from: bin/paseo.cmd
|
||||||
to: bin/paseo.cmd
|
to: bin/paseo.cmd
|
||||||
target:
|
target:
|
||||||
- nsis
|
- target: nsis
|
||||||
- zip
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
|
- target: zip
|
||||||
|
arch:
|
||||||
|
- x64
|
||||||
|
- arm64
|
||||||
nsis:
|
nsis:
|
||||||
oneClick: false
|
oneClick: false
|
||||||
perMachine: false
|
perMachine: false
|
||||||
|
|||||||
@@ -1030,9 +1030,9 @@ function GetStarted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DownloadButton() {
|
function DownloadButton() {
|
||||||
const { version } = useRelease();
|
const release = useRelease();
|
||||||
const detectedPlatform = useDetectedPlatform();
|
const detectedPlatform = useDetectedPlatform();
|
||||||
const primary = getDownloadOptions(version).find((o) => o.platform === detectedPlatform)!;
|
const primary = getDownloadOptions(release).find((o) => o.platform === detectedPlatform)!;
|
||||||
const PrimaryIcon = primary.icon;
|
const PrimaryIcon = primary.icon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,14 @@ export function releaseBase(version: string) {
|
|||||||
return `https://github.com/getpaseo/paseo/releases/download/v${version}`;
|
return `https://github.com/getpaseo/paseo/releases/download/v${version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadUrls(version: string) {
|
export interface ReleaseAssetInfo {
|
||||||
|
version: string;
|
||||||
|
windowsX64Asset: string | null;
|
||||||
|
windowsArm64Asset: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadUrls(release: ReleaseAssetInfo) {
|
||||||
|
const { version, windowsX64Asset, windowsArm64Asset } = release;
|
||||||
const base = releaseBase(version);
|
const base = releaseBase(version);
|
||||||
return {
|
return {
|
||||||
macAppleSilicon: `${base}/Paseo-${version}-arm64.dmg`,
|
macAppleSilicon: `${base}/Paseo-${version}-arm64.dmg`,
|
||||||
@@ -12,7 +19,8 @@ export function downloadUrls(version: string) {
|
|||||||
linuxAppImage: `${base}/Paseo-${version}-x86_64.AppImage`,
|
linuxAppImage: `${base}/Paseo-${version}-x86_64.AppImage`,
|
||||||
linuxDeb: `${base}/Paseo-${version}-amd64.deb`,
|
linuxDeb: `${base}/Paseo-${version}-amd64.deb`,
|
||||||
linuxRpm: `${base}/Paseo-${version}-x86_64.rpm`,
|
linuxRpm: `${base}/Paseo-${version}-x86_64.rpm`,
|
||||||
windowsExe: `${base}/Paseo-Setup-${version}.exe`,
|
windowsExeX64: `${base}/${windowsX64Asset ?? `Paseo-Setup-${version}.exe`}`,
|
||||||
|
windowsExeArm64: windowsArm64Asset ? `${base}/${windowsArm64Asset}` : null,
|
||||||
androidApk: `${base}/paseo-v${version}-android.apk`,
|
androidApk: `${base}/paseo-v${version}-android.apk`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -30,8 +38,8 @@ export interface DownloadOption {
|
|||||||
icon: (props: React.SVGProps<SVGSVGElement>) => React.ReactElement;
|
icon: (props: React.SVGProps<SVGSVGElement>) => React.ReactElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDownloadOptions(version: string): DownloadOption[] {
|
export function getDownloadOptions(release: ReleaseAssetInfo): DownloadOption[] {
|
||||||
const urls = downloadUrls(version);
|
const urls = downloadUrls(release);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
platform: "mac-silicon",
|
platform: "mac-silicon",
|
||||||
@@ -48,7 +56,7 @@ export function getDownloadOptions(version: string): DownloadOption[] {
|
|||||||
{
|
{
|
||||||
platform: "windows",
|
platform: "windows",
|
||||||
label: "Windows",
|
label: "Windows",
|
||||||
href: urls.windowsExe,
|
href: urls.windowsExeX64,
|
||||||
icon: WindowsIcon,
|
icon: WindowsIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface GitHubRelease {
|
|||||||
const REQUIRED_ASSET_PATTERNS = [
|
const REQUIRED_ASSET_PATTERNS = [
|
||||||
/Paseo-.*-arm64\.dmg$/, // Mac Apple Silicon
|
/Paseo-.*-arm64\.dmg$/, // Mac Apple Silicon
|
||||||
/Paseo-.*-x86_64\.AppImage$/, // Linux AppImage
|
/Paseo-.*-x86_64\.AppImage$/, // Linux AppImage
|
||||||
/Paseo-Setup-.*\.exe$/, // Windows
|
/Paseo-Setup-.*\.exe$/, // Windows (any arch)
|
||||||
];
|
];
|
||||||
|
|
||||||
function hasRequiredAssets(release: GitHubRelease): boolean {
|
function hasRequiredAssets(release: GitHubRelease): boolean {
|
||||||
@@ -24,14 +24,40 @@ function hasRequiredAssets(release: GitHubRelease): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickWindowsAssets(assets: GitHubAsset[]) {
|
||||||
|
const x64Suffixed = assets.find((a) => /Paseo-Setup-.*-x64\.exe$/.test(a.name));
|
||||||
|
const arm64 = assets.find((a) => /Paseo-Setup-.*-arm64\.exe$/.test(a.name));
|
||||||
|
const legacy = assets.find(
|
||||||
|
(a) =>
|
||||||
|
/Paseo-Setup-.*\.exe$/.test(a.name) &&
|
||||||
|
!a.name.endsWith("-x64.exe") &&
|
||||||
|
!a.name.endsWith("-arm64.exe"),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
x64: (x64Suffixed ?? legacy)?.name ?? null,
|
||||||
|
arm64: arm64?.name ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function versionFromTag(tag: string): string {
|
function versionFromTag(tag: string): string {
|
||||||
return tag.replace(/^v/, "");
|
return tag.replace(/^v/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReleaseInfo {
|
||||||
|
version: string;
|
||||||
|
windowsX64Asset: string | null;
|
||||||
|
windowsArm64Asset: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
const GITHUB_RELEASES_URL = "https://api.github.com/repos/getpaseo/paseo/releases?per_page=10";
|
const GITHUB_RELEASES_URL = "https://api.github.com/repos/getpaseo/paseo/releases?per_page=10";
|
||||||
|
|
||||||
async function fetchLatestReadyRelease(): Promise<string> {
|
async function fetchLatestReadyRelease(): Promise<ReleaseInfo> {
|
||||||
const fallback = websitePackage.version.replace(/-.*$/, "");
|
const fallbackVersion = websitePackage.version.replace(/-.*$/, "");
|
||||||
|
const fallback: ReleaseInfo = {
|
||||||
|
version: fallbackVersion,
|
||||||
|
windowsX64Asset: `Paseo-Setup-${fallbackVersion}.exe`,
|
||||||
|
windowsArm64Asset: null,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(GITHUB_RELEASES_URL, {
|
const res = await fetch(GITHUB_RELEASES_URL, {
|
||||||
@@ -52,13 +78,18 @@ async function fetchLatestReadyRelease(): Promise<string> {
|
|||||||
|
|
||||||
const releases = (await res.json()) as GitHubRelease[];
|
const releases = (await res.json()) as GitHubRelease[];
|
||||||
const ready = releases.find((r) => !r.prerelease && !r.draft && hasRequiredAssets(r));
|
const ready = releases.find((r) => !r.prerelease && !r.draft && hasRequiredAssets(r));
|
||||||
return ready ? versionFromTag(ready.tag_name) : fallback;
|
if (!ready) return fallback;
|
||||||
|
const win = pickWindowsAssets(ready.assets);
|
||||||
|
return {
|
||||||
|
version: versionFromTag(ready.tag_name),
|
||||||
|
windowsX64Asset: win.x64,
|
||||||
|
windowsArm64Asset: win.arm64,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLatestRelease = createServerFn({ method: "GET" }).handler(async () => {
|
export const getLatestRelease = createServerFn({ method: "GET" }).handler(async () => {
|
||||||
const version = await fetchLatestReadyRelease();
|
return fetchLatestReadyRelease();
|
||||||
return { version };
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import { getStarCount } from "~/stars";
|
|||||||
|
|
||||||
interface ReleaseContext {
|
interface ReleaseContext {
|
||||||
version: string;
|
version: string;
|
||||||
|
windowsX64Asset: string | null;
|
||||||
|
windowsArm64Asset: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StarsContext {
|
interface StarsContext {
|
||||||
stars: string;
|
stars: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReleaseCtx = createContext<ReleaseContext>({ version: "" });
|
const ReleaseCtx = createContext<ReleaseContext>({
|
||||||
|
version: "",
|
||||||
|
windowsX64Asset: null,
|
||||||
|
windowsArm64Asset: null,
|
||||||
|
});
|
||||||
const StarsCtx = createContext<StarsContext>({ stars: "" });
|
const StarsCtx = createContext<StarsContext>({ stars: "" });
|
||||||
|
|
||||||
const PLAUSIBLE_INIT_SCRIPT = {
|
const PLAUSIBLE_INIT_SCRIPT = {
|
||||||
|
|||||||
@@ -33,8 +33,9 @@ const homebrewTrigger = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
function Download() {
|
function Download() {
|
||||||
const { version } = useRelease();
|
const release = useRelease();
|
||||||
const urls = downloadUrls(version);
|
const { version } = release;
|
||||||
|
const urls = downloadUrls(release);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
@@ -130,7 +131,11 @@ function Download() {
|
|||||||
<span className="font-medium">Windows</span>
|
<span className="font-medium">Windows</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<DownloadPill href={urls.windowsExe} label="Download" />
|
<DownloadPill
|
||||||
|
href={urls.windowsExeX64}
|
||||||
|
label={urls.windowsExeArm64 ? "Intel / x64" : "Download"}
|
||||||
|
/>
|
||||||
|
{urls.windowsExeArm64 && <DownloadPill href={urls.windowsExeArm64} label="ARM64" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user