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
16 changes: 16 additions & 0 deletions docs/errors/OXDT0005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
outline: deep
---
# OXDT0005: Oxlint Setup Failed

## Message
> Failed to set up Oxlint: `{reason}`

## Cause
Vite DevTools could not install or initialize Oxlint in the project root.

## Fix
Check the project package manager and configuration, then try again.

## Source
- [`packages/oxc/src/node/rpc/functions/oxlint-migrate.ts`](https://github.com/vitejs/devtools/blob/main/packages/oxc/src/node/rpc/functions/oxlint-migrate.ts) — runs the installation, initialization, and migration commands.
1 change: 1 addition & 0 deletions docs/errors/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ Emitted by `@vitejs/devtools-oxc`.
| [OXDT0002](./OXDT0002) | error | Invalid Lint Result ID |
| [OXDT0003](./OXDT0003) | error | Failed to Delete Lint Result |
| [OXDT0004](./OXDT0004) | error | Oxlint Config Inspection Failed |
| [OXDT0005](./OXDT0005) | error | Oxlint Setup Failed |
1 change: 1 addition & 0 deletions packages/oxc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"cac": "catalog:deps",
"devframe": "catalog:deps",
"local-pkg": "catalog:deps",
"nypm": "catalog:deps",
"nostics": "catalog:deps",
"pathe": "catalog:deps",
"tinyexec": "catalog:deps"
Expand Down
197 changes: 127 additions & 70 deletions packages/oxc/src/app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
<script setup lang="ts">
import BannerOxcDevTools from '@vitejs/devtools-ui/components/Banner/BannerOxcDevTools.vue'
import { DEVTOOLS_TERMINALS_DOCK_ID } from '@vitejs/devtools-kit/constants'
import DisplayBadge from '@vitejs/devtools-ui/components/Display/DisplayBadge.vue'
import DisplayFileIcon from '@vitejs/devtools-ui/components/Display/DisplayFileIcon.vue'
import DisplayNumberBadge from '@vitejs/devtools-ui/components/Display/DisplayNumberBadge.vue'
import { useAsyncState } from '@vueuse/core'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { createOverview } from '../utils/overview'
import { useRpc } from '#imports'

const rpc = useRpc()
const vitePlusDarkLogo = '/__devtools-oxc/viteplus-dark.svg'
const vitePlusLightLogo = '/__devtools-oxc/viteplus-light.svg'

const { state: overview, isLoading } = useAsyncState(
() => rpc.value.call('devtools-oxc:overview'),
{
oxlint: {
installed: false,
version: undefined,
latest: true,
npmxLink: undefined,
},
oxfmt: {
installed: false,
version: undefined,
latest: true,
npmxLink: undefined,
},
vitePlus: undefined,
},
)
const {
state: overview,
isLoading,
execute: refreshOverview,
} = useAsyncState(() => rpc.value.call('devtools-oxc:overview'), createOverview())

const isMigrating = ref(false)
const migrationError = ref<string>()
const migrationSessionId = ref<string>()

async function migrateOxlint() {
await runOxlintSetup('devtools-oxc:migrate-eslint')
}

async function installOxlint() {
await runOxlintSetup('devtools-oxc:install-oxlint')
}

async function runOxlintSetup(
action: 'devtools-oxc:migrate-eslint' | 'devtools-oxc:install-oxlint',
) {
isMigrating.value = true
migrationError.value = undefined
migrationSessionId.value = undefined
try {
const { sessionId } = await rpc.value.call(action)
migrationSessionId.value = sessionId
await rpc.value.call('devtools-oxc:wait-for-setup')
await refreshOverview()
} catch (error) {
migrationError.value = error instanceof Error ? error.message : String(error)
} finally {
isMigrating.value = false
}
}

const { state: configFiles } = useAsyncState(
() => rpc.value.call('devtools-oxc:get-config-files'),
[],
)
async function viewMigrationInTerminal() {
if (!migrationSessionId.value) return
await rpc.value.call('hub:docks:activate', {
dockId: DEVTOOLS_TERMINALS_DOCK_ID,
params: { sessionId: migrationSessionId.value },
})
}

interface ToolView {
title: string
description: string
icon: string
to: string
}
Expand All @@ -46,29 +67,47 @@ const tools = computed(() => {
const oxlintViews: ToolView[] = [
...(oxlint.installed
? [
{ title: 'Lint Inspector', icon: 'i-ph-magnifying-glass-duotone', to: '/oxlint/lint' },
{ title: 'Config Inspector', icon: 'i-ph-gear-duotone', to: '/oxlint/config' },
{
title: 'Lint Inspector',
description: 'View diagnostics and rules',
icon: 'i-ph-magnifying-glass-duotone',
to: '/oxlint/lint',
},
{
title: 'Config Inspector',
description: 'Inspect configuration files',
icon: 'i-ph-gear-duotone',
to: '/oxlint/config',
},
]
: []),
{ title: 'Documents', icon: 'i-ph-book-open-duotone', to: '/oxlint/documents' },
{
title: 'Documents',
description: 'Guides and references',
icon: 'i-ph-book-open-duotone',
to: '/oxlint/documents',
},
]
const oxfmtViews: ToolView[] = [
{ title: 'Documents', icon: 'i-ph-book-open-duotone', to: '/oxfmt/documents' },
{
title: 'Documents',
description: 'Guides and references',
icon: 'i-ph-book-open-duotone',
to: '/oxfmt/documents',
},
]

return [
{
id: 'oxlint',
name: 'Oxlint',
info: oxlint,
configs: configFiles.value.filter(file => file.tool === 'oxlint'),
views: oxlintViews,
},
{
id: 'oxfmt',
name: 'Oxfmt',
info: overview.value.oxfmt,
configs: configFiles.value.filter(file => file.tool === 'oxfmt'),
views: oxfmtViews,
},
]
Expand Down Expand Up @@ -106,26 +145,23 @@ const tools = computed(() => {
<div
v-for="tool in tools"
:key="tool.id"
class="border border-base rounded p2 flex-1 min-w-max"
class="border border-base rounded-lg p-4 flex-1 min-w-120"
>
<div class="p4 flex flex-col gap-4 h-full">
<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" />
<span class="op-fade">Version</span>
<div class="flex items-center gap-2 w-full">
<div class="flex flex-col gap-6 h-full">
<div class="flex items-start justify-between gap-4">
<div>
<div class="text-2xl font-semibold">{{ tool.name }}</div>
<a
v-if="tool.info.installed"
:href="tool.info.npmxLink"
target="_blank"
class="hover:color-active font-mono"
class="block mt-1 hover:color-active op-fade text-sm font-mono"
>
v{{ tool.info.version }}
</a>
<span v-else class="op-fade">Not installed</span>
</div>
<template v-if="tool.info.installed">
<a
v-if="tool.info.installed"
:href="
tool.info.latest ? undefined : `https://npmx.dev/package/${tool.id}/v/latest`
"
Expand All @@ -138,45 +174,66 @@ const tools = computed(() => {
:class="tool.info.latest ? 'badge-color-green' : 'badge-color-amber'"
/>
</a>
</div>

<div class="i-ph-files-duotone op-fade" />
<span class="op-fade">Configs</span>
<VDropdown v-if="tool.configs.length" placement="bottom-start" :triggers="['hover']">
<DisplayNumberBadge
:number="tool.configs.length"
class="py1 rounded-full font-mono inline-block text-sm cursor-pointer hover:color-active"
/>

<template #popper>
<div class="p3 min-w-60 flex flex-col gap-2 font-mono text-sm">
<div
v-for="config in tool.configs"
:key="`${config.tool}:${config.path}`"
class="flex items-center gap-2"
>
<DisplayFileIcon class="flex-none" :filename="config.path" />
<span>{{ config.path }}</span>
</div>
</div>
</template>
</VDropdown>
<span v-else class="op-fade text-sm">Not found</span>
</template>
<DisplayBadge v-else text="Not installed" :color="false" class="badge-color-gray" />
</div>

<div class="flex gap-2">
<div class="grid grid-cols-[repeat(auto-fit,minmax(10rem,1fr))] gap-2">
<button
v-if="tool.id === 'oxlint' && overview.needsOxlintMigration"
type="button"
class="rounded-lg px-3 py-3 flex items-start gap-3 hover:bg-active transition-colors disabled:pointer-events-none disabled:op30!"
:disabled="isMigrating"
@click="migrateOxlint"
>
<div class="i-simple-icons:eslint text-2xl mt-0.5" />
<div class="text-left">
<div class="font-medium">
{{ isMigrating ? 'Migrating…' : 'Migrate from ESLint' }}
</div>
<div class="text-xs op-fade mt-0.5">Migrate from ESLint flat config</div>
</div>
</button>
<button
v-if="tool.id === 'oxlint' && !tool.info.installed && !overview.needsOxlintMigration"
type="button"
class="rounded-lg px-3 py-3 flex items-start gap-3 hover:bg-active transition-colors disabled:pointer-events-none disabled:op30!"
:disabled="isMigrating"
@click="installOxlint"
>
<div class="i-ph-rocket-launch-duotone text-2xl mt-0.5" />
<div class="text-left">
<div class="font-medium">{{ isMigrating ? 'Installing…' : 'Install Oxlint' }}</div>
<div class="text-xs op-fade mt-0.5">Install & set up Oxlint</div>
</div>
</button>
<NuxtLink
v-for="view in tool.views"
:key="view.title"
:to="view.to"
class="btn-action flex flex-col flex-1 min-w-max p4 !px4 whitespace-nowrap"
class="rounded-lg px-3 py-3 flex items-start gap-3 hover:bg-active transition-colors"
>
<div :class="[view.icon, 'text-2xl']" />
{{ view.title }}
<div :class="[view.icon, 'text-2xl mt-0.5']" />
<div class="text-left">
<div class="font-medium">{{ view.title }}</div>
<div class="text-xs op-fade mt-0.5">{{ view.description }}</div>
</div>
</NuxtLink>
</div>
<p v-if="tool.id === 'oxlint' && migrationError" class="text-sm text-red">
{{ migrationError }}
</p>
</div>
</div>
</div>
<button
v-if="isMigrating && migrationSessionId"
type="button"
class="btn-action flex items-center gap-2 px3 py2 text-sm"
@click="viewMigrationInTerminal"
>
<div class="i-ph-terminal-window-duotone" />
View in terminals
</button>
</div>
</template>
10 changes: 5 additions & 5 deletions packages/oxc/src/app/pages/oxfmt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ useSideNav(() => [
icon: 'i-ph-house-duotone',
to: '/',
},
{
title: 'Config Inspector',
icon: 'i-ph-sliders-duotone',
to: '/oxfmt/config',
},
// {
// title: 'Config Inspector',
// icon: 'i-ph-sliders-duotone',
// to: '/oxfmt/config',
// },
{
title: 'Documents',
icon: 'i-ph-book-open-duotone',
Expand Down
25 changes: 20 additions & 5 deletions packages/oxc/src/app/pages/oxfmt/documents.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
<script setup lang="ts">
import VisualLoading from '@vitejs/devtools-ui/components/Visual/VisualLoading.vue'
import { ref } from 'vue'

const isLoading = ref(true)
</script>

<template>
<iframe
src="https://oxc.rs/docs/guide/usage/formatter.html"
title="Oxfmt Documentation"
class="border-0 block w-full h-full"
/>
<div class="relative h-full">
<VisualLoading
v-if="isLoading"
class="absolute inset-0"
text="Loading Oxfmt documentation..."
/>
<iframe
src="https://oxc.rs/docs/guide/usage/formatter.html"
title="Oxfmt Documentation"
class="border-0 block w-full h-full"
@load="isLoading = false"
/>
</div>
</template>
33 changes: 23 additions & 10 deletions packages/oxc/src/app/pages/oxlint.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
<script setup lang="ts">
import PanelSideNav from '@vitejs/devtools-ui/components/Panel/PanelSideNav.vue'
import { useSideNav } from '@vitejs/devtools-ui/composables/nav'
import { useAsyncState } from '@vueuse/core'
import { createOverview } from '../utils/overview'
import { useRpc } from '#imports'

const rpc = useRpc()
const { state: overview } = useAsyncState(
() => rpc.value.call('devtools-oxc:overview'),
createOverview(),
)

useSideNav(() => [
{
title: 'Home',
icon: 'i-ph-house-duotone',
to: '/',
},
{
title: 'Lint Inspector',
icon: 'i-ph-magnifying-glass-duotone',
to: '/oxlint/lint',
},
{
title: 'Config Inspector',
icon: 'i-ph-gear-duotone',
to: '/oxlint/config',
},
...(overview.value.oxlint.installed
? [
{
title: 'Lint Inspector',
icon: 'i-ph-magnifying-glass-duotone',
to: '/oxlint/lint',
},
{
title: 'Config Inspector',
icon: 'i-ph-gear-duotone',
to: '/oxlint/config',
},
]
: []),
{
title: 'Documents',
icon: 'i-ph-book-open-duotone',
Expand Down
Loading
Loading