Deleting the old Windows build from R2 surfaced something worse: the two macOS DMG links had no objects behind them at all - the mac builds were never uploaded, so every mac download click has been a 404. - R2 now holds exactly one desktop artifact: AC/AgentCompany-1.1.0-x64 .exe (old exe deleted on operator instruction, verified 404) - The release manifest drops the macOS entry with a comment explaining how it comes back (upload real DMGs first); footer mac links removed; hero reads "Available on Windows - macOS build coming soon" - Root fix in platform-support: FAQ/JSON-LD copy now derives from whatever platforms the manifest actually ships instead of hard-requiring macos+windows, so the site structurally cannot advertise a platform without a real artifact behind it - Landing e2e updated (>=1 tracked asset) and green; redeployed and verified live: no r2.dev leaks, no mac links, coming-soon copy shown, tracked download 302s to the 1.1.0 installer
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import {
|
|
platformDownloads,
|
|
type PlatformDownload,
|
|
type PlatformDownloadAsset,
|
|
type ReleaseArchitecture,
|
|
} from "./release-links.generated";
|
|
|
|
const ARCHITECTURE_ORDER: ReleaseArchitecture[] = ["arm64", "x64", "x86"];
|
|
|
|
export type PlatformSupportSummary = {
|
|
downloadAnswer: string;
|
|
platformAnswer: string;
|
|
};
|
|
|
|
export const platformSupportSummary = buildPlatformSupportSummary(platformDownloads);
|
|
|
|
/**
|
|
* Copy derives from whatever platforms the release manifest actually ships,
|
|
* so the download matrix stays the single source of truth and the site can
|
|
* never advertise a platform without a real artifact behind it.
|
|
*/
|
|
export function buildPlatformSupportSummary(downloads: PlatformDownload[]): PlatformSupportSummary {
|
|
if (downloads.length === 0) {
|
|
throw new Error("Release download metadata must include at least one platform.");
|
|
}
|
|
return {
|
|
downloadAnswer:
|
|
`Use the download section on this page to choose the build that matches your architecture and package format. ` +
|
|
downloads.map(formatDownloadSentence).join(" "),
|
|
platformAnswer:
|
|
`AgentCompany is available on ${joinWithAnd(downloads.map(formatAvailability))}. ` +
|
|
`It is built with Electron and the desktop feature set stays aligned across all supported targets.`,
|
|
};
|
|
}
|
|
|
|
function joinWithAnd(parts: string[]): string {
|
|
if (parts.length === 1) {
|
|
return parts[0];
|
|
}
|
|
if (parts.length === 2) {
|
|
return `${parts[0]} and ${parts[1]}`;
|
|
}
|
|
return `${parts.slice(0, -1).join(", ")}, and ${parts.at(-1)}`;
|
|
}
|
|
|
|
function formatDownloadSentence(download: PlatformDownload): string {
|
|
const architectures = formatArchitectureList(collectArchitectures(download));
|
|
|
|
if (!download.packageDescription.trim()) {
|
|
throw new Error(`Release platform ${download.id} must include package format copy.`);
|
|
}
|
|
|
|
return `${download.title} ships as ${download.packageDescription} for ${architectures}.`;
|
|
}
|
|
|
|
function formatAvailability(download: PlatformDownload): string {
|
|
return `${download.title} (${formatArchitectureList(collectArchitectures(download), "comma")})`;
|
|
}
|
|
|
|
function collectArchitectures(download: PlatformDownload): ReleaseArchitecture[] {
|
|
const architectures = new Set<ReleaseArchitecture>();
|
|
|
|
for (const asset of download.assets) {
|
|
collectAssetArchitectures(download.id, asset).forEach((architecture) => architectures.add(architecture));
|
|
}
|
|
|
|
return [...architectures].sort((left, right) => architectureRank(left) - architectureRank(right));
|
|
}
|
|
|
|
function collectAssetArchitectures(
|
|
platformId: PlatformDownload["id"],
|
|
asset: PlatformDownloadAsset,
|
|
): ReleaseArchitecture[] {
|
|
if (asset.architectures.length === 0) {
|
|
throw new Error(`Release asset ${platformId}/${asset.id} must declare at least one architecture.`);
|
|
}
|
|
|
|
for (const architecture of asset.architectures) {
|
|
if (!isReleaseArchitecture(architecture)) {
|
|
throw new Error(`Release asset ${platformId}/${asset.id} declares unsupported architecture: ${architecture}`);
|
|
}
|
|
}
|
|
|
|
return asset.architectures;
|
|
}
|
|
|
|
function isReleaseArchitecture(value: string): value is ReleaseArchitecture {
|
|
return ARCHITECTURE_ORDER.includes(value as ReleaseArchitecture);
|
|
}
|
|
|
|
function architectureRank(architecture: ReleaseArchitecture): number {
|
|
return ARCHITECTURE_ORDER.indexOf(architecture);
|
|
}
|
|
|
|
function formatArchitectureList(
|
|
architectures: ReleaseArchitecture[],
|
|
style: "and" | "comma" = "and",
|
|
): string {
|
|
if (architectures.length === 0) {
|
|
throw new Error("Release download metadata must include at least one architecture.");
|
|
}
|
|
if (style === "comma") {
|
|
return architectures.join(", ");
|
|
}
|
|
if (architectures.length === 1) {
|
|
return architectures[0];
|
|
}
|
|
if (architectures.length === 2) {
|
|
return `${architectures[0]} and ${architectures[1]}`;
|
|
}
|
|
return `${architectures.slice(0, -1).join(", ")}, and ${architectures.at(-1)}`;
|
|
}
|