Skip to content

Commit 2f8af13

Browse files
committed
feat(download): add Windows launcher download from GitHub and download site
1 parent 2c9b6d3 commit 2f8af13

File tree

5 files changed

+204
-68
lines changed

5 files changed

+204
-68
lines changed

app/download/components/download-page-client.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import { type PlatformInfoData } from './platforms/platform-info';
1313

1414
interface DownloadPageClientProps {
1515
release: GitHubRelease | null;
16+
launcherRelease: GitHubRelease | null;
1617
}
1718

18-
export default function DownloadPageClient({ release }: DownloadPageClientProps) {// 使用 download 命名空间
19+
export default function DownloadPageClient({ release, launcherRelease }: DownloadPageClientProps) {// 使用 download 命名空间
1920

2021
// 平台配置 - 直接使用 useMemo,不依赖 t 函数
2122
const platforms = useMemo((): PlatformInfoData[] => [
@@ -169,7 +170,7 @@ export default function DownloadPageClient({ release }: DownloadPageClientProps)
169170
{selectedPlatform && (
170171
<section className="py-16 bg-muted/30">
171172
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
172-
<PlatformFactory platform={selectedPlatform} release={release} />
173+
<PlatformFactory platform={selectedPlatform} release={release} launcherRelease={launcherRelease} />
173174
</div>
174175
</section>
175176
)}

app/download/components/platforms/platform-factory.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ import { type GitHubRelease } from '@/lib/github';
1010
interface PlatformFactoryProps {
1111
platform: PlatformInfoData;
1212
release: GitHubRelease | null;
13+
launcherRelease: GitHubRelease | null;
1314
className?: string;
1415
}
1516

16-
export default function PlatformFactory({ platform, release, className }: PlatformFactoryProps) {switch (platform.id) {
17+
export default function PlatformFactory({ platform, release, launcherRelease, className }: PlatformFactoryProps) {switch (platform.id) {
1718
case 'linux':
1819
return <LinuxDownloadSection platform={platform} release={release} className={className} />;
1920
case 'docker':
2021
return <DockerDownloadSection platform={platform} release={release} className={className} />;
2122
case 'macos':
2223
return <MacOSDownloadSection platform={platform} release={release} className={className} />;
2324
case 'windows':
24-
return <WindowsDownloadSection platform={platform} release={release} className={className} />;
25+
return <WindowsDownloadSection platform={platform} release={release} launcherRelease={launcherRelease} className={className} />;
2526
default:
2627
// Fallback for unknown platforms
2728
return (

app/download/components/platforms/windows-download-section.tsx

Lines changed: 128 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,156 @@
11
'use client'
22

3+
import { formatVersion, type GitHubRelease } from '@/lib/github';
34
import { cn } from "@/lib/utils";
4-
import { DownloadIcon } from "lucide-react";
5-
import CodeBlock from "../code-block";
6-
import Note from "../common/note";
5+
import { DownloadIcon, ExternalLinkIcon } from "lucide-react";
76
import PlatformHeader from "../common/platform-header";
87
import { type PlatformInfoData } from "./platform-info";
9-
import { getDownloadUrlForPlatform, type GitHubRelease } from '@/lib/github';
108

119
interface WindowsDownloadSectionProps {
1210
platform: PlatformInfoData;
1311
release: GitHubRelease | null;
12+
launcherRelease: GitHubRelease | null;
1413
className?: string;
1514
}
1615

17-
export default function WindowsDownloadSection({ platform, release, className }: WindowsDownloadSectionProps) {
18-
// Get download URL from release assets or use fallback
19-
const downloadUrl = release
20-
? getDownloadUrlForPlatform(release, 'windows', 'x86_64')
21-
: null;
22-
23-
const fallbackUrl = release?.html_url || 'https://github.com/rustfs/rustfs/releases/latest';
24-
const finalDownloadUrl = downloadUrl || fallbackUrl;
25-
26-
// Extract filename from URL for code block
27-
const getFilenameFromUrl = (url: string) => {
28-
if (url.includes('github.com')) {
29-
return 'rustfs.exe';
30-
}
31-
const match = url.match(/([^\/]+\.exe)/);
32-
return match ? match[1] : 'rustfs.exe';
33-
};
16+
export default function WindowsDownloadSection({ platform, launcherRelease, className }: WindowsDownloadSectionProps) {
17+
const releaseUrl = launcherRelease?.html_url || 'https://github.com/rustfs/launcher/releases/latest';
18+
const version = launcherRelease ? formatVersion(launcherRelease.tag_name) : 'latest';
19+
const versionTag = launcherRelease?.tag_name || 'latest';
20+
const versionWithoutV = versionTag.startsWith('v') ? versionTag.slice(1) : versionTag;
21+
const versionWithV = versionTag.startsWith('v') ? versionTag : `v${versionTag}`;
22+
const installerFilename = `rustfs-launcher-windows-x86_64-v${versionWithoutV}-setup.exe`;
23+
const directDownloadUrl = launcherRelease
24+
? `https://dl.rustfs.com/artifacts/rustfs-launcher/release/rustfs-launcher-windows-x86_64-${versionWithV}-setup.exe`
25+
: 'https://dl.rustfs.com/artifacts/rustfs-launcher/release/rustfs-launcher-windows-x86_64-latest-setup.exe';
3426

3527
return (
3628
<div className={cn("space-y-8", className)}>
3729
{/* Platform Header */}
3830
<PlatformHeader platform={platform} />
3931

40-
{/* Binary Downloads */}
32+
{/* Installer Download */}
4133
<div className="space-y-6">
42-
<h3 className="text-lg font-semibold text-foreground">{'Binary Downloads'}</h3>
34+
<h3 className="text-lg font-semibold text-foreground">{'Download from GitHub'}</h3>
4335

44-
{/* x86_64 Variant */}
4536
<div className="space-y-4">
46-
<div className="flex items-center justify-between">
47-
<div>
48-
<h4 className="font-medium text-foreground">x86_64</h4>
49-
<p className="text-sm text-muted-foreground">
50-
{'Architecture'}: x86_64
51-
</p>
37+
<div className="p-6 bg-card rounded-lg border border-border">
38+
<div className="space-y-4">
39+
<div>
40+
<h4 className="font-medium text-foreground mb-2">{'Latest Version'}</h4>
41+
<p className="text-sm text-muted-foreground mb-4">
42+
{launcherRelease ? `Current latest version: ${version}` : 'Fetching latest version information...'}
43+
</p>
44+
</div>
45+
46+
<div className="space-y-3">
47+
<div>
48+
<p className="text-sm font-medium text-foreground mb-2">{'Download Steps:'}</p>
49+
<ol className="list-decimal list-inside space-y-2 text-sm text-muted-foreground">
50+
<li>
51+
{'Visit '}
52+
<a
53+
href={releaseUrl}
54+
target="_blank"
55+
rel="noopener noreferrer"
56+
className="inline-flex items-center space-x-1 text-primary hover:underline"
57+
>
58+
<span>{'GitHub Release page'}</span>
59+
<ExternalLinkIcon className="w-3 h-3" />
60+
</a>
61+
</li>
62+
<li>
63+
{'Find and download '}
64+
<code className="px-1.5 py-0.5 bg-muted rounded text-xs font-mono text-foreground">
65+
{installerFilename}
66+
</code>
67+
{' from the Assets section'}
68+
</li>
69+
<li>{'After downloading, double-click the installer to complete the installation'}</li>
70+
</ol>
71+
</div>
72+
73+
<div className="pt-2">
74+
<a
75+
href={releaseUrl}
76+
target="_blank"
77+
rel="noopener noreferrer"
78+
className="inline-flex items-center space-x-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
79+
>
80+
<span>{'Go to Release Page'}</span>
81+
<ExternalLinkIcon className="w-4 h-4" />
82+
</a>
83+
</div>
84+
</div>
5285
</div>
53-
<a
54-
href={finalDownloadUrl}
55-
target="_blank"
56-
rel="noopener noreferrer"
57-
className="inline-flex items-center space-x-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors">
58-
<DownloadIcon className="w-4 h-4" />
59-
<span>{'Download'}</span>
60-
</a>
6186
</div>
6287

63-
<CodeBlock
64-
code={[
65-
"# Download RustFS for Windows",
66-
"# Option 1: Download from GitHub Releases",
67-
`# Visit: ${fallbackUrl}`,
68-
"",
69-
"# Option 2: Using PowerShell",
70-
downloadUrl
71-
? `Invoke-WebRequest -Uri "${downloadUrl}" -OutFile "${getFilenameFromUrl(downloadUrl)}"`
72-
: "# Download from GitHub Releases page",
73-
"",
74-
"# Run RustFS",
75-
".\\rustfs.exe --version",
76-
]}
77-
title={'Installation Commands'}
78-
/>
88+
<div className="p-4 bg-muted/50 rounded-lg border border-border">
89+
<p className="text-sm text-muted-foreground">
90+
<strong className="text-foreground">{'Tip: '}</strong>
91+
{'The Launcher installer includes the RustFS binary. After installation, you can use it immediately.'}
92+
</p>
93+
</div>
94+
</div>
95+
</div>
96+
97+
{/* Direct Download */}
98+
<div className="space-y-6">
99+
<h3 className="text-lg font-semibold text-foreground">{'Download from Download Site'}</h3>
100+
101+
<div className="space-y-4">
102+
<div className="p-6 bg-card rounded-lg border border-border">
103+
<div className="space-y-4">
104+
<div>
105+
<h4 className="font-medium text-foreground mb-2">{'Latest Version'}</h4>
106+
<p className="text-sm text-muted-foreground mb-4">
107+
{launcherRelease ? `Current latest version: ${version}` : 'Fetching latest version information...'}
108+
</p>
109+
</div>
110+
111+
<div className="space-y-3">
112+
<div>
113+
<p className="text-sm font-medium text-foreground mb-2">{'Download Steps:'}</p>
114+
<ol className="list-decimal list-inside space-y-2 text-sm text-muted-foreground">
115+
<li>{'Click the download button or link below to download the installer'}</li>
116+
<li>{'After downloading, double-click the installer to complete the installation'}</li>
117+
</ol>
118+
</div>
119+
120+
<div className="pt-2">
121+
<a
122+
href={directDownloadUrl}
123+
target="_blank"
124+
rel="noopener noreferrer"
125+
className="inline-flex items-center space-x-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
126+
>
127+
<DownloadIcon className="w-4 h-4" />
128+
<span>{'Download Windows x86_64 Installer'}</span>
129+
</a>
130+
</div>
131+
132+
<div className="pt-2">
133+
<p className="text-xs text-muted-foreground">
134+
{'Download link: '}
135+
<a
136+
href={directDownloadUrl}
137+
target="_blank"
138+
rel="noopener noreferrer"
139+
className="ml-1 text-primary hover:underline break-all"
140+
>
141+
{directDownloadUrl}
142+
</a>
143+
</p>
144+
</div>
145+
</div>
146+
</div>
147+
</div>
79148

80-
<div className="space-y-2">
81-
<Note type="tip">
82-
{'Default credentials: rustfsadmin / rustfsadmin'}
83-
</Note>
84-
<Note type="info">
85-
{'Windows 10/11 x64 compatible'}
86-
</Note>
149+
<div className="p-4 bg-muted/50 rounded-lg border border-border">
150+
<p className="text-sm text-muted-foreground">
151+
<strong className="text-foreground">{'Tip: '}</strong>
152+
{'The Launcher installer includes the RustFS binary. After installation, you can use it immediately.'}
153+
</p>
87154
</div>
88155
</div>
89156
</div>

app/download/page.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import DownloadPageClient from './components/download-page-client';
2-
import { getLatestRelease } from '@/lib/github';
2+
import { getLatestRelease, getLatestLauncherRelease } from '@/lib/github';
33
import type { Metadata } from 'next';
44

55
export const metadata: Metadata = {
@@ -14,6 +14,9 @@ export const metadata: Metadata = {
1414
};
1515

1616
export default async function DownloadPage() {
17-
const release = await getLatestRelease();
18-
return <DownloadPageClient release={release} />;
17+
const [release, launcherRelease] = await Promise.all([
18+
getLatestRelease(),
19+
getLatestLauncherRelease()
20+
]);
21+
return <DownloadPageClient release={release} launcherRelease={launcherRelease} />;
1922
}

lib/github.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,70 @@ export async function getLatestVersion(): Promise<string> {
199199
return fallback;
200200
}
201201

202+
/**
203+
* Get the latest launcher release information
204+
* @returns Promise<GitHubRelease | null>
205+
*/
206+
export async function getLatestLauncherRelease(): Promise<GitHubRelease | null> {
207+
// Try to get the latest official release first
208+
try {
209+
const response = await fetch(
210+
'https://api.github.com/repos/rustfs/launcher/releases/latest',
211+
{
212+
headers: {
213+
'Accept': 'application/vnd.github.v3+json',
214+
'User-Agent': 'RustFS-Website'
215+
},
216+
// Cache for 1 hour
217+
next: { revalidate: 3600 }
218+
}
219+
)
220+
221+
if (response.ok) {
222+
const release = await response.json()
223+
return release
224+
}
225+
} catch (error) {
226+
console.warn('Failed to fetch latest launcher release:', error)
227+
}
228+
229+
// If official release doesn't exist (404), get the latest version with assets
230+
try {
231+
const response = await fetch(
232+
'https://api.github.com/repos/rustfs/launcher/releases?per_page=10',
233+
{
234+
headers: {
235+
'Accept': 'application/vnd.github.v3+json',
236+
'User-Agent': 'RustFS-Website'
237+
},
238+
// Cache for 1 hour
239+
next: { revalidate: 3600 }
240+
}
241+
)
242+
243+
if (response.ok) {
244+
const releases = await response.json()
245+
246+
// Prioritize latest non-draft version with assets
247+
const releaseWithAssets = releases.find((release: GitHubRelease) =>
248+
!release.draft && release.assets && release.assets.length > 0
249+
)
250+
251+
if (releaseWithAssets) {
252+
return releaseWithAssets
253+
}
254+
255+
// If no version with assets found, return latest non-draft version
256+
const latestNonDraft = releases.find((release: GitHubRelease) => !release.draft)
257+
return latestNonDraft || null
258+
}
259+
} catch (error) {
260+
console.error('Failed to fetch launcher releases:', error)
261+
}
262+
263+
return null
264+
}
265+
202266
/**
203267
* Get download link for a version
204268
* @param release GitHub release information

0 commit comments

Comments
 (0)