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
6 changes: 4 additions & 2 deletions apps/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"typecheck": "astro sync && astro check"
"typecheck": "astro sync && astro check",
"test": "vitest run"
},
"dependencies": {
"astro": "^6.0.4"
},
"devDependencies": {
"@astrojs/check": "^0.9.7",
"typescript": "catalog:"
"typescript": "catalog:",
"vitest": "catalog:"
}
}
123 changes: 123 additions & 0 deletions apps/marketing/src/lib/download.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { describe, expect, it } from "vitest";
import { detectPlatform, pickAsset } from "./download";
import type { ReleaseAsset } from "./releases";

/* -------------------------------------------------------------------------- */
/* Fixture helpers */
/* -------------------------------------------------------------------------- */

function asset(name: string, url = `https://example.com/${name}`): ReleaseAsset {
return { name, browser_download_url: url };
}

const SAMPLE_ASSETS: ReleaseAsset[] = [
asset("okcode-1.0.0-arm64.dmg"),
asset("okcode-1.0.0-x64.dmg"),
asset("okcode-1.0.0-x64.exe"),
asset("okcode-1.0.0-x64.AppImage"),
];

/* -------------------------------------------------------------------------- */
/* detectPlatform */
/* -------------------------------------------------------------------------- */

describe("detectPlatform", () => {
it("returns Windows platform for a Windows user-agent", () => {
const result = detectPlatform("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
expect(result).toEqual({ os: "win", label: "Download for Windows" });
});

it("returns macOS platform for a macOS user-agent", () => {
const result = detectPlatform("Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)");
expect(result).toEqual({ os: "mac", label: "Download for macOS", arch: "arm64" });
});

it("returns Linux platform for a Linux user-agent", () => {
const result = detectPlatform("Mozilla/5.0 (X11; Linux x86_64)");
expect(result).toEqual({ os: "linux", label: "Download for Linux" });
});

it("returns null for an unrecognised user-agent", () => {
expect(detectPlatform("")).toBeNull();
expect(detectPlatform("SomeBot/1.0")).toBeNull();
});

it("is case-insensitive", () => {
expect(detectPlatform("windows")).not.toBeNull();
expect(detectPlatform("LINUX")).not.toBeNull();
expect(detectPlatform("mac")).not.toBeNull();
});
});

/* -------------------------------------------------------------------------- */
/* pickAsset */
/* -------------------------------------------------------------------------- */

describe("pickAsset", () => {
describe("Windows", () => {
const platform = { os: "win", label: "Download for Windows" };

it("selects the .exe asset", () => {
expect(pickAsset(SAMPLE_ASSETS, platform)).toBe("https://example.com/okcode-1.0.0-x64.exe");
});

it("returns null when no .exe is available", () => {
const assets = [asset("okcode-1.0.0-arm64.dmg")];
expect(pickAsset(assets, platform)).toBeNull();
});
});

describe("macOS", () => {
it("prefers the arch-specific .dmg", () => {
const platform = { os: "mac", label: "Download for macOS", arch: "arm64" };
expect(pickAsset(SAMPLE_ASSETS, platform)).toBe("https://example.com/okcode-1.0.0-arm64.dmg");
});

it("falls back to any .dmg when arch-specific is missing", () => {
const platform = { os: "mac", label: "Download for macOS", arch: "arm64" };
const assets = [asset("okcode-1.0.0.dmg")];
expect(pickAsset(assets, platform)).toBe("https://example.com/okcode-1.0.0.dmg");
});

it("selects x64 .dmg when arch is x64", () => {
const platform = { os: "mac", label: "Download for macOS", arch: "x64" };
expect(pickAsset(SAMPLE_ASSETS, platform)).toBe("https://example.com/okcode-1.0.0-x64.dmg");
});

it("returns null when no .dmg is available", () => {
const platform = { os: "mac", label: "Download for macOS", arch: "arm64" };
const assets = [asset("okcode-1.0.0-x64.exe")];
expect(pickAsset(assets, platform)).toBeNull();
});
});

describe("Linux", () => {
const platform = { os: "linux", label: "Download for Linux" };

it("selects the .AppImage asset", () => {
expect(pickAsset(SAMPLE_ASSETS, platform)).toBe(
"https://example.com/okcode-1.0.0-x64.AppImage",
);
});

it("returns null when no .AppImage is available", () => {
const assets = [asset("okcode-1.0.0-arm64.dmg")];
expect(pickAsset(assets, platform)).toBeNull();
});
});

describe("unknown platform", () => {
it("returns null for an unrecognised OS", () => {
const platform = { os: "freebsd", label: "Download" };
expect(pickAsset(SAMPLE_ASSETS, platform)).toBeNull();
});
});

describe("empty assets", () => {
it("returns null when assets array is empty", () => {
expect(pickAsset([], { os: "win", label: "Download for Windows" })).toBeNull();
expect(pickAsset([], { os: "mac", label: "Download for macOS", arch: "arm64" })).toBeNull();
expect(pickAsset([], { os: "linux", label: "Download for Linux" })).toBeNull();
});
});
});
35 changes: 35 additions & 0 deletions apps/marketing/src/lib/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ReleaseAsset } from "./releases";

export interface Platform {
os: string;
label: string;
arch?: string;
}

export function detectPlatform(userAgent: string): Platform | null {
if (/Win/i.test(userAgent)) return { os: "win", label: "Download for Windows" };
if (/Mac/i.test(userAgent)) {
return {
os: "mac",
label: "Download for macOS",
arch: "arm64",
};
}
if (/Linux/i.test(userAgent)) return { os: "linux", label: "Download for Linux" };
return null;
}

export function pickAsset(assets: ReleaseAsset[], platform: Platform): string | null {
if (platform.os === "win") {
return assets.find((a) => a.name.endsWith("-x64.exe"))?.browser_download_url ?? null;
}
if (platform.os === "mac") {
const preferred = assets.find((a) => a.name.endsWith(`-${platform.arch}.dmg`));
const fallback = assets.find((a) => a.name.endsWith(".dmg"));
return (preferred ?? fallback)?.browser_download_url ?? null;
}
if (platform.os === "linux") {
return assets.find((a) => a.name.endsWith(".AppImage"))?.browser_download_url ?? null;
}
return null;
}
36 changes: 4 additions & 32 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -395,42 +395,14 @@ const themeIdeas = [

<script>
import { fetchLatestRelease, RELEASES_URL } from "../lib/releases";

function detectPlatform() {
const ua = navigator.userAgent;
if (/Win/i.test(ua)) return { os: "win", label: "Download for Windows" };
if (/Mac/i.test(ua)) {
return {
os: "mac",
label: "Download for macOS",
arch: "arm64",
};
}
if (/Linux/i.test(ua)) return { os: "linux", label: "Download for Linux" };
return null;
}

function pickAsset(assets: {name: string; browser_download_url: string}[], platform: {os: string; label: string; arch?: string}) {
if (platform.os === "win") {
return assets.find((a) => a.name.endsWith("-x64.exe"))?.browser_download_url ?? null;
}
if (platform.os === "mac") {
const preferred = assets.find((a) => a.name.endsWith(`-${platform.arch}.dmg`));
const fallback = assets.find((a) => a.name.endsWith(".dmg"));
return (preferred ?? fallback)?.browser_download_url ?? null;
}
if (platform.os === "linux") {
return assets.find((a) => a.name.endsWith(".AppImage"))?.browser_download_url ?? null;
}
return null;
}
import { detectPlatform, pickAsset } from "../lib/download";

function initDownloadButton() {
const btn = document.getElementById("download-btn") as HTMLAnchorElement | null;
const label = document.getElementById("download-label");
if (!btn || !label) return;

const platform = detectPlatform();
const platform = detectPlatform(navigator.userAgent);
if (!platform) return;

document.documentElement.dataset.platform = platform.os;
Expand All @@ -454,8 +426,8 @@ const themeIdeas = [
const groups = document.querySelectorAll("[data-tab-group]");
groups.forEach((group) => {
const defaultTab = group.getAttribute("data-tab-default");
const buttons = Array.from(group.querySelectorAll("[data-tab-button]"));
const panels = Array.from(group.querySelectorAll("[data-tab-panel]"));
const buttons = Array.from(group.querySelectorAll<HTMLElement>("[data-tab-button]"));
const panels = Array.from(group.querySelectorAll<HTMLElement>("[data-tab-panel]"));
const heroOverlay = group.querySelector("[data-hero-overlay]");

const activate = (id: string) => {
Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading