@@ -2,12 +2,14 @@ import { RELEASE_METADATA_UPSTREAM_URL, formatStableReleaseVersion } from './rel
22
33export interface GithubRepoMeta {
44 starsLabel : string ;
5+ contributorsCount : number ;
56 versionLabel : string ;
67}
78
89const REPO_API = 'https://api.github.com/repos/nexu-io/open-design' ;
910const FALLBACK_META : GithubRepoMeta = {
10- starsLabel : '40K+' ,
11+ starsLabel : '83.3K+' ,
12+ contributorsCount : 387 ,
1113 // Build-time fallback when the GitHub releases API is unavailable / rate
1214 // limited. Keep in step with the latest published release.
1315 versionLabel : 'v0.9.0' ,
@@ -18,7 +20,23 @@ let repoMetaPromise: Promise<GithubRepoMeta> | null = null;
1820function formatStars ( count : unknown ) : string | null {
1921 if ( typeof count !== 'number' || ! Number . isFinite ( count ) || count <= 0 ) return null ;
2022 if ( count < 1000 ) return String ( count ) ;
21- return `${ ( count / 1000 ) . toFixed ( 1 ) . replace ( / \. 0 $ / , '' ) } K` ;
23+ return `${ ( count / 1000 ) . toFixed ( 1 ) . replace ( / \. 0 $ / , '' ) } K+` ;
24+ }
25+
26+ async function fetchContributorCount ( ) : Promise < number > {
27+ const response = await fetch ( `${ REPO_API } /contributors?per_page=1` , {
28+ headers : { Accept : 'application/vnd.github+json' } ,
29+ } ) ;
30+ if ( ! response . ok ) {
31+ throw new Error ( `Request returned ${ response . status } : ${ response . url } ` ) ;
32+ }
33+
34+ const link = response . headers . get ( 'link' ) ?? '' ;
35+ const lastPage = link . match ( / [ ? & ] p a g e = ( \d + ) > ; \s * r e l = " l a s t " / ) ;
36+ if ( lastPage ?. [ 1 ] ) return Number . parseInt ( lastPage [ 1 ] , 10 ) ;
37+
38+ const contributors = ( await response . json ( ) ) as unknown ;
39+ return Array . isArray ( contributors ) ? contributors . length : 0 ;
2240}
2341
2442// Parse a display version (e.g. "v0.9.0") from a GitHub /releases/latest object.
@@ -51,18 +69,24 @@ async function fetchJson(url: string, headers?: Record<string, string>): Promise
5169
5270export function getGithubRepoMeta ( ) : Promise < GithubRepoMeta > {
5371 repoMetaPromise ??= ( async ( ) => {
54- const [ repoResult , releaseMetadataResult ] = await Promise . allSettled ( [
72+ const [ repoResult , contributorsResult , releaseMetadataResult ] = await Promise . allSettled ( [
5573 fetchJson ( REPO_API , { Accept : 'application/vnd.github+json' } ) ,
74+ fetchContributorCount ( ) ,
5675 fetchJson ( RELEASE_METADATA_UPSTREAM_URL , { Accept : 'application/json' } ) ,
5776 ] ) ;
5877
5978 const repo = repoResult . status === 'fulfilled' ? repoResult . value : null ;
79+ const contributorsCount =
80+ contributorsResult . status === 'fulfilled' && contributorsResult . value > 0
81+ ? contributorsResult . value
82+ : null ;
6083 const releaseMetadata = releaseMetadataResult . status === 'fulfilled' ? releaseMetadataResult . value : null ;
6184 const starsLabel = formatStars ( ( repo as { stargazers_count ?: unknown } | null ) ?. stargazers_count ) ;
6285 const versionLabel = formatStableReleaseVersion ( releaseMetadata ) ;
6386
6487 return {
6588 starsLabel : starsLabel ?? FALLBACK_META . starsLabel ,
89+ contributorsCount : contributorsCount ?? FALLBACK_META . contributorsCount ,
6690 versionLabel : versionLabel ?? FALLBACK_META . versionLabel ,
6791 } ;
6892 } ) ( ) ;
@@ -131,6 +155,66 @@ const EMPTY_MATRIX: ReleaseMatrix = {
131155 linux : null ,
132156} ;
133157
158+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
159+ return Boolean ( value ) && typeof value === 'object' ;
160+ }
161+
162+ function stableArtifact ( value : unknown ) : ReleaseAsset | null {
163+ if ( ! isRecord ( value ) || typeof value . name !== 'string' || typeof value . url !== 'string' ) {
164+ return null ;
165+ }
166+
167+ return {
168+ name : value . name ,
169+ url : value . url ,
170+ size : typeof value . size === 'number' && Number . isFinite ( value . size ) ? value . size : 0 ,
171+ sha256Url : typeof value . sha256Url === 'string' ? value . sha256Url : null ,
172+ } ;
173+ }
174+
175+ function stablePlatformArtifact (
176+ metadata : unknown ,
177+ platformKey : string ,
178+ artifactKey : string ,
179+ ) : ReleaseAsset | null {
180+ if ( ! isRecord ( metadata ) || ! isRecord ( metadata . platforms ) ) return null ;
181+ const platform = metadata . platforms [ platformKey ] ;
182+ if ( ! isRecord ( platform ) || ! isRecord ( platform . artifacts ) ) return null ;
183+ return stableArtifact ( platform . artifacts [ artifactKey ] ) ;
184+ }
185+
186+ /**
187+ * Parse the canonical stable-release manifest served from releases.open-design.ai.
188+ * Unlike the unauthenticated GitHub API, this endpoint is not constrained by the
189+ * shared 60-request rate limit, so high-intent download links stay direct even
190+ * when GitHub metadata cannot be resolved during a build.
191+ */
192+ export function buildMatrixFromStableMetadata ( metadata : unknown ) : ReleaseMatrix {
193+ return {
194+ macArm64Dmg : stablePlatformArtifact ( metadata , 'mac' , 'dmg' ) ,
195+ macArm64Zip : stablePlatformArtifact ( metadata , 'mac' , 'zip' ) ,
196+ macX64Dmg : stablePlatformArtifact ( metadata , 'macIntel' , 'dmg' ) ,
197+ macX64Zip : stablePlatformArtifact ( metadata , 'macIntel' , 'zip' ) ,
198+ winSetup : stablePlatformArtifact ( metadata , 'win' , 'installer' ) ,
199+ winPortable : stablePlatformArtifact ( metadata , 'win' , 'portableZip' ) ,
200+ linux :
201+ stablePlatformArtifact ( metadata , 'linux' , 'appImage' ) ??
202+ stablePlatformArtifact ( metadata , 'linux' , 'appimage' ) ,
203+ } ;
204+ }
205+
206+ function mergeMatrices ( preferred : ReleaseMatrix , fallback : ReleaseMatrix ) : ReleaseMatrix {
207+ return {
208+ macArm64Dmg : preferred . macArm64Dmg ?? fallback . macArm64Dmg ,
209+ macArm64Zip : preferred . macArm64Zip ?? fallback . macArm64Zip ,
210+ macX64Dmg : preferred . macX64Dmg ?? fallback . macX64Dmg ,
211+ macX64Zip : preferred . macX64Zip ?? fallback . macX64Zip ,
212+ winSetup : preferred . winSetup ?? fallback . winSetup ,
213+ winPortable : preferred . winPortable ?? fallback . winPortable ,
214+ linux : preferred . linux ?? fallback . linux ,
215+ } ;
216+ }
217+
134218function cleanVersion ( versionLabel : string ) : string {
135219 return versionLabel . replace ( / ^ v / , '' ) ;
136220}
@@ -172,12 +256,13 @@ let latestReleasePromise: Promise<LatestRelease> | null = null;
172256
173257export function getLatestRelease ( ) : Promise < LatestRelease > {
174258 latestReleasePromise ??= ( async ( ) => {
175- let release : unknown = null ;
176- try {
177- release = await fetchJson ( `${ REPO_API } /releases/latest` ) ;
178- } catch {
179- release = null ;
180- }
259+ const [ releaseResult , stableMetadataResult ] = await Promise . allSettled ( [
260+ fetchJson ( `${ REPO_API } /releases/latest` , { Accept : 'application/vnd.github+json' } ) ,
261+ fetchJson ( RELEASE_METADATA_UPSTREAM_URL , { Accept : 'application/json' } ) ,
262+ ] ) ;
263+ const release = releaseResult . status === 'fulfilled' ? releaseResult . value : null ;
264+ const stableMetadata =
265+ stableMetadataResult . status === 'fulfilled' ? stableMetadataResult . value : null ;
181266
182267 const rec = ( release && typeof release === 'object' ? release : { } ) as {
183268 tag_name ?: unknown ;
@@ -186,17 +271,39 @@ export function getLatestRelease(): Promise<LatestRelease> {
186271 assets ?: unknown ;
187272 } ;
188273
189- const versionLabel = formatVersion ( release ) ?? FALLBACK_META . versionLabel ;
274+ const stableRec = isRecord ( stableMetadata ) ? stableMetadata : { } ;
275+ const versionLabel =
276+ formatVersion ( release ) ??
277+ formatStableReleaseVersion ( stableMetadata ) ??
278+ FALLBACK_META . versionLabel ;
190279 const rawAssets = Array . isArray ( rec . assets ) ? ( rec . assets as RawAsset [ ] ) : [ ] ;
191- const matrix = release ? buildMatrix ( rawAssets ) : EMPTY_MATRIX ;
192- const resolved = Boolean ( release ) && Object . values ( matrix ) . some ( ( a ) => a !== null ) ;
280+ const githubMatrix = release ? buildMatrix ( rawAssets ) : EMPTY_MATRIX ;
281+ const stableMatrix = buildMatrixFromStableMetadata ( stableMetadata ) ;
282+ const matrix = mergeMatrices ( stableMatrix , githubMatrix ) ;
283+ const resolved = Object . values ( matrix ) . some ( ( asset ) => asset !== null ) ;
284+ const tagName =
285+ typeof rec . tag_name === 'string'
286+ ? rec . tag_name
287+ : typeof stableRec . versionTag === 'string'
288+ ? stableRec . versionTag
289+ : null ;
193290
194291 return {
195292 version : cleanVersion ( versionLabel ) ,
196293 versionLabel,
197- tagName : typeof rec . tag_name === 'string' ? rec . tag_name : null ,
198- publishedAt : typeof rec . published_at === 'string' ? rec . published_at : null ,
199- releaseUrl : typeof rec . html_url === 'string' ? rec . html_url : REPO_RELEASES ,
294+ tagName,
295+ publishedAt :
296+ typeof rec . published_at === 'string'
297+ ? rec . published_at
298+ : typeof stableRec . generatedAt === 'string'
299+ ? stableRec . generatedAt
300+ : null ,
301+ releaseUrl :
302+ typeof rec . html_url === 'string'
303+ ? rec . html_url
304+ : tagName
305+ ? `${ REPO_RELEASES } /tag/${ encodeURIComponent ( tagName ) } `
306+ : REPO_RELEASES ,
200307 matrix,
201308 resolved,
202309 } ;
0 commit comments