create-vexcms 0.1.0-alpha.5 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/index.js +33 -0
  2. package/package.json +1 -1
  3. package/templates/base-nextjs/convex/auth/index.ts +2 -2
  4. package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
  5. package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
  6. package/templates/base-nextjs/next.config.ts +34 -5
  7. package/templates/base-nextjs/package.json +6 -6
  8. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +1 -1
  9. package/templates/base-nextjs/src/auth/serverUtils.ts +5 -5
  10. package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
  11. package/templates/base-nextjs/src/vexcms/collections/users.ts +1 -1
  12. package/templates/marketing-site/convex/schema.ts +4 -4
  13. package/templates/marketing-site/convex/seed.ts +286 -97
  14. package/templates/marketing-site/convex/theme.ts +2 -2
  15. package/templates/marketing-site/convex/vex.schema.ts +70 -38
  16. package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +7 -8
  17. package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +4 -2
  18. package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +13 -3
  19. package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +4 -2
  20. package/templates/marketing-site/src/app/globals.css +374 -0
  21. package/templates/marketing-site/src/app/layout.tsx +14 -0
  22. package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
  23. package/templates/marketing-site/src/components/CodePane.tsx +86 -0
  24. package/templates/marketing-site/src/components/Container.tsx +25 -0
  25. package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
  26. package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +20 -17
  27. package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
  28. package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
  29. package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
  30. package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
  31. package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
  32. package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
  33. package/templates/marketing-site/src/components/ThemeStyle.tsx +1 -2
  34. package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +19 -12
  35. package/templates/marketing-site/src/lib/highlight.ts +71 -0
  36. package/templates/marketing-site/src/lib/metadata.ts +19 -21
  37. package/templates/marketing-site/src/lib/shikiStarkEmber.json +57 -0
  38. package/templates/marketing-site/src/proxy.ts +123 -0
  39. package/templates/marketing-site/src/vex.config.ts +1 -2
  40. package/templates/marketing-site/src/vex.types.ts +92 -75
  41. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +1 -0
  42. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
  43. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
  44. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
  45. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +1 -0
  46. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
  47. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
  48. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
  49. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +3 -0
  50. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
  51. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +3 -0
  52. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
  53. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +26 -11
  54. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +172 -83
  55. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +1 -0
  56. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
  57. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
  58. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +255 -73
  59. package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
  60. package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
  61. package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
  62. package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
  63. package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
  64. package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
  65. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
  66. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
  67. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
  68. package/templates/marketing-site/src/vexcms/collections/pages.ts +1 -0
  69. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
  70. package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
package/dist/index.js CHANGED
@@ -2068,6 +2068,10 @@ function stripOrgFromDbConstants(filePath, content) {
2068
2068
  }
2069
2069
 
2070
2070
  // src/installers/base.ts
2071
+ var MARKETING_DEPENDENCIES = {
2072
+ /** Render-time syntax highlighting for the CodeShowcase and Split blocks. */
2073
+ shiki: "^4.4.3"
2074
+ };
2071
2075
  var VexFrameworkInstaller = class {
2072
2076
  /**
2073
2077
  * Binds this installer to the destination directory and package name used
@@ -2106,6 +2110,34 @@ var VexFrameworkInstaller = class {
2106
2110
  }
2107
2111
  await overlayTemplate({ overlayDir, targetDir: this.targetPath });
2108
2112
  }
2113
+ /**
2114
+ * Apply the parts of the marketing overlay that copying files cannot express.
2115
+ *
2116
+ * `overlayTemplate` only adds and overwrites — it has no way to remove a
2117
+ * base file, and it does not touch `package.json`. Two things therefore
2118
+ * have to happen here:
2119
+ *
2120
+ * 1. **Delete base's `(frontend)/page.tsx`.** It and the overlay's
2121
+ * `(frontend)/(site)/page.tsx` both resolve to `/`. Next picks base's,
2122
+ * so the marketing home page is unreachable and every scaffold shows the
2123
+ * bare bootstrap `WelcomePage` instead of the seeded site. The `(site)`
2124
+ * route group owns `/` in a marketing scaffold.
2125
+ * 2. **Add `shiki`.** The CodeShowcase and Split blocks highlight at render
2126
+ * time in a server component. It is a marketing-only dependency, so it
2127
+ * is added here rather than carried in base's `package.json` where a
2128
+ * `--bare` scaffold would install it for nothing.
2129
+ */
2130
+ async finalizeMarketingOverlay() {
2131
+ const homeRoute = path2.join(this.targetPath, "src/app/(frontend)/page.tsx");
2132
+ await fs5.remove(homeRoute);
2133
+ const pkgPath = path2.join(this.targetPath, "package.json");
2134
+ const pkg = await fs5.readJson(pkgPath);
2135
+ pkg.dependencies = { ...pkg.dependencies, shiki: MARKETING_DEPENDENCIES.shiki };
2136
+ pkg.dependencies = Object.fromEntries(
2137
+ Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
2138
+ );
2139
+ await fs5.writeJson(pkgPath, pkg, { spaces: 2 });
2140
+ }
2109
2141
  /**
2110
2142
  * Update the package.json name field, the vex:update script (to match the
2111
2143
  * detected package manager), and README.md's `{{PROJECT_NAME}}` heading.
@@ -2398,6 +2430,7 @@ var VexFrameworkInstaller = class {
2398
2430
  const overlaySpinner = ora("Applying marketing site template...").start();
2399
2431
  try {
2400
2432
  await this.applyTemplateOverlay("marketing-site");
2433
+ await this.finalizeMarketingOverlay();
2401
2434
  overlaySpinner.succeed("Marketing site template applied");
2402
2435
  } catch (error) {
2403
2436
  overlaySpinner.fail("Failed to apply template overlay");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vexcms",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.7",
4
4
  "description": "Scaffold a new VexCMS project",
5
5
  "keywords": [
6
6
  "cms",
@@ -3,10 +3,10 @@ import type { GenericActionCtx } from "convex/server"
3
3
  import { createBetterAuthAdapter } from "@vexcms/better-auth"
4
4
  import { betterAuth } from "better-auth"
5
5
 
6
- import { authOptions } from "./options"
7
-
8
6
  import type { DataModel } from "../_generated/dataModel"
9
7
 
8
+ import { authOptions } from "./options"
9
+
10
10
  export const createAuth = (ctx: GenericActionCtx<DataModel>, { optionsOnly } = { optionsOnly: false }) => {
11
11
  return betterAuth({
12
12
  database: createBetterAuthAdapter(ctx),
@@ -19,7 +19,7 @@ export const getSessionWithUser = query({
19
19
  .withIndex("by_token", (q) => q.eq("token", args.sessionToken))
20
20
  .first()
21
21
 
22
- if (!session) return null
22
+ if (!session) {return null}
23
23
 
24
24
  if (session.expiresAt < Date.now()) {
25
25
  console.error("[getSessionWithUser] Session Expired")
@@ -27,10 +27,10 @@ export const getSessionWithUser = query({
27
27
  }
28
28
 
29
29
  const userId = session.userId
30
- if (!userId) return null
30
+ if (!userId) {return null}
31
31
 
32
32
  const user = await ctx.db.get(userId as Id<typeof TABLE_SLUG_USERS>)
33
- if (!user) return null
33
+ if (!user) {return null}
34
34
 
35
35
  return {
36
36
  session: {
@@ -1,10 +1,10 @@
1
1
  import { ConvexError } from "convex/values"
2
2
 
3
- import { mutation, query } from "../_generated/server"
4
-
5
3
  import { TABLE_SLUG_USERS } from "~/db/constants"
6
4
  import { USER_ROLES } from "~/db/constants/auth"
7
5
 
6
+ import { mutation, query } from "../_generated/server"
7
+
8
8
  /**
9
9
  * Check whether the admin panel has been bootstrapped (at least one admin
10
10
  * exists). Used by `WelcomePage` to decide between "Sign Up" and "Sign In".
@@ -1,7 +1,27 @@
1
1
  import type { NextConfig } from "next"
2
2
 
3
+ import { existsSync } from "node:fs"
4
+ import { dirname, join, resolve } from "node:path"
5
+
3
6
  import { env } from "./src/env.mjs"
4
7
 
8
+ /**
9
+ * Walks up from the app directory looking for `pnpm-workspace.yaml`.
10
+ *
11
+ * @param from - Directory to start from.
12
+ * @returns The workspace root when this app is a workspace member, otherwise
13
+ * `from` unchanged.
14
+ */
15
+ function findWorkspaceRoot(from: string): string {
16
+ let dir = resolve(from)
17
+ for (;;) {
18
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) {return dir}
19
+ const parent = dirname(dir)
20
+ if (parent === dir) {return resolve(from)}
21
+ dir = parent
22
+ }
23
+ }
24
+
5
25
  // Convex file storage URLs (`next/image` sources for uploaded media) are
6
26
  // served from `<deployment>.convex.cloud` — derive the hostname from the
7
27
  // validated NEXT_PUBLIC_CONVEX_URL so scaffolds work without a manual
@@ -20,12 +40,21 @@ const nextConfig: NextConfig = {
20
40
  },
21
41
  allowedDevOrigins: ["127.0.01", "localhost"],
22
42
  reactCompiler: true,
23
- // Pin the workspace root to THIS app. Without it, Next walks up and adopts
24
- // any outer lockfile (e.g. when this project lives inside a monorepo),
25
- // which shifts Turbopack's file-tracing root and Tailwind's source-scan
26
- // root producing wrong relative paths and scanning files outside the app.
43
+ // Turbopack's root sets the file-tracing root AND Tailwind's source-scan
44
+ // root, so it has to be the directory that actually owns `node_modules`.
45
+ //
46
+ // Standalone scaffold: that is this app. Pinning it stops Next walking up
47
+ // and adopting an unrelated outer lockfile.
48
+ //
49
+ // pnpm workspace member: it is the workspace root — dependencies are
50
+ // hoisted into the root virtual store and `next` is only reachable from
51
+ // there. Pinning to the app dir makes the build fail with "Could not find
52
+ // the Next.js package".
53
+ //
54
+ // Detecting `pnpm-workspace.yaml` picks the right answer in both cases
55
+ // without the template needing a monorepo-specific variant.
27
56
  turbopack: {
28
- root: import.meta.dirname,
57
+ root: findWorkspaceRoot(import.meta.dirname),
29
58
  },
30
59
  images: {
31
60
  // Additional remote image hosts (e.g. an external CMS or CDN) can be
@@ -24,11 +24,11 @@
24
24
  "@t3-oss/env-nextjs": "0.13.11",
25
25
  "@tanstack/react-form": "1.33.1",
26
26
  "@tanstack/react-query": "5.101.2",
27
- "@vexcms/better-auth": "~0.1.0-alpha.5",
28
- "@vexcms/core": "~0.1.0-alpha.5",
29
- "@vexcms/file-storage-convex": "~0.1.0-alpha.5",
30
- "@vexcms/next": "~0.1.0-alpha.5",
31
- "@vexcms/react": "~0.1.0-alpha.5",
27
+ "@vexcms/better-auth": "~0.1.0-alpha.7",
28
+ "@vexcms/core": "~0.1.0-alpha.7",
29
+ "@vexcms/file-storage-convex": "~0.1.0-alpha.7",
30
+ "@vexcms/next": "~0.1.0-alpha.7",
31
+ "@vexcms/react": "~0.1.0-alpha.7",
32
32
  "better-auth": "1.6.23",
33
33
  "class-variance-authority": "0.7.1",
34
34
  "clsx": "2.1.1",
@@ -52,7 +52,7 @@
52
52
  "@types/node": "20.19.43",
53
53
  "@types/react": "19.2.17",
54
54
  "@types/react-dom": "19.2.3",
55
- "@vexcms/cli": "~0.1.0-alpha.5",
55
+ "@vexcms/cli": "~0.1.0-alpha.7",
56
56
  "babel-plugin-react-compiler": "1.0.0",
57
57
  "eslint": "9.39.5",
58
58
  "eslint-config-next": "15.5.9",
@@ -1,6 +1,6 @@
1
1
  import { api } from "@convex/_generated/api";
2
- import { NextAdminPage } from "@vexcms/next/server";
3
2
  import { canAccessAdminPanel } from "@vexcms/core";
3
+ import { NextAdminPage } from "@vexcms/next/server";
4
4
  import { redirect } from "next/navigation";
5
5
 
6
6
  import { fetchAuthQuery, getToken } from "~/auth/server";
@@ -6,7 +6,7 @@ export async function getSessionToken() {
6
6
  const cookieStore = await cookies()
7
7
  const sessionTokenCookie = cookieStore.get("better-auth.session_token")?.value
8
8
 
9
- if (!sessionTokenCookie) return null
9
+ if (!sessionTokenCookie) {return null}
10
10
 
11
11
  // better-auth stores tokens as "<raw_token>.<hmac_signature>" (2 parts, not a JWT)
12
12
  // The raw token (first part) is what's indexed in the DB by_token
@@ -24,12 +24,12 @@ export async function getSessionToken() {
24
24
  export const getCurrentUser = async () => {
25
25
  try {
26
26
  const sessionToken = await getSessionToken()
27
- if (!sessionToken) return null
27
+ if (!sessionToken) {return null}
28
28
 
29
29
  const session = await fetchQuery(api.auth.sessions.getSessionWithUser, {
30
30
  sessionToken,
31
31
  })
32
- if (!session?.user) return null
32
+ if (!session?.user) {return null}
33
33
  return session.user
34
34
  } catch (error) {
35
35
  console.error("Error getting current user:", error)
@@ -43,12 +43,12 @@ export const getCurrentUser = async () => {
43
43
  export async function getSession() {
44
44
  try {
45
45
  const sessionToken = await getSessionToken()
46
- if (!sessionToken) return null
46
+ if (!sessionToken) {return null}
47
47
 
48
48
  const session = await fetchQuery(api.auth.sessions.getSessionWithUser, {
49
49
  sessionToken,
50
50
  })
51
- if (!session?.user) return null
51
+ if (!session?.user) {return null}
52
52
  return session
53
53
  } catch (error) {
54
54
  console.error("[getSession] Error:", error)
@@ -1,25 +1,12 @@
1
1
  "use client"
2
2
 
3
+ import { api } from "@convex/_generated/api"
3
4
  import { useMutation, useQuery } from "convex/react"
4
5
  import { usePathname, useRouter } from "next/navigation"
5
- import { useEffect, useState } from "react"
6
+ import { useEffect, useRef, useState } from "react"
6
7
 
7
- import { api } from "@convex/_generated/api"
8
8
  import { useSession } from "~/auth/client"
9
9
 
10
- function LoadingSpinner() {
11
- return (
12
- <svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
13
- <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
14
- <path
15
- className="opacity-75"
16
- fill="currentColor"
17
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
18
- />
19
- </svg>
20
- )
21
- }
22
-
23
10
  /**
24
11
  * Landing UI for a project that has not been bootstrapped yet (no admin user),
25
12
  * and the "Go to Admin" / "Sign in" entry point once it has.
@@ -41,33 +28,44 @@ export function WelcomePage() {
41
28
  const [promoting, setPromoting] = useState(false)
42
29
  const [navigating, setNavigating] = useState(false)
43
30
 
44
- // Reset navigating state when the user comes back to the home page
45
- // (e.g. closing the intercepting auth dialog)
46
- useEffect(() => {
47
- if (pathname === "/") {
48
- setNavigating(false)
49
- }
50
- }, [pathname])
31
+ // Reset `navigating` when the user lands back on the home page (e.g. they
32
+ // dismissed the intercepting auth dialog). Adjusting state during render on
33
+ // a changed input is the pattern React documents for this; an effect would
34
+ // paint the stale spinner for a frame first.
35
+ const [lastPathname, setLastPathname] = useState(pathname)
36
+ if (lastPathname !== pathname) {
37
+ setLastPathname(pathname)
38
+ if (pathname === "/") {setNavigating(false)}
39
+ }
40
+
41
+ // Run-once latch. `promoting` and `navigating` both render, so they stay
42
+ // state; the guard does not, so it is a ref — that keeps it out of the
43
+ // dependency array and stops the effect re-running just to re-read it.
44
+ const promotingRef = useRef(false)
51
45
 
52
- // If the user is signed in and this is a fresh project, try to promote them
53
46
  useEffect(() => {
54
- if (session?.user && isBootstrapped === false && !promoting) {
55
- setPromoting(true)
56
- setNavigating(true)
57
- promoteFirstAdmin()
58
- .then((result) => {
59
- if (result.promoted) {
60
- router.push("/admin")
61
- } else {
62
- setNavigating(false)
63
- }
64
- })
65
- .catch(() => {
66
- setPromoting(false)
67
- setNavigating(false)
68
- })
69
- }
70
- }, [session, isBootstrapped, promoting, promoteFirstAdmin, router])
47
+ if (!session?.user || isBootstrapped !== false || promotingRef.current) {return}
48
+
49
+ promotingRef.current = true
50
+ setPromoting(true)
51
+ setNavigating(true)
52
+
53
+ promoteFirstAdmin()
54
+ .then((result) => {
55
+ if (result.promoted) {
56
+ router.push("/admin")
57
+ return
58
+ }
59
+ promotingRef.current = false
60
+ setPromoting(false)
61
+ setNavigating(false)
62
+ })
63
+ .catch(() => {
64
+ promotingRef.current = false
65
+ setPromoting(false)
66
+ setNavigating(false)
67
+ })
68
+ }, [session, isBootstrapped, promoteFirstAdmin, router])
71
69
 
72
70
  // Treat undefined (query still loading) as not bootstrapped — show the welcome page immediately
73
71
  const bootstrapped = isBootstrapped === true
@@ -93,17 +91,17 @@ export function WelcomePage() {
93
91
 
94
92
  <div className="flex gap-4">
95
93
  {session?.user ? (
96
- <button onClick={() => handleNavigate("/admin")} disabled={navigating} className={buttonClass}>
94
+ <button className={buttonClass} disabled={navigating} onClick={() => handleNavigate("/admin")}>
97
95
  {navigating && <LoadingSpinner />}
98
96
  {navigating ? "Loading..." : "Go to Admin Panel"}
99
97
  </button>
100
98
  ) : bootstrapped ? (
101
- <button onClick={() => handleNavigate("/auth/sign-in")} disabled={navigating} className={buttonClass}>
99
+ <button className={buttonClass} disabled={navigating} onClick={() => handleNavigate("/auth/sign-in")}>
102
100
  {navigating && <LoadingSpinner />}
103
101
  {navigating ? "Loading..." : "Sign In"}
104
102
  </button>
105
103
  ) : (
106
- <button onClick={() => handleNavigate("/auth/sign-up")} disabled={navigating} className={buttonClass}>
104
+ <button className={buttonClass} disabled={navigating} onClick={() => handleNavigate("/auth/sign-up")}>
107
105
  {navigating && <LoadingSpinner />}
108
106
  {navigating ? "Loading..." : "Create Admin Account"}
109
107
  </button>
@@ -114,3 +112,16 @@ export function WelcomePage() {
114
112
  </div>
115
113
  )
116
114
  }
115
+
116
+ function LoadingSpinner() {
117
+ return (
118
+ <svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
119
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
120
+ <path
121
+ className="opacity-75"
122
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
123
+ fill="currentColor"
124
+ />
125
+ </svg>
126
+ )
127
+ }
@@ -13,7 +13,7 @@ export const users = defineCollection({
13
13
  plural: "Users",
14
14
  },
15
15
  fields: {
16
- name: text({ required: true }),
16
+ name: text({ label: "Name", required: true }),
17
17
  // Add custom user fields here — the auth adapter already contributes
18
18
  // name/email/roles/etc. from Better Auth's schema.
19
19
  },
@@ -5,14 +5,14 @@ import { TABLE_SLUG_ACCOUNTS, TABLE_SLUG_USERS } from "~/db/constants"
5
5
 
6
6
  import {
7
7
  apikey,
8
+ footers,
9
+ headers,
8
10
  images,
9
11
  jwks,
10
- session,
11
- verification,
12
12
  pages,
13
- headers,
14
- footers,
13
+ session,
15
14
  themes,
15
+ verification,
16
16
  } from "./vex.schema"
17
17
 
18
18
  export default defineSchema({