Skip to content

Commit 136c9e2

Browse files
committed
fix(releases): skip empty releases when finding latest release
Filters out releases with no assets to prevent downloading from empty placeholder releases.
1 parent d45b7cb commit 136c9e2

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

src/releases/github.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,11 @@ export async function getLatestRelease(
269269
return false
270270
}
271271

272+
// Skip releases with no assets (empty releases).
273+
if (!assets || assets.length === 0) {
274+
return false
275+
}
276+
272277
// If asset pattern provided, check if release has matching asset.
273278
if (isMatch) {
274279
const hasMatchingAsset = assets.some((a: { name: string }) =>

test/unit/releases-github.test.mts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,105 @@ describe('releases/github', () => {
441441
// Should return the newest release that has the matching asset.
442442
expect(tag).toBe('node-smol-20260112-newer')
443443
})
444+
445+
it('should skip releases with no assets', async () => {
446+
// Mock releases where the newest has no assets (empty placeholder release).
447+
const releasesWithEmpty = [
448+
{
449+
assets: [], // Empty release (no binaries built yet)
450+
published_at: '2026-01-13T03:06:00Z', // Newest but empty
451+
tag_name: 'node-smol-20260113-121d029',
452+
},
453+
{
454+
assets: [{ name: 'node-darwin-arm64' }, { name: 'node-linux-x64' }],
455+
published_at: '2025-12-26T00:17:00Z', // Older but has assets
456+
tag_name: 'node-smol-20251226-2126245',
457+
},
458+
]
459+
460+
vi.mocked(httpRequest).mockResolvedValue(
461+
createMockHttpResponse(
462+
Buffer.from(JSON.stringify(releasesWithEmpty)),
463+
true,
464+
200,
465+
),
466+
)
467+
468+
const tag = await getLatestRelease('node-smol-', SOCKET_BTM_REPO, {
469+
quiet: true,
470+
})
471+
472+
// Should skip the empty release and return the older release with assets.
473+
expect(tag).toBe('node-smol-20251226-2126245')
474+
})
475+
476+
it('should return null when all matching releases have no assets', async () => {
477+
// All releases matching prefix are empty.
478+
const allEmpty = [
479+
{
480+
assets: [],
481+
published_at: '2026-01-13T03:06:00Z',
482+
tag_name: 'binject-20260113-121d029',
483+
},
484+
{
485+
assets: [],
486+
published_at: '2026-01-13T03:05:00Z',
487+
tag_name: 'binject-20260113-abc1234',
488+
},
489+
]
490+
491+
vi.mocked(httpRequest).mockResolvedValue(
492+
createMockHttpResponse(
493+
Buffer.from(JSON.stringify(allEmpty)),
494+
true,
495+
200,
496+
),
497+
)
498+
499+
const tag = await getLatestRelease('binject-', SOCKET_BTM_REPO, {
500+
quiet: true,
501+
})
502+
503+
// Should return null since all matching releases are empty.
504+
expect(tag).toBeNull()
505+
})
506+
507+
it('should skip empty releases when asset pattern is provided', async () => {
508+
// Mix of empty releases and releases with assets.
509+
const mixedReleases = [
510+
{
511+
assets: [], // Empty
512+
published_at: '2026-01-13T03:00:00Z',
513+
tag_name: 'models-20260113-empty',
514+
},
515+
{
516+
assets: [{ name: 'models-embeddings.bin' }], // Wrong asset
517+
published_at: '2026-01-12T12:00:00Z',
518+
tag_name: 'models-20260112-wrong',
519+
},
520+
{
521+
assets: [{ name: 'models-data.tar.gz' }], // Correct asset
522+
published_at: '2026-01-11T12:00:00Z',
523+
tag_name: 'models-20260111-correct',
524+
},
525+
]
526+
527+
vi.mocked(httpRequest).mockResolvedValue(
528+
createMockHttpResponse(
529+
Buffer.from(JSON.stringify(mixedReleases)),
530+
true,
531+
200,
532+
),
533+
)
534+
535+
const tag = await getLatestRelease('models-', SOCKET_BTM_REPO, {
536+
assetPattern: '*.tar.gz',
537+
quiet: true,
538+
})
539+
540+
// Should skip empty release and release without matching asset.
541+
expect(tag).toBe('models-20260111-correct')
542+
})
444543
})
445544

446545
describe('getReleaseAssetUrl', () => {

0 commit comments

Comments
 (0)