Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/oxc/src/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const tools = computed(() => {
</div>
<template v-if="tool.info.installed">
<a
v-if="tool.info.latest !== undefined"
:href="
tool.info.latest ? undefined : `https://npmx.dev/package/${tool.id}/v/latest`
"
Expand Down
67 changes: 67 additions & 0 deletions packages/oxc/src/node/__tests__/overview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fetchLatestVersions, overview } from '../rpc/functions/overview'

vi.mock('@vitejs/devtools-kit/node', () => ({ isVitePlusInstalled: () => true }))
vi.mock('../utils/vite-plus', () => ({
getVitePlusVersions: async () => ({ vitePlus: '0.2.4', oxlint: '1.72.0', oxfmt: '0.57.0' }),
}))
vi.mock('../rpc/functions/oxlint-setup', () => ({ needsOxlintMigration: () => false }))

const fetchMock = vi.fn<typeof fetch>()

function runOverview() {
const setup = overview.setup!({ cwd: '/project' } as any)
return setup.handler!()
}

beforeEach(() => {
vi.stubGlobal('fetch', fetchMock)
})

afterEach(() => {
fetchMock.mockReset()
vi.unstubAllGlobals()
})

describe('fetchLatestVersions', () => {
it('returns the latest published versions', async () => {
fetchMock.mockResolvedValue(Response.json([{ version: '1.72.0' }, { version: '0.58.0' }]))

await expect(fetchLatestVersions()).resolves.toEqual({ oxlint: '1.72.0', oxfmt: '0.58.0' })
})

it('returns unknown versions when the request fails', async () => {
fetchMock.mockRejectedValue(new TypeError('fetch failed'))

await expect(fetchLatestVersions()).resolves.toEqual({ oxlint: undefined, oxfmt: undefined })
})

it('returns unknown versions on a non-OK response', async () => {
fetchMock.mockResolvedValue(new Response('Bad Gateway', { status: 502 }))

await expect(fetchLatestVersions()).resolves.toEqual({ oxlint: undefined, oxfmt: undefined })
})
})

describe('overview', () => {
it('compares local versions with the latest versions', async () => {
fetchMock.mockResolvedValue(Response.json([{ version: '1.72.0' }, { version: '0.58.0' }]))

const result = await runOverview()

expect(result.oxlint).toMatchObject({ installed: true, version: '1.72.0', latest: true })
expect(result.oxfmt).toMatchObject({ installed: true, version: '0.57.0', latest: false })
})

it('still reports local versions when the latest versions are unreachable', async () => {
fetchMock.mockRejectedValue(new TypeError('fetch failed'))

const result = await runOverview()

expect(result).toMatchObject({
oxlint: { installed: true, version: '1.72.0', latest: undefined },
oxfmt: { installed: true, version: '0.57.0', latest: undefined },
vitePlus: '0.2.4',
})
})
})
48 changes: 41 additions & 7 deletions packages/oxc/src/node/rpc/functions/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,45 @@ import { needsOxlintMigration } from './oxlint-setup'
type Package = {
installed: boolean
version: string | undefined
latest: boolean
/** `undefined` when the latest version could not be determined. */
latest: boolean | undefined
npmxLink: string | undefined
}

type LatestVersions = {
oxlint: string | undefined
oxfmt: string | undefined
}

const LATEST_VERSIONS_URL = 'https://npm.antfu.dev/oxlint+oxfmt'
const LATEST_VERSIONS_TIMEOUT = 5000

/**
* Best-effort lookup of the latest published Oxlint / Oxfmt versions.
* Resolves to `undefined` versions when the registry is unreachable so the
* overview can still render local toolchain info.
*/
export async function fetchLatestVersions(): Promise<LatestVersions> {
try {
const res = await fetch(LATEST_VERSIONS_URL, {
signal: AbortSignal.timeout(LATEST_VERSIONS_TIMEOUT),
})
if (!res.ok) return { oxlint: undefined, oxfmt: undefined }
const data = await res.json()
const [oxlintData, oxfmtData] = Array.isArray(data) ? data : []
return {
oxlint: typeof oxlintData?.version === 'string' ? oxlintData.version : undefined,
oxfmt: typeof oxfmtData?.version === 'string' ? oxfmtData.version : undefined,
}
} catch {
return { oxlint: undefined, oxfmt: undefined }
}
}

function isLatest(version: string | undefined, latestVersion: string | undefined) {
return version && latestVersion ? version === latestVersion : undefined
}

export const overview = defineOxcRpc({
name: 'devtools-oxc:overview',
type: 'query',
Expand All @@ -31,22 +66,21 @@ export const overview = defineOxcRpc({
npmxLink: undefined,
}

const res = await fetch('https://npm.antfu.dev/oxlint+oxfmt')
const [oxlintData, oxfmtData] = await res.json()
const latestVersions = await fetchLatestVersions()
const vitePlus = isVitePlusInstalled(ctx.cwd)
const vitePlusVersions = vitePlus ? await getVitePlusVersions(ctx.cwd) : undefined

if (vitePlusVersions) {
oxlint = {
installed: true,
version: vitePlusVersions.oxlint,
latest: vitePlusVersions.oxlint === oxlintData.version,
latest: isLatest(vitePlusVersions.oxlint, latestVersions.oxlint),
npmxLink: `https://npmx.dev/package/oxlint/v/${vitePlusVersions.oxlint}`,
}
oxfmt = {
installed: true,
version: vitePlusVersions.oxfmt,
latest: vitePlusVersions.oxfmt === oxfmtData.version,
latest: isLatest(vitePlusVersions.oxfmt, latestVersions.oxfmt),
npmxLink: `https://npmx.dev/package/oxfmt/v/${vitePlusVersions.oxfmt}`,
}
} else {
Expand All @@ -57,7 +91,7 @@ export const overview = defineOxcRpc({
}
oxlint.installed = true
oxlint.version = stdout.split(' ')[1]?.trim().replaceAll('\n', '') ?? undefined
oxlint.latest = oxlint.version === oxlintData.version
oxlint.latest = isLatest(oxlint.version, latestVersions.oxlint)
oxlint.npmxLink = `https://npmx.dev/package/oxlint/v/${oxlint.version}`
} catch {
oxlint.installed = false
Expand All @@ -69,7 +103,7 @@ export const overview = defineOxcRpc({
}
oxfmt.installed = true
oxfmt.version = stdout.split(' ')[1]?.trim().replaceAll('\n', '') ?? undefined
oxfmt.latest = oxfmt.version === oxfmtData.version
oxfmt.latest = isLatest(oxfmt.version, latestVersions.oxfmt)
oxfmt.npmxLink = `https://npmx.dev/package/oxfmt/v/${oxfmt.version}`
} catch {
oxfmt.installed = false
Expand Down