mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(cli): add nuxt + convex support (#458)
This commit is contained in:
5
.changeset/common-steaks-joke.md
Normal file
5
.changeset/common-steaks-joke.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Nuxt + Convex support
|
||||||
@@ -101,11 +101,17 @@ export const dependencyVersionMap = {
|
|||||||
"@trpc/server": "^11.4.2",
|
"@trpc/server": "^11.4.2",
|
||||||
"@trpc/client": "^11.4.2",
|
"@trpc/client": "^11.4.2",
|
||||||
|
|
||||||
convex: "^1.25.0",
|
convex: "^1.25.4",
|
||||||
"@convex-dev/react-query": "^0.0.0-alpha.8",
|
"@convex-dev/react-query": "^0.0.0-alpha.8",
|
||||||
"convex-svelte": "^0.0.11",
|
"convex-svelte": "^0.0.11",
|
||||||
|
"convex-nuxt": "0.1.5",
|
||||||
|
"convex-vue": "^0.1.5",
|
||||||
|
|
||||||
"@tanstack/svelte-query": "^5.74.4",
|
"@tanstack/svelte-query": "^5.74.4",
|
||||||
|
|
||||||
|
"@tanstack/vue-query-devtools": "^5.83.0",
|
||||||
|
"@tanstack/vue-query": "^5.83.0",
|
||||||
|
|
||||||
"@tanstack/react-query-devtools": "^5.80.5",
|
"@tanstack/react-query-devtools": "^5.80.5",
|
||||||
"@tanstack/react-query": "^5.80.5",
|
"@tanstack/react-query": "^5.80.5",
|
||||||
|
|
||||||
|
|||||||
@@ -345,5 +345,5 @@ function getBunWebNativeWarning(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getWorkersDeployInstructions(runCmd?: string): string {
|
function getWorkersDeployInstructions(runCmd?: string): string {
|
||||||
return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd || "bun run"} deploy`}`;
|
return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export async function setupApi(config: ProjectConfig) {
|
|||||||
if (api === "orpc") {
|
if (api === "orpc") {
|
||||||
await addPackageDependency({
|
await addPackageDependency({
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"@tanstack/vue-query",
|
||||||
|
"@tanstack/vue-query-devtools",
|
||||||
"@orpc/tanstack-query",
|
"@orpc/tanstack-query",
|
||||||
"@orpc/client",
|
"@orpc/client",
|
||||||
"@orpc/server",
|
"@orpc/server",
|
||||||
@@ -219,7 +221,10 @@ export async function setupApi(config: ProjectConfig) {
|
|||||||
if (hasSvelteWeb) {
|
if (hasSvelteWeb) {
|
||||||
webDepsToAdd.push("convex-svelte");
|
webDepsToAdd.push("convex-svelte");
|
||||||
}
|
}
|
||||||
|
if (hasNuxtWeb) {
|
||||||
|
webDepsToAdd.push("convex-nuxt");
|
||||||
|
webDepsToAdd.push("convex-vue");
|
||||||
|
}
|
||||||
await addPackageDependency({
|
await addPackageDependency({
|
||||||
dependencies: webDepsToAdd,
|
dependencies: webDepsToAdd,
|
||||||
projectDir: webDir,
|
projectDir: webDir,
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ export async function getBackendFrameworkChoice(
|
|||||||
): Promise<Backend> {
|
): Promise<Backend> {
|
||||||
if (backendFramework !== undefined) return backendFramework;
|
if (backendFramework !== undefined) return backendFramework;
|
||||||
|
|
||||||
const hasIncompatibleFrontend = frontends?.some(
|
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
|
||||||
(f) => f === "nuxt" || f === "solid",
|
|
||||||
);
|
|
||||||
|
|
||||||
const backendOptions: Array<{
|
const backendOptions: Array<{
|
||||||
value: Backend;
|
value: Backend;
|
||||||
@@ -59,15 +57,10 @@ export async function getBackendFrameworkChoice(
|
|||||||
hint: "No backend server",
|
hint: "No backend server",
|
||||||
});
|
});
|
||||||
|
|
||||||
let initialValue = DEFAULT_CONFIG.backend;
|
|
||||||
if (hasIncompatibleFrontend && initialValue === "convex") {
|
|
||||||
initialValue = "hono";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await select<Backend>({
|
const response = await select<Backend>({
|
||||||
message: "Select backend",
|
message: "Select backend",
|
||||||
options: backendOptions,
|
options: backendOptions,
|
||||||
initialValue,
|
initialValue: DEFAULT_CONFIG.backend,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export async function getFrontendChoice(
|
|||||||
|
|
||||||
const webOptions = allWebOptions.filter((option) => {
|
const webOptions = allWebOptions.filter((option) => {
|
||||||
if (backend === "convex") {
|
if (backend === "convex") {
|
||||||
return option.value !== "nuxt" && option.value !== "solid";
|
return option.value !== "solid";
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export function processAndValidateFlags(
|
|||||||
|
|
||||||
if (providedFlags.has("frontend") && options.frontend) {
|
if (providedFlags.has("frontend") && options.frontend) {
|
||||||
const incompatibleFrontends = options.frontend.filter(
|
const incompatibleFrontends = options.frontend.filter(
|
||||||
(f) => f === "nuxt" || f === "solid",
|
(f) => f === "solid",
|
||||||
);
|
);
|
||||||
if (incompatibleFrontends.length > 0) {
|
if (incompatibleFrontends.length > 0) {
|
||||||
consola.fatal(
|
consola.fatal(
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"convex": "^1.25.0"
|
"convex": "^1.25.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
|
|
||||||
|
|
||||||
const { $orpc } = useNuxtApp()
|
|
||||||
|
|
||||||
const newTodoText = ref('')
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
const todos = useQuery($orpc.todo.getAll.queryOptions())
|
|
||||||
|
|
||||||
const createMutation = useMutation($orpc.todo.create.mutationOptions({
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries()
|
|
||||||
newTodoText.value = ''
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const toggleMutation = useMutation($orpc.todo.toggle.mutationOptions({
|
|
||||||
onSuccess: () => queryClient.invalidateQueries()
|
|
||||||
}))
|
|
||||||
|
|
||||||
const deleteMutation = useMutation($orpc.todo.delete.mutationOptions({
|
|
||||||
onSuccess: () => queryClient.invalidateQueries()
|
|
||||||
}))
|
|
||||||
|
|
||||||
function handleAddTodo() {
|
|
||||||
if (newTodoText.value.trim()) {
|
|
||||||
createMutation.mutate({ text: newTodoText.value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleToggleTodo(id: number, completed: boolean) {
|
|
||||||
toggleMutation.mutate({ id, completed: !completed })
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDeleteTodo(id: number) {
|
|
||||||
deleteMutation.mutate({ id })
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mx-auto w-full max-w-md py-10">
|
|
||||||
<UCard>
|
|
||||||
<template #header>
|
|
||||||
<div>
|
|
||||||
<div class="text-xl font-bold">Todo List</div>
|
|
||||||
<div class="text-muted text-sm">Manage your tasks efficiently</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<form @submit.prevent="handleAddTodo" class="mb-6 flex items-center gap-2">
|
|
||||||
<UInput
|
|
||||||
v-model="newTodoText"
|
|
||||||
placeholder="Add a new task..."
|
|
||||||
autocomplete="off"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
<UButton
|
|
||||||
type="submit"
|
|
||||||
icon="i-lucide-plus"
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</UButton>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div v-if="todos.status.value === 'pending'" class="flex justify-center py-4">
|
|
||||||
<UIcon name="i-lucide-loader-2" class="animate-spin w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<p v-else-if="todos.status.value === 'error'" class="py-4 text-center text-red-500">
|
|
||||||
Error: {{ todos.error.value?.message || 'Failed to load todos' }}
|
|
||||||
</p>
|
|
||||||
<p v-else-if="todos.data.value?.length === 0" class="py-4 text-center">
|
|
||||||
No todos yet. Add one above!
|
|
||||||
</p>
|
|
||||||
<ul v-else class="space-y-2">
|
|
||||||
<li
|
|
||||||
v-for="todo in todos.data.value"
|
|
||||||
:key="todo.id"
|
|
||||||
class="flex items-center justify-between rounded-md border p-2"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<UCheckbox
|
|
||||||
:model-value="todo.completed"
|
|
||||||
@update:model-value="() => handleToggleTodo(todo.id, todo.completed)"
|
|
||||||
:id="`todo-${todo.id}`"
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
:for="`todo-${todo.id}`"
|
|
||||||
:class="{ 'line-through text-muted': todo.completed }"
|
|
||||||
class="cursor-pointer"
|
|
||||||
>
|
|
||||||
{{ todo.text }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<UButton
|
|
||||||
color="neutral"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
square
|
|
||||||
@click="handleDeleteTodo(todo.id)"
|
|
||||||
aria-label="Delete todo"
|
|
||||||
icon="i-lucide-trash-2"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</UCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
||||||
|
import type { Id } from "@{{ projectName }}/backend/convex/_generated/dataModel";
|
||||||
|
import { useConvexMutation, useConvexQuery } from "convex-vue";
|
||||||
|
|
||||||
|
const { data, error, isPending } = useConvexQuery(api.todos.getAll, {});
|
||||||
|
|
||||||
|
const newTodoText = ref("");
|
||||||
|
const { mutate: createTodo, isPending: isCreatePending } = useConvexMutation(api.todos.create);
|
||||||
|
|
||||||
|
const { mutate: toggleTodo } = useConvexMutation(api.todos.toggle);
|
||||||
|
const { mutate: deleteTodo, error: deleteError } = useConvexMutation(
|
||||||
|
api.todos.deleteTodo,
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleAddTodo() {
|
||||||
|
const text = newTodoText.value.trim();
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
createTodo({ text });
|
||||||
|
newTodoText.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggleTodo(id: Id<"todos">, completed: boolean) {
|
||||||
|
toggleTodo({ id, completed: !completed });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteTodo(id: Id<"todos">) {
|
||||||
|
deleteTodo({ id });
|
||||||
|
}
|
||||||
|
{{else}}
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
|
||||||
|
|
||||||
|
const { $orpc } = useNuxtApp()
|
||||||
|
|
||||||
|
const newTodoText = ref('')
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const todos = useQuery($orpc.todo.getAll.queryOptions())
|
||||||
|
|
||||||
|
const createMutation = useMutation($orpc.todo.create.mutationOptions({
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries()
|
||||||
|
newTodoText.value = ''
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const toggleMutation = useMutation($orpc.todo.toggle.mutationOptions({
|
||||||
|
onSuccess: () => queryClient.invalidateQueries()
|
||||||
|
}))
|
||||||
|
|
||||||
|
const deleteMutation = useMutation($orpc.todo.delete.mutationOptions({
|
||||||
|
onSuccess: () => queryClient.invalidateQueries()
|
||||||
|
}))
|
||||||
|
|
||||||
|
function handleAddTodo() {
|
||||||
|
if (newTodoText.value.trim()) {
|
||||||
|
createMutation.mutate({ text: newTodoText.value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToggleTodo(id: number, completed: boolean) {
|
||||||
|
toggleMutation.mutate({ id, completed: !completed })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteTodo(id: number) {
|
||||||
|
deleteMutation.mutate({ id })
|
||||||
|
}
|
||||||
|
{{/if}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto w-full max-w-md py-10">
|
||||||
|
<UCard>
|
||||||
|
<template #header>
|
||||||
|
<div>
|
||||||
|
<div class="text-xl font-bold">Todo List</div>
|
||||||
|
<div class="text-muted text-sm">Manage your tasks efficiently</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<form @submit.prevent="handleAddTodo" class="mb-6 flex items-center gap-2">
|
||||||
|
<UInput
|
||||||
|
v-model="newTodoText"
|
||||||
|
placeholder="Add a new task..."
|
||||||
|
autocomplete="off"
|
||||||
|
class="w-full"
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
:disabled="isCreatePending"
|
||||||
|
{{/if}}
|
||||||
|
/>
|
||||||
|
<UButton
|
||||||
|
type="submit"
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
:disabled="isCreatePending || !newTodoText.trim()"
|
||||||
|
{{/if}}
|
||||||
|
>
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
<span v-if="isCreatePending">
|
||||||
|
<UIcon name="i-lucide-loader-2" class="animate-spin" />
|
||||||
|
</span>
|
||||||
|
<span v-else>Add</span>
|
||||||
|
{{else}}
|
||||||
|
Add
|
||||||
|
{{/if}}
|
||||||
|
</UButton>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
<p v-if="error || deleteError" class="mb-4 text-red-500">
|
||||||
|
Error: \{{ error?.message || deleteError?.message }}
|
||||||
|
</p>
|
||||||
|
<div v-if="isPending" class="flex justify-center py-4">
|
||||||
|
<UIcon name="i-lucide-loader-2" class="animate-spin w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<p v-else-if="data?.length === 0" class="py-4 text-center">
|
||||||
|
No todos yet. Add one above!
|
||||||
|
</p>
|
||||||
|
<ul v-else-if="data" class="space-y-2">
|
||||||
|
<li
|
||||||
|
v-for="todo in data"
|
||||||
|
:key="todo._id"
|
||||||
|
class="flex items-center justify-between rounded-md border p-2"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UCheckbox
|
||||||
|
:model-value="todo.completed"
|
||||||
|
@update:model-value="() => handleToggleTodo(todo._id, todo.completed)"
|
||||||
|
:id="`todo-${todo._id}`"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
:for="`todo-${todo._id}`"
|
||||||
|
:class="{ 'line-through text-muted': todo.completed }"
|
||||||
|
class="cursor-pointer"
|
||||||
|
>
|
||||||
|
\{{ todo.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
square
|
||||||
|
@click="handleDeleteTodo(todo._id)"
|
||||||
|
aria-label="Delete todo"
|
||||||
|
icon="i-lucide-trash-2"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<div v-if="todos.status.value === 'pending'" class="flex justify-center py-4">
|
||||||
|
<UIcon name="i-lucide-loader-2" class="animate-spin w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<p v-else-if="todos.status.value === 'error'" class="py-4 text-center text-red-500">
|
||||||
|
Error: \{{ todos.error.value?.message || 'Failed to load todos' }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="todos.data.value?.length === 0" class="py-4 text-center">
|
||||||
|
No todos yet. Add one above!
|
||||||
|
</p>
|
||||||
|
<ul v-else class="space-y-2">
|
||||||
|
<li
|
||||||
|
v-for="todo in todos.data.value"
|
||||||
|
:key="todo.id"
|
||||||
|
class="flex items-center justify-between rounded-md border p-2"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UCheckbox
|
||||||
|
:model-value="todo.completed"
|
||||||
|
@update:model-value="() => handleToggleTodo(todo.id, todo.completed)"
|
||||||
|
:id="`todo-${todo.id}`"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
:for="`todo-${todo.id}`"
|
||||||
|
:class="{ 'line-through text-muted': todo.completed }"
|
||||||
|
class="cursor-pointer"
|
||||||
|
>
|
||||||
|
\{{ todo.text }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
square
|
||||||
|
@click="handleDeleteTodo(todo.id)"
|
||||||
|
aria-label="Delete todo"
|
||||||
|
icon="i-lucide-trash-2"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
{{#if (eq api "orpc")}}
|
||||||
import { VueQueryDevtools } from '@tanstack/vue-query-devtools'
|
import { VueQueryDevtools } from '@tanstack/vue-query-devtools'
|
||||||
|
{{/if}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -9,5 +11,7 @@ import { VueQueryDevtools } from '@tanstack/vue-query-devtools'
|
|||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</UApp>
|
</UApp>
|
||||||
|
{{#if (eq api "orpc")}}
|
||||||
<VueQueryDevtools />
|
<VueQueryDevtools />
|
||||||
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { USeparator } from '#components';
|
|
||||||
import ModeToggle from './ModeToggle.vue'
|
import ModeToggle from './ModeToggle.vue'
|
||||||
{{#if auth}}
|
{{#if auth}}
|
||||||
import UserMenu from './UserMenu.vue'
|
import UserMenu from './UserMenu.vue'
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const isDark = computed({
|
const isDark = computed({
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
||||||
|
import { useConvexQuery } from "convex-vue";
|
||||||
|
{{else}}
|
||||||
{{#unless (eq api "none")}}
|
{{#unless (eq api "none")}}
|
||||||
const { $orpc } = useNuxtApp()
|
const { $orpc } = useNuxtApp()
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
const TITLE_TEXT = `
|
const TITLE_TEXT = `
|
||||||
██████╗ ███████╗████████╗████████╗███████╗██████╗
|
██████╗ ███████╗████████╗████████╗███████╗██████╗
|
||||||
@@ -20,19 +25,34 @@ const TITLE_TEXT = `
|
|||||||
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
const healthCheck = useConvexQuery(api.healthCheck.get, {});
|
||||||
|
{{else}}
|
||||||
{{#unless (eq api "none")}}
|
{{#unless (eq api "none")}}
|
||||||
const healthCheck = useQuery($orpc.healthCheck.queryOptions())
|
const healthCheck = useQuery($orpc.healthCheck.queryOptions())
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto max-w-3xl px-4 py-2">
|
<div class="container mx-auto max-w-3xl px-4 py-2">
|
||||||
<pre class="overflow-x-auto font-mono text-sm whitespace-pre-wrap">\{{ TITLE_TEXT }}</pre>
|
<pre class="overflow-x-auto font-mono text-sm whitespace-pre-wrap">\{{ TITLE_TEXT }}</pre>
|
||||||
<div class="grid gap-6 mt-4">
|
<div class="grid gap-6 mt-4">
|
||||||
{{#unless (eq api "none")}}
|
|
||||||
<section class="rounded-lg border p-4">
|
<section class="rounded-lg border p-4">
|
||||||
<h2 class="mb-2 font-medium">API Status</h2>
|
<h2 class="mb-2 font-medium">API Status</h2>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
<span class="text-sm text-muted-foreground">
|
||||||
|
\{{
|
||||||
|
healthCheck === undefined
|
||||||
|
? "Checking..."
|
||||||
|
: healthCheck.data.value === "OK"
|
||||||
|
? "Connected"
|
||||||
|
: "Error"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
{{#unless (eq api "none")}}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
class="w-2 h-2 rounded-full"
|
class="w-2 h-2 rounded-full"
|
||||||
@@ -60,9 +80,10 @@ const healthCheck = useQuery($orpc.healthCheck.queryOptions())
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{/unless}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,12 +2,22 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: 'latest',
|
compatibilityDate: 'latest',
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
modules: ['@nuxt/ui'],
|
modules: [
|
||||||
|
'@nuxt/ui'
|
||||||
|
{{#if (eq backend "convex")}},
|
||||||
|
'convex-nuxt'
|
||||||
|
{{/if}}
|
||||||
|
],
|
||||||
css: ['~/assets/css/main.css'],
|
css: ['~/assets/css/main.css'],
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 3001
|
port: 3001
|
||||||
},
|
},
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
{{#if (eq backend "convex")}}
|
||||||
|
convex: {
|
||||||
|
url: process.env.NUXT_PUBLIC_CONVEX_URL,
|
||||||
|
},
|
||||||
|
{{/if}}
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
serverURL: process.env.NUXT_PUBLIC_SERVER_URL,
|
serverURL: process.env.NUXT_PUBLIC_SERVER_URL,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/ui": "3.3.0",
|
"@nuxt/ui": "3.3.0",
|
||||||
"@tanstack/vue-query": "^5.83.0",
|
|
||||||
"nuxt": "^4.0.2",
|
"nuxt": "^4.0.2",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"vue": "^3.5.18",
|
"vue": "^3.5.18",
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"@tanstack/vue-query-devtools": "^5.83.0",
|
|
||||||
"@iconify-json/lucide": "^1.2.57"
|
"@iconify-json/lucide": "^1.2.57"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const incompatibleConvexFrontends = ["nuxt", "solid"];
|
const incompatibleConvexFrontends = ["solid"];
|
||||||
const originalWebFrontendLength = nextStack.webFrontend.length;
|
const originalWebFrontendLength = nextStack.webFrontend.length;
|
||||||
nextStack.webFrontend = nextStack.webFrontend.filter(
|
nextStack.webFrontend = nextStack.webFrontend.filter(
|
||||||
(f) => !incompatibleConvexFrontends.includes(f),
|
(f) => !incompatibleConvexFrontends.includes(f),
|
||||||
@@ -241,16 +241,14 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
|||||||
if (nextStack.webFrontend.length !== originalWebFrontendLength) {
|
if (nextStack.webFrontend.length !== originalWebFrontendLength) {
|
||||||
changed = true;
|
changed = true;
|
||||||
notes.webFrontend.notes.push(
|
notes.webFrontend.notes.push(
|
||||||
"Nuxt and Solid are not compatible with Convex backend and have been removed.",
|
"Solid is not compatible with Convex backend and has been removed.",
|
||||||
);
|
|
||||||
notes.backend.notes.push(
|
|
||||||
"Convex backend is not compatible with Nuxt or Solid.",
|
|
||||||
);
|
);
|
||||||
|
notes.backend.notes.push("Convex backend is not compatible with Solid.");
|
||||||
notes.webFrontend.hasIssue = true;
|
notes.webFrontend.hasIssue = true;
|
||||||
notes.backend.hasIssue = true;
|
notes.backend.hasIssue = true;
|
||||||
changes.push({
|
changes.push({
|
||||||
category: "convex",
|
category: "convex",
|
||||||
message: "Removed incompatible web frontends (Nuxt, Solid)",
|
message: "Removed incompatible web frontends (Solid)",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (nextStack.nativeFrontend[0] === "none") {
|
if (nextStack.nativeFrontend[0] === "none") {
|
||||||
|
|||||||
Reference in New Issue
Block a user