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

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

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

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

## Source
- [`packages/oxc/src/node/rpc/functions/oxfmt-setup.ts`](https://github.com/vitejs/devtools/blob/main/packages/oxc/src/node/rpc/functions/oxfmt-setup.ts) — runs the installation 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 @@ -79,3 +79,4 @@ Emitted by `@vitejs/devtools-oxc`.
| [OXDT0003](./OXDT0003) | error | Failed to Delete Lint Result |
| [OXDT0004](./OXDT0004) | error | Oxlint Config Inspection Failed |
| [OXDT0005](./OXDT0005) | error | Oxlint Setup Failed |
| [OXDT0006](./OXDT0006) | error | Oxfmt Setup Failed |
185 changes: 185 additions & 0 deletions packages/oxc/src/app/components/SetupOxfmtDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<script setup lang="ts">
import { DEVTOOLS_TERMINALS_DOCK_ID } from '@vitejs/devtools-kit/constants'
import ActionButton from '@vitejs/devtools-ui/components/Action/ActionButton.vue'
import FormCheckbox from '@vitejs/devtools-ui/components/Form/FormCheckbox.vue'
import OverlayModal from '@vitejs/devtools-ui/components/Overlay/OverlayModal.vue'
import VisualLoading from '@vitejs/devtools-ui/components/Visual/VisualLoading.vue'
import { ref, watch } from 'vue'
import { useRpc } from '#imports'

const emit = defineEmits<{ refresh: [] }>()
const open = defineModel<boolean>('open', { default: false })
const rpc = useRpc()

type Stage = 'confirm' | 'running' | 'error'
type Migration = 'prettier' | 'biome'

const stage = ref<Stage>('confirm')
const canMigrate = ref(false)
const migrate = ref(true)
const migration = ref<Migration>()
const gitDirty = ref(false)
const commandLine = ref('')
const sessionId = ref<string>()
const errorMessage = ref<string>()
const isLoading = ref(false)
let previewRequest = 0

async function loadPreview() {
const request = ++previewRequest
isLoading.value = true
try {
const preview = await rpc.value.call('devtools-oxc:oxfmt-setup-preview', {
migrate: migrate.value,
})
if (request !== previewRequest) return
canMigrate.value = preview.canMigrate
migration.value = preview.migration
if (!preview.canMigrate) migrate.value = false
commandLine.value = preview.command
gitDirty.value = preview.gitDirty
} catch (error) {
if (request !== previewRequest) return
errorMessage.value = error instanceof Error ? error.message : String(error)
} finally {
if (request === previewRequest) isLoading.value = false
}
}

watch(open, async isOpen => {
if (!isOpen) return
stage.value = 'confirm'
canMigrate.value = false
migrate.value = true
migration.value = undefined
gitDirty.value = false
commandLine.value = ''
sessionId.value = undefined
errorMessage.value = undefined
await loadPreview()
})

watch(migrate, () => {
if (open.value && stage.value === 'confirm') loadPreview()
})

async function confirmSetup() {
stage.value = 'running'
errorMessage.value = undefined
try {
const result = await rpc.value.call('devtools-oxc:setup-oxfmt', {
migrate: migrate.value,
})
sessionId.value = result.sessionId
await rpc.value.call('devtools-oxc:wait-for-setup')
if (!open.value) return
emit('refresh')
open.value = false
} catch (error) {
if (!open.value) return
stage.value = 'error'
errorMessage.value = error instanceof Error ? error.message : String(error)
}
}

async function viewInTerminal() {
if (sessionId.value) {
await rpc.value.call('hub:docks:activate', {
dockId: DEVTOOLS_TERMINALS_DOCK_ID,
params: { sessionId: sessionId.value },
})
}
}
</script>

<template>
<OverlayModal v-model:open="open">
<template #title> Setup Oxfmt with devtools</template>

<div class="flex flex-col gap-4 w-140 max-w-full min-h-64">
<template v-if="stage === 'confirm'">
<p class="m0 op70 text-sm">
<template v-if="canMigrate && migrate">
Oxfmt will be installed as a development dependency and migrate your
{{ migration === 'prettier' ? 'Prettier' : 'Biome' }} configuration.
</template>
<template v-else>
Oxfmt will be installed as a development dependency and create a starter configuration
with <code>oxfmt --init</code>.
</template>
</p>

<div>
<pre
class="m0 p3 rounded-lg border border-base bg-code font-mono text-sm of-auto text-left"
><code>{{ commandLine || 'Loading…' }}</code></pre>
</div>

<p v-if="gitDirty" class="m0 text-amber text-sm flex gap-2 items-start">
<span class="i-ph-warning-duotone mt-0.5 shrink-0" />
<span>The Git working tree is not clean. Setup may modify project files.</span>
</p>

<p v-if="errorMessage" class="m0 text-red text-sm">
{{ errorMessage }}
</p>

<div class="flex-auto" />
<div v-if="!isLoading" class="flex items-center justify-between gap-2">
<FormCheckbox v-if="canMigrate" v-model="migrate">
<span class="text-sm"
>Migrate from {{ migration === 'prettier' ? 'Prettier' : 'Biome' }}</span
>
</FormCheckbox>
<div class="flex gap-2">
<ActionButton @click="open = false"> Cancel </ActionButton>
<ActionButton
variant="primary"
icon="i-ph-rocket-launch-duotone"
@click="confirmSetup()"
>
Setup Oxfmt
</ActionButton>
</div>
</div>
</template>

<template v-else-if="stage === 'running'">
<VisualLoading class="flex-auto" text="Setting up Oxfmt…" />
<div class="flex justify-end gap-2">
<ActionButton @click="open = false"> Dismiss </ActionButton>
<ActionButton
v-if="sessionId"
variant="primary"
icon="i-ph-terminal-window-duotone"
@click="viewInTerminal()"
>
View in terminals
</ActionButton>
</div>
</template>

<template v-else>
<div class="flex gap-2 items-center text-red">
<span class="i-ph-x-circle-duotone" />
Oxfmt setup failed.
</div>
<p v-if="errorMessage" class="m0 op70 text-sm">
{{ errorMessage }}
</p>
<div class="flex-auto" />
<div class="flex justify-end gap-2">
<ActionButton @click="open = false"> Close </ActionButton>
<ActionButton
v-if="sessionId"
variant="primary"
icon="i-ph-terminal-window-duotone"
@click="viewInTerminal()"
>
View in terminals
</ActionButton>
</div>
</template>
</div>
</OverlayModal>
</template>
7 changes: 5 additions & 2 deletions packages/oxc/src/app/components/SetupOxlintDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ const gitDirty = ref(false)
const commandLine = ref('')
const sessionId = ref<string>()
const errorMessage = ref<string>()
const isLoading = ref(false)
let previewRequest = 0

async function loadPreview() {
const request = ++previewRequest
isLoading.value = true
try {
const preview = await rpc.value.call('devtools-oxc:setup-preview', {
migrate: migrate.value,
Expand All @@ -36,6 +38,8 @@ async function loadPreview() {
} catch (error) {
if (request !== previewRequest) return
errorMessage.value = error instanceof Error ? error.message : String(error)
} finally {
if (request === previewRequest) isLoading.value = false
}
}

Expand Down Expand Up @@ -83,7 +87,6 @@ async function viewInTerminal() {
params: { sessionId: sessionId.value },
})
}
open.value = false
}
</script>

Expand Down Expand Up @@ -128,7 +131,7 @@ async function viewInTerminal() {
</p>

<div class="flex-auto" />
<div class="flex items-center justify-between gap-2">
<div v-if="!isLoading" class="flex items-center justify-between gap-2">
<FormCheckbox v-if="canMigrate" v-model="migrate">
<span class="text-sm">Migrate from ESLint</span>
</FormCheckbox>
Expand Down
14 changes: 14 additions & 0 deletions packages/oxc/src/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
} = useAsyncState(() => rpc.value.call('devtools-oxc:overview'), createOverview())

const setupOpen = ref(false)
const oxfmtSetupOpen = ref(false)

interface ToolView {
title: string
Expand Down Expand Up @@ -155,6 +156,18 @@ const tools = computed(() => {
<div class="text-xs op-fade mt-0.5">Install & setup Oxlint</div>
</div>
</button>
<button
v-if="tool.id === 'oxfmt' && !tool.info.installed"
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!"
@click="oxfmtSetupOpen = true"
>
<div class="i-ph-rocket-launch-duotone text-2xl mt-0.5" />
<div class="text-left">
<div class="font-medium">Setup Oxfmt</div>
<div class="text-xs op-fade mt-0.5">Install & setup Oxfmt</div>
</div>
</button>
<NuxtLink
v-for="view in tool.views"
:key="view.title"
Expand All @@ -172,5 +185,6 @@ const tools = computed(() => {
</div>
</div>
<SetupOxlintDialog v-model:open="setupOpen" @refresh="refreshOverview()" />
<SetupOxfmtDialog v-model:open="oxfmtSetupOpen" @refresh="refreshOverview()" />
</div>
</template>
24 changes: 24 additions & 0 deletions packages/oxc/src/modules/rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
import { DevToolsServer } from '../../../core/src/node/plugins/server'
import { rpcFunctions } from '../node/rpc'

export default defineNuxtModule({
meta: {
name: 'devtools-rpc',
configKey: 'devtoolsRpc',
},
setup() {
addVitePlugin({
name: 'vite:devtools:oxc',
devtools: {
setup(ctx) {
for (const fn of rpcFunctions) {
ctx.rpc.register(fn as any)
}
},
},
})

addVitePlugin(DevToolsServer())
},
})
Loading
Loading