Skip to content
Merged
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
50 changes: 27 additions & 23 deletions packages/oxc/src/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const { state: overview, isLoading } = useAsyncState(
latest: true,
npmxLink: undefined,
},
vitePlus: false,
vitePlus: undefined,
},
)

Expand Down Expand Up @@ -77,35 +77,39 @@ const tools = computed(() => {

<template>
<VisualLoading v-if="isLoading" text="Connecting..." />
<div v-else class="h-full p4 flex flex-col gap-4 items-center justify-center relative">
<BannerOxcDevTools class="mx-auto" />
<div v-else class="h-full p4 flex flex-col gap-5 items-center justify-center relative">
<div class="w-fit mx-auto flex flex-col items-center gap-3">
<BannerOxcDevTools />
<a
v-if="overview.vitePlus"
href="https://viteplus.dev/"
target="_blank"
rel="noopener noreferrer"
aria-label="Powered by Vite+"
>
<DisplayBadge
:color="false"
size="lg"
class="border border-#6254FE/30 bg-#6254FE/10 dark:border-#8B7FFF/40 dark:bg-#6254FE/20 px-2 py-1 flex items-center gap-1 text-xs font-normal"
>
<span class="color-#6254FE dark:color-#A89FFF font-semibold">Via</span>
<img :src="vitePlusDarkLogo" alt="" class="h-3 w-20 dark:hidden" />
<img :src="vitePlusLightLogo" alt="" class="hidden h-3 w-20 dark:block" />
<span class="color-#6254FE dark:color-#A89FFF font-semibold"
>v{{ overview.vitePlus }}</span
>
</DisplayBadge>
</a>
</div>

<div class="flex flex-col lg:flex-row gap-4 mx-auto mt-4">
<div class="flex flex-col lg:flex-row gap-4 mx-auto">
<div
v-for="tool in tools"
:key="tool.id"
class="border border-base rounded p2 flex-1 min-w-max"
>
<div class="p4 flex flex-col gap-4 h-full">
<div class="flex items-center gap-3 text-2xl font-semibold">
<span>{{ tool.name }}</span>
<a
v-if="overview.vitePlus"
href="https://viteplus.dev/"
target="_blank"
rel="noopener noreferrer"
aria-label="Powered by Vite+"
>
<DisplayBadge
:color="false"
class="border border-#6254FE/30 bg-#6254FE/10 dark:border-#8B7FFF/40 dark:bg-#6254FE/20 flex items-center gap-1 font-normal"
>
<span class="color-#6254FE dark:color-#A89FFF">Powered by</span>
<img :src="vitePlusDarkLogo" alt="" class="h-2.5 w-17 dark:hidden" />
<img :src="vitePlusLightLogo" alt="" class="hidden h-2.5 w-17 dark:block" />
</DisplayBadge>
</a>
</div>
<div class="text-2xl font-semibold">{{ tool.name }}</div>

<div class="grid grid-cols-[max-content_160px_2fr] gap-2 items-center">
<div class="i-ph-tag-duotone op-fade" />
Expand Down
52 changes: 34 additions & 18 deletions packages/oxc/src/node/__tests__/vite-plus.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import { describe, expect, it } from 'vitest'
import { parseVitePlusVersions } from '../utils/vite-plus'
import { x } from 'tinyexec'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getVitePlusVersions } from '../utils/vite-plus'

describe('parseVitePlusVersions', () => {
it('reads the bundled Oxc tool versions from vp -V output', () => {
const output = `VITE+ - The Unified Toolchain for the Web\r
\r
Tools:\r
vite v8.1.3\r
oxfmt v0.57.0\r
oxlint v1.72.0\r
tsdown v0.22.3\r
\r
Environment:\r
oxfmt v9.0.0\r
oxlint v9.0.0\r
`
vi.mock('tinyexec', () => ({ x: vi.fn<typeof x>() }))

expect(parseVitePlusVersions(output)).toEqual({
oxfmt: '0.57.0',
describe('getVitePlusVersions', () => {
beforeEach(() => {
vi.mocked(x).mockResolvedValue({
exitCode: 0,
stdout: `vp v0.2.4
Local vite-plus:
vite-plus v0.2.4
Tools:
vite v8.1.3
oxfmt v0.57.0
oxlint v1.72.0
Environment:
oxfmt v9.0.0
oxlint v9.0.0
Node.js v24.18.0
`,
stderr: '',
})
})

it('loads all Vite+ versions with one command', async () => {
await expect(getVitePlusVersions('/project')).resolves.toEqual({
vitePlus: '0.2.4',
oxlint: '1.72.0',
oxfmt: '0.57.0',
})

expect(x).toHaveBeenCalledOnce()
expect(x).toHaveBeenCalledWith('vp', ['-V'], expect.anything())
})
})
2 changes: 1 addition & 1 deletion packages/oxc/src/node/rpc/functions/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const overview = defineOxcRpc({
return {
oxlint,
oxfmt,
vitePlus,
vitePlus: vitePlusVersions?.vitePlus,
}
},
}
Expand Down
26 changes: 17 additions & 9 deletions packages/oxc/src/node/utils/vite-plus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,31 @@ export function isVitePlusInstalled(cwd: string) {
}

export function parseVitePlusVersions(output: string) {
const vitePlus = /^\s*vite-plus\s+v(\S+)\s*$/m.exec(output)?.[1]
const tools = output
.split(/^[ \t]*Tools:[ \t\r]*$/m)[1]
?.split(/^[ \t]*Environment:[ \t\r]*$/m)[0]
if (!tools) return undefined
if (!vitePlus || !tools) return undefined

const versions: Partial<Record<'oxfmt' | 'oxlint', string>> = {}
for (const [, tool, version] of tools.matchAll(/^\s*(oxfmt|oxlint)\s+v(\S+)\s*$/gm)) {
versions[tool as 'oxfmt' | 'oxlint'] = version
}
return versions.oxfmt && versions.oxlint
? { oxfmt: versions.oxfmt, oxlint: versions.oxlint }
: undefined
const versions = new Map(
[...tools.matchAll(/^\s*(oxfmt|oxlint)\s+v(\S+)\s*$/gm)].map(([, tool, version]) => [
tool,
version,
]),
)
const oxfmt = versions.get('oxfmt')
const oxlint = versions.get('oxlint')
return oxfmt && oxlint ? { vitePlus, oxfmt, oxlint } : undefined
}

export async function getVitePlusVersions(cwd: string) {
try {
const result = await x('vp', ['-V'], { nodeOptions: { cwd } })
const result = await x('vp', ['-V'], {
nodeOptions: {
cwd,
env: { NO_COLOR: '1', FORCE_COLOR: undefined },
},
})
return result.exitCode === 0 ? parseVitePlusVersions(result.stdout) : undefined
} catch {
return undefined
Expand Down
Loading