create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (115) hide show
  1. package/README.md +48 -14
  2. package/dist/index.js +1714 -1504
  3. package/package.json +2 -2
  4. package/templates/base-nextjs/README.md +18 -5
  5. package/templates/base-nextjs/convex/auth/index.ts +2 -2
  6. package/templates/base-nextjs/convex/auth/options.ts +27 -26
  7. package/templates/base-nextjs/convex/auth/schema.ts +51 -0
  8. package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
  9. package/templates/base-nextjs/convex/tsconfig.json +2 -2
  10. package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
  11. package/templates/base-nextjs/convex/vex/globals.ts +3 -2
  12. package/templates/base-nextjs/convex/vex/media.ts +3 -2
  13. package/templates/base-nextjs/convex/vex.schema.ts +1 -0
  14. package/templates/base-nextjs/convex/vex.ts +17 -4
  15. package/templates/base-nextjs/next.config.ts +52 -6
  16. package/templates/base-nextjs/package.json +18 -7
  17. package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
  18. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
  19. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +2 -2
  20. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
  21. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +8 -6
  22. package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +19 -0
  23. package/templates/base-nextjs/src/app/globals.css +10 -9
  24. package/templates/base-nextjs/src/app/layout.tsx +6 -0
  25. package/templates/base-nextjs/src/auth/access.ts +14 -2
  26. package/templates/base-nextjs/src/auth/serverUtils.ts +20 -6
  27. package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
  28. package/templates/base-nextjs/src/components/component-example.tsx +1 -1
  29. package/templates/base-nextjs/src/components/providers/server.tsx +16 -8
  30. package/templates/base-nextjs/src/vex.config.server.ts +30 -0
  31. package/templates/base-nextjs/src/vex.config.ts +14 -11
  32. package/templates/base-nextjs/src/vex.types.ts +2 -1
  33. package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
  34. package/templates/base-nextjs/src/vexcms/collections/users.ts +3 -1
  35. package/templates/marketing-site/convex/pages.ts +20 -1
  36. package/templates/marketing-site/convex/schema.ts +4 -4
  37. package/templates/marketing-site/convex/seed.ts +515 -70
  38. package/templates/marketing-site/convex/siteSettings.ts +1 -1
  39. package/templates/marketing-site/convex/theme.ts +3 -3
  40. package/templates/marketing-site/convex/vex.schema.ts +75 -38
  41. package/templates/marketing-site/public/favicon.svg +14 -0
  42. package/templates/marketing-site/public/favicons/apple-touch-icon.png +0 -0
  43. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  44. package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +64 -0
  45. package/templates/marketing-site/src/app/(frontend)/(site)/SiteLivePreviewProvider.tsx +31 -0
  46. package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +63 -0
  47. package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +50 -0
  48. package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +33 -0
  49. package/templates/marketing-site/src/app/(frontend)/layout.tsx +10 -24
  50. package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +32 -0
  51. package/templates/marketing-site/src/app/globals.css +413 -0
  52. package/templates/marketing-site/src/app/layout.tsx +85 -0
  53. package/templates/marketing-site/src/app/robots.ts +13 -0
  54. package/templates/marketing-site/src/app/sitemap.ts +18 -0
  55. package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
  56. package/templates/marketing-site/src/components/CodeHighlightContext.tsx +76 -0
  57. package/templates/marketing-site/src/components/CodePane.tsx +98 -0
  58. package/templates/marketing-site/src/components/Container.tsx +25 -0
  59. package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
  60. package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +54 -0
  61. package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
  62. package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
  63. package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
  64. package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
  65. package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
  66. package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
  67. package/templates/marketing-site/src/components/ThemeStyle.tsx +3 -3
  68. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
  69. package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
  70. package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +47 -266
  71. package/templates/marketing-site/src/lib/codeHighlightKey.ts +27 -0
  72. package/templates/marketing-site/src/lib/highlight.ts +134 -0
  73. package/templates/marketing-site/src/lib/highlightClient.ts +37 -0
  74. package/templates/marketing-site/src/lib/metadata.ts +61 -33
  75. package/templates/marketing-site/src/lib/resolvePagePath.ts +8 -0
  76. package/templates/marketing-site/src/lib/vex.ts +18 -0
  77. package/templates/marketing-site/src/proxy.ts +179 -0
  78. package/templates/marketing-site/src/vex.config.server.ts +27 -0
  79. package/templates/marketing-site/src/vex.config.ts +27 -9
  80. package/templates/marketing-site/src/vex.types.ts +102 -76
  81. package/templates/marketing-site/src/vexcms/api.ts +2 -2
  82. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -2
  83. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
  84. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
  85. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
  86. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -3
  87. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
  88. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
  89. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
  90. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +6 -3
  91. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
  92. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +10 -3
  93. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
  94. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +27 -12
  95. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +174 -119
  96. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +2 -1
  97. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
  98. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
  99. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +256 -73
  100. package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
  101. package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
  102. package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
  103. package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
  104. package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
  105. package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
  106. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
  107. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
  108. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
  109. package/templates/marketing-site/src/vexcms/collections/pages.ts +23 -1
  110. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
  111. package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
  112. package/dist/index.mjs +0 -30976
  113. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +0 -53
  114. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +0 -25
  115. package/templates/marketing-site/src/app/(frontend)/page.tsx +0 -21
@@ -7,7 +7,19 @@ import {
7
7
  TABLE_SLUG_PAGES,
8
8
  TABLE_SLUG_THEMES,
9
9
  } from "~/db/constants"
10
- import config from "~/vex.config"
10
+ import config from "~/vex.config.server"
11
+ import {
12
+ BLOCK_SLUG_CODE_SHOWCASE,
13
+ BLOCK_SLUG_CTA,
14
+ BLOCK_SLUG_FAQ,
15
+ BLOCK_SLUG_FEATURES,
16
+ BLOCK_SLUG_FOOTER,
17
+ BLOCK_SLUG_HEADER,
18
+ BLOCK_SLUG_HERO,
19
+ BLOCK_SLUG_HOW_IT_WORKS,
20
+ BLOCK_SLUG_ROADMAP,
21
+ BLOCK_SLUG_STATS,
22
+ } from "~/vexcms/blocks/constants"
11
23
 
12
24
  import { internalMutation } from "./_generated/server"
13
25
 
@@ -330,7 +342,7 @@ export const init = internalMutation({
330
342
  const created: string[] = []
331
343
  const skipped: string[] = []
332
344
 
333
- let activeThemeId: string | null = null
345
+ let activeThemeId: null | string = null
334
346
  for (const preset of THEME_PRESETS) {
335
347
  const existing = await ctx.db
336
348
  .query(TABLE_SLUG_THEMES)
@@ -338,12 +350,12 @@ export const init = internalMutation({
338
350
  .first()
339
351
  if (existing) {
340
352
  skipped.push(`theme:${preset.name}`)
341
- if (preset.name === "Stark × Ember") activeThemeId = existing._id
353
+ if (preset.name === "Stark × Ember") {activeThemeId = existing._id}
342
354
  continue
343
355
  }
344
356
  const id = await ctx.db.insert(TABLE_SLUG_THEMES, preset)
345
357
  created.push(`theme:${preset.name}`)
346
- if (preset.name === "Stark × Ember") activeThemeId = id
358
+ if (preset.name === "Stark × Ember") {activeThemeId = id}
347
359
  }
348
360
 
349
361
  const existingSettings = await getGlobal({
@@ -360,8 +372,8 @@ export const init = internalMutation({
360
372
  config,
361
373
  slug: GLOBAL_SLUG_SITE_SETTINGS,
362
374
  data: {
363
- name: "My Site",
364
- description: "Built with Vex CMS.",
375
+ name: "VexCMS",
376
+ description: "A headless CMS built natively on Convex.",
365
377
  activeTheme: activeThemeId ? [activeThemeId] : [],
366
378
  },
367
379
  access: { bypass: true },
@@ -380,19 +392,26 @@ export const init = internalMutation({
380
392
  name: "Main Header",
381
393
  content: [
382
394
  {
383
- blockType: "header",
395
+ blockType: BLOCK_SLUG_HEADER,
384
396
  blockName: "Site Header",
385
397
  id: "main-header",
386
- logoText: "My Site",
398
+ logoText: "VexCMS",
387
399
  logoHref: "/",
388
400
  menuItems: [
389
401
  { label: "Features", href: "/features" },
390
402
  { label: "Roadmap", href: "/roadmap" },
391
- { label: "Docs", href: "/docs" },
403
+ { label: "Docs", href: "https://docs.vexcms.dev" },
404
+ // A scaffolded project has no always-visible admin affordance in
405
+ // the header (`Header/index.tsx` still gates on
406
+ // `useCanAccessAdminPanel()`), so this nav item is the owner's
407
+ // route to the panel. `/admin` is gated server-side, so linking
408
+ // it publicly exposes nothing. `apps/www` drops this entry
409
+ // because its header renders a button unconditionally.
410
+ { label: "Admin", href: "/admin" },
392
411
  ],
393
412
  actionButtons: [
394
- { label: "GitHub", href: "https://github.com/vexcms/vex", variant: ["ghost"] },
395
- { label: "Get Started", href: "/docs", variant: ["default"] },
413
+ { label: "GitHub", href: "https://github.com/ianyimi/vex", variant: ["ghost"] },
414
+ { label: "Get started", href: "https://docs.vexcms.dev/guides/quickstart/", variant: ["default"] },
396
415
  ],
397
416
  },
398
417
  ],
@@ -411,17 +430,22 @@ export const init = internalMutation({
411
430
  name: "Main Footer",
412
431
  content: [
413
432
  {
414
- blockType: "footer",
433
+ blockType: BLOCK_SLUG_FOOTER,
415
434
  blockName: "Site Footer",
416
435
  id: "main-footer",
417
- logoText: "My Site",
418
- copyright: "My Site. All rights reserved.",
436
+ logoText: "VexCMS",
437
+ copyright: "VexCMS. Apache-2.0 licensed.",
419
438
  links: [
420
439
  { label: "Features", href: "/features" },
421
440
  { label: "Roadmap", href: "/roadmap" },
422
- { label: "Documentation", href: "/docs" },
441
+ { label: "Docs", href: "https://docs.vexcms.dev" },
442
+ { label: "Quickstart", href: "https://docs.vexcms.dev/guides/quickstart/" },
443
+ { label: "Admin demo", href: "/admin" },
444
+ { label: "npm", href: "https://www.npmjs.com/package/@vexcms/core" },
445
+ { label: "Licence", href: "https://github.com/ianyimi/vex/blob/master/LICENSE" },
446
+ { label: "Convex", href: "https://convex.dev" },
423
447
  ],
424
- socialLinks: [{ platform: "GitHub", href: "https://github.com/vexcms/vex", icon: "Github" }],
448
+ socialLinks: [{ platform: "GitHub", href: "https://github.com/ianyimi/vex", icon: "Github" }],
425
449
  },
426
450
  ],
427
451
  })
@@ -440,87 +464,202 @@ export const init = internalMutation({
440
464
  slug: "home",
441
465
  blocks: [
442
466
  {
443
- blockType: "hero",
467
+ blockType: BLOCK_SLUG_HERO,
444
468
  blockName: "Hero",
445
469
  id: "home-hero",
446
- badgeText: "Now in public beta",
447
- badgeLink: "/docs",
448
- heading: "The CMS built for Convex",
470
+ variant: ["full"],
471
+ installCommand: "pnpm create vexcms@alpha",
472
+ badgeText: "v0.1.0-alpha — now on npm",
473
+ badgeLink: "https://www.npmjs.com/package/@vexcms/core",
474
+ heading: "The CMS that thinks in types.",
449
475
  subheading:
450
- "Vex CMS gives you a full-featured content management system powered by Convex — real-time data, type-safe schemas, and a beautiful admin panel out of the box.",
451
- primaryCtaLabel: "Get Started",
452
- primaryCtaHref: "/docs",
476
+ "A headless CMS built natively on Convex. Declare your collections in TypeScript and Vex generates the Convex schema, the types, and the queries — no translation layer. Every edit reaches every subscriber in milliseconds.",
477
+ primaryCtaLabel: "Read the docs",
478
+ primaryCtaHref: "https://docs.vexcms.dev/guides/quickstart/",
453
479
  secondaryCtaLabel: "View on GitHub",
454
- secondaryCtaHref: "https://github.com/vexcms/vex",
480
+ secondaryCtaHref: "https://github.com/ianyimi/vex",
455
481
  },
456
482
  {
457
- blockType: "features",
483
+ blockType: BLOCK_SLUG_STATS,
484
+ blockName: "Stats",
485
+ id: "home-stats",
486
+ items: [
487
+ {
488
+ value: "12",
489
+ label: "field types",
490
+ description:
491
+ "text, url, number, checkbox, select, date, color, upload, relationship, group, array, blocks",
492
+ },
493
+ {
494
+ value: "8",
495
+ label: "published packages",
496
+ description:
497
+ "core, react, next, cli, better-auth, file-storage-convex, richtext-plate, create-vexcms",
498
+ },
499
+ {
500
+ value: "0",
501
+ label: "database config",
502
+ description: "Convex is the database. There is no connection string",
503
+ },
504
+ {
505
+ value: "1",
506
+ label: "command to start",
507
+ description:
508
+ "pnpm create vexcms@alpha scaffolds Next.js, Convex, auth, and the admin panel",
509
+ },
510
+ ],
511
+ },
512
+ {
513
+ blockType: BLOCK_SLUG_FEATURES,
458
514
  blockName: "Features",
459
515
  id: "home-features",
460
- heading: "Everything you need to manage content",
516
+ heading: "Everything comes from one schema.",
461
517
  subheading:
462
- "Built on Convex's real-time infrastructure with a developer experience that doesn't compromise on power.",
518
+ "Collections, the Convex tables behind them, the TypeScript types, the Zod validators, the admin forms, and the access rules are all derived from the same declaration.",
463
519
  features: [
464
520
  {
465
- title: "Real-Time by Default",
521
+ title: "Convex-native codegen",
466
522
  description:
467
- "Every query is live. Content updates appear instantly across all connected clients — no polling, no webhooks.",
468
- icon: "Zap",
523
+ "vex dev writes your Convex schema, TypeScript interfaces, and Zod validators straight from defineCollection(). There is no hand-maintained schema.ts",
524
+ icon: "Wand",
469
525
  },
470
526
  {
471
- title: "Type-Safe Schemas",
527
+ title: "End-to-end types",
472
528
  description:
473
- "Define your collections with TypeScript. Vex generates Convex schemas, Zod validators, and typed queries automatically.",
474
- icon: "Shield",
529
+ "Fields, relationships, and query return types are checked from the database to the component. Rename a field and the compiler names every call site",
530
+ icon: "ShieldCheck",
475
531
  },
476
532
  {
477
- title: "Developer First",
533
+ title: "Real-time admin panel",
478
534
  description:
479
- "Code-first configuration, CLI tooling, and a clean API. Build with the tools you already know and love.",
480
- icon: "Code",
535
+ "Every list view is a Convex subscription. Pagination, live totalDocs, and bulk operations update without a refetch",
536
+ icon: "Radio",
537
+ },
538
+ {
539
+ title: "RBAC with indexed access",
540
+ description:
541
+ "Document-level rules with { constraints } that compile to withIndex ranges inside the query, plus per-call access.action / access.bypass and an anonRole fallback",
542
+ icon: "Lock",
543
+ },
544
+ {
545
+ title: "Globals and themes",
546
+ description:
547
+ "defineGlobal() gives you singletons like site settings. Themes are a collection — 32 shadcn tokens × light and dark, stored as oklch and applied on first paint",
548
+ icon: "Boxes",
549
+ },
550
+ {
551
+ title: "Page-builder blocks",
552
+ description:
553
+ "A blocks field composes typed content blocks into a discriminated union. Each block is a config plus a React renderer, colocated",
554
+ icon: "LayoutGrid",
481
555
  },
482
556
  ],
483
557
  },
484
558
  {
485
- blockType: "how_it_works",
559
+ blockType: BLOCK_SLUG_CODE_SHOWCASE,
560
+ blockName: "Code Showcase",
561
+ id: "home-code-showcase",
562
+ heading: "Write the collection. Get the database.",
563
+ subheading:
564
+ "A blog's Posts collection on the left, hand-written once. On the right, the Convex schema vex dev emits from it — validators, ids, and indexes included, never edited by hand.",
565
+ panes: [
566
+ {
567
+ label: "You write",
568
+ filename: "src/vexcms/collections/posts.ts",
569
+ language: ["ts"],
570
+ authored: ["authored"],
571
+ code: `export const posts = defineCollection({
572
+ slug: "posts",
573
+ admin: { useAsTitle: "title", icon: "Newspaper" },
574
+ fields: {
575
+ title: text({ label: "Title", required: true }),
576
+ slug: text({ label: "Slug", required: true, index: "by_slug" }),
577
+ excerpt: text({ label: "Excerpt" }),
578
+ featured: checkbox({ label: "Featured" }),
579
+ readingMinutes: number({ label: "Reading Minutes" }),
580
+ coverImage: upload({ to: "images", label: "Cover Image" }),
581
+ author: relationship({ label: "Author", collection: { slug: "authors" } }),
582
+ tags: array({ label: "Tags", items: text({ label: "Tag" }) }),
583
+ seo: group({
584
+ label: "SEO",
585
+ fields: {
586
+ metaTitle: text({ label: "Meta Title" }),
587
+ metaDescription: text({ label: "Meta Description" }),
588
+ },
589
+ }),
590
+ },
591
+ labels: { singular: "Post", plural: "Posts" },
592
+ })`,
593
+ },
594
+ {
595
+ label: "Vex generates",
596
+ filename: "convex/vex.schema.ts",
597
+ language: ["ts"],
598
+ authored: ["generated"],
599
+ code: `// ⚠️ AUTO-GENERATED BY VEX CMS — DO NOT EDIT ⚠️
600
+ // Run 'vex dev' or 'vex generate' to update this file.
601
+
602
+ import { defineTable } from "convex/server"
603
+ import { v } from "convex/values"
604
+
605
+ export const posts = defineTable({
606
+ title: v.string(),
607
+ slug: v.string(),
608
+ excerpt: v.optional(v.string()),
609
+ featured: v.optional(v.boolean()),
610
+ readingMinutes: v.optional(v.number()),
611
+ coverImage: v.optional(v.array(v.id("images"))),
612
+ author: v.optional(v.array(v.id("authors"))),
613
+ tags: v.optional(v.array(v.string())),
614
+ seo: v.optional(
615
+ v.object({ metaTitle: v.optional(v.string()), metaDescription: v.optional(v.string()) })
616
+ ),
617
+ })
618
+ .index("by_slug", ["slug"])
619
+ .index("by_author", ["author"])`,
620
+ },
621
+ ],
622
+ },
623
+ {
624
+ blockType: BLOCK_SLUG_HOW_IT_WORKS,
486
625
  blockName: "How It Works",
487
626
  id: "home-how-it-works",
488
- heading: "Get started in minutes",
489
- subheading: "From zero to a fully functional CMS in four steps. No boilerplate, no config files to wrestle with.",
627
+ heading: "From zero to a live CMS in four steps.",
628
+ subheading: "No boilerplate and no config files to wrestle with — the scaffolder wires Convex, auth, and the admin panel for you.",
490
629
  steps: [
491
630
  {
492
631
  icon: "Terminal",
493
632
  title: "Scaffold your project",
494
633
  description:
495
- "Run npx create-vexcms@latest to get a Next.js app with Convex, authentication, and the admin panel pre-configured.",
634
+ "pnpm create vexcms@alpha gives you a Next.js app with Convex, Better Auth, and the admin panel already wired",
496
635
  },
497
636
  {
498
637
  icon: "Code",
499
638
  title: "Define your schema",
500
639
  description:
501
- "Use defineCollection() and field helpers to declare your content model in TypeScript. Vex generates your Convex schema, types, and queries automatically.",
640
+ "Declare collections with defineCollection() and the field helpers. vex dev watches and regenerates your Convex schema, types, and validators",
502
641
  },
503
642
  {
504
643
  icon: "LayoutGrid",
505
644
  title: "Build with blocks",
506
645
  description:
507
- "Compose pages from reusable content blocks. Each block is a React component with a typed config — drag, drop, and edit inline from the admin panel.",
646
+ "Compose pages from typed content blocks. Each block is a config plus a renderer, edited from the admin panel",
508
647
  },
509
648
  {
510
649
  icon: "Rocket",
511
650
  title: "Deploy and go live",
512
651
  description:
513
- "Push to Convex and deploy your Next.js app. Real-time content updates flow to every connected client instantly.",
652
+ "Push to Convex, deploy the Next.js app. Content changes propagate to every subscriber with no cache invalidation",
514
653
  },
515
654
  ],
516
655
  },
517
656
  {
518
- blockType: "roadmap",
657
+ blockType: BLOCK_SLUG_ROADMAP,
519
658
  blockName: "Roadmap",
520
659
  id: "home-roadmap",
521
660
  heading: "Roadmap",
522
661
  subheading:
523
- "What we've shipped and what's coming next. Vex CMS is actively developed — here's where we're headed.",
662
+ "What we've shipped and what's coming next. VexCMS is actively developed and everything ships as 0.1.0-alpha.",
524
663
  items: [
525
664
  {
526
665
  feature: "12 Field Types",
@@ -571,18 +710,30 @@ export const init = internalMutation({
571
710
  },
572
711
  {
573
712
  feature: "Versioning & Drafts",
574
- description: "Draft/publish workflow with live preview — in active development.",
575
- status: ["coming-soon"],
713
+ description: "Draft/publish workflow with autosave and version history — in active development.",
714
+ status: ["in-progress"],
576
715
  },
577
716
  {
578
- feature: "Richtext, JSON, Email & Textarea Fields",
579
- description: "Plate.js-powered rich text, plus structured JSON, email, and multi-line text inputs.",
717
+ feature: "Live Preview",
718
+ description:
719
+ "Side-by-side preview of draft content against the real frontend before publishing — builds on the drafts infrastructure.",
720
+ status: ["in-progress"],
721
+ },
722
+ {
723
+ feature: "Form Builder",
724
+ description:
725
+ "Composable form fields beyond content editing, with generated validation and submission storage.",
580
726
  status: ["planned"],
581
727
  },
582
728
  {
583
- feature: "Form Builder & Lifecycle Hooks",
729
+ feature: "Field Input Consistency Pass",
584
730
  description:
585
- "Composable form fields beyond content editing, plus beforeChange/afterChange hooks for custom side effects.",
731
+ "Touch-ups across field inputs — starting with the relationship field — for consistent interaction patterns in the admin panel.",
732
+ status: ["planned"],
733
+ },
734
+ {
735
+ feature: "Richtext, JSON, Email & Textarea Fields",
736
+ description: "Plate.js-powered rich text, plus structured JSON, email, and multi-line text inputs.",
586
737
  status: ["planned"],
587
738
  },
588
739
  {
@@ -590,52 +741,85 @@ export const init = internalMutation({
590
741
  description: "Invite users, assign roles, and issue scoped read-only API tokens for external integrations.",
591
742
  status: ["planned"],
592
743
  },
744
+ {
745
+ feature: "Lifecycle Hooks",
746
+ description:
747
+ "beforeChange/afterChange hooks for custom side effects around document writes.",
748
+ status: ["future"],
749
+ },
750
+ {
751
+ feature: "Analytics Adapter",
752
+ description:
753
+ "Per-document and per-block metrics surfaced directly in the admin panel.",
754
+ status: ["future"],
755
+ },
756
+ {
757
+ feature: "Multi-Component Workspaces",
758
+ description: "Compose several Convex components into one workspace.",
759
+ status: ["future"],
760
+ },
761
+ {
762
+ feature: "Plugin System",
763
+ description: "Third-party extensions over collections, fields, and admin views.",
764
+ status: ["future"],
765
+ },
766
+ {
767
+ feature: "React Package Testing Suite",
768
+ description:
769
+ "Exportable Vitest suite from @vexcms/react for testing custom field components and admin extensions in consumer projects.",
770
+ status: ["planned"],
771
+ },
593
772
  ],
594
773
  },
595
774
  {
596
- blockType: "faq",
775
+ blockType: BLOCK_SLUG_FAQ,
597
776
  blockName: "FAQ",
598
777
  id: "home-faq",
599
- heading: "Frequently Asked Questions",
600
- subheading: "Everything you need to know about Vex CMS and building with Convex.",
601
- supportLink: "https://github.com/vexcms/vex/issues",
778
+ heading: "Questions we keep getting.",
779
+ subheading: "",
780
+ supportLink: "https://github.com/ianyimi/vex/issues",
602
781
  items: [
603
782
  {
604
- question: "What is Vex CMS?",
783
+ question: "What is VexCMS?",
784
+ answer:
785
+ "A headless CMS built on Convex. You define collections in TypeScript; Vex generates the Convex schema, the TypeScript types, and the queries, and gives you a real-time admin panel over them.",
786
+ },
787
+ {
788
+ question: "How is this different from other headless CMS platforms?",
605
789
  answer:
606
- "Vex CMS is a headless content management system built on Convex. It provides real-time data, type-safe schemas, RBAC, and a beautiful admin panel — all configured with TypeScript.",
790
+ "Most of them put a REST or GraphQL API in front of a database you configure separately. Vex has no API layer and no database configuration: Convex is the database, your schema is code, and every read is a live subscription.",
607
791
  },
608
792
  {
609
- question: "How is Vex different from other headless CMS platforms?",
793
+ question: "Do I need to know Convex?",
610
794
  answer:
611
- "Vex is powered by Convex's real-time database, so content updates are instant across all clients — no polling, no webhooks. Your schema is defined in code, giving you full type safety from database to frontend.",
795
+ "Some familiarity helps, but the CLI generates the schema, queries, and types from your collection definitions. You mostly write field declarations.",
612
796
  },
613
797
  {
614
- question: "Do I need to know Convex to use Vex CMS?",
798
+ question: "Is it production ready?",
615
799
  answer:
616
- "Basic familiarity helps, but Vex handles most of the complexity for you. The CLI generates your Convex schema, queries, and types automatically from your collection definitions.",
800
+ "No. Everything ships as 0.1.0-alpha. Versioning, drafts, and live preview are in progress. There are 12 field types today; richtext, json, tabs, and ui are planned, not built. See the roadmap.",
617
801
  },
618
802
  {
619
- question: "Is Vex CMS free?",
803
+ question: "Which frameworks does it support?",
620
804
  answer:
621
- "Yes — Vex CMS is open source and free to use. You only pay for your Convex usage, which has a generous free tier for most projects.",
805
+ "The data layer works with any Convex client. The admin panel is Next.js today, via @vexcms/next and @vexcms/react. A TanStack Start adapter is planned.",
622
806
  },
623
807
  {
624
- question: "How do I get started?",
808
+ question: "What does it cost?",
625
809
  answer:
626
- "Run npx create-vexcms@latest to scaffold a new project with Vex CMS pre-configured — Next.js app, Convex backend, authentication, and admin panel in under a minute.",
810
+ "Nothing. VexCMS is Apache-2.0 licensed. You pay for your Convex usage, which has a free tier.",
627
811
  },
628
812
  ],
629
813
  },
630
814
  {
631
- blockType: "cta",
815
+ blockType: BLOCK_SLUG_CTA,
632
816
  blockName: "CTA",
633
817
  id: "home-cta",
634
- heading: "Ready to build with Vex CMS?",
635
- subheading: "Get started in minutes with create-vexcms. Real-time content management powered by Convex.",
818
+ heading: "Start with a schema. Ship in an hour.",
819
+ subheading: "Scaffold a Next.js + Convex project with authentication, the admin panel, and this marketing site already seeded.",
636
820
  actions: [
637
- { label: "Get Started", href: "/docs" },
638
- { label: "View on GitHub", href: "https://github.com/vexcms/vex" },
821
+ { label: "Read the docs", href: "https://docs.vexcms.dev/guides/quickstart/" },
822
+ { label: "View on GitHub", href: "https://github.com/ianyimi/vex" },
639
823
  ],
640
824
  },
641
825
  ],
@@ -643,6 +827,267 @@ export const init = internalMutation({
643
827
  created.push("page:home")
644
828
  }
645
829
 
830
+ const existingFeatures = await ctx.db
831
+ .query(TABLE_SLUG_PAGES)
832
+ .withIndex("by_slug", (q) => q.eq("slug", "features"))
833
+ .first()
834
+ if (existingFeatures) {
835
+ skipped.push("page:features")
836
+ } else {
837
+ await ctx.db.insert(TABLE_SLUG_PAGES, {
838
+ title: "Features",
839
+ slug: "features",
840
+ blocks: [
841
+ {
842
+ blockType: BLOCK_SLUG_HERO,
843
+ blockName: "Hero",
844
+ id: "features-hero",
845
+ variant: ["compact"],
846
+ heading: "Everything comes from one schema.",
847
+ subheading:
848
+ "Collections, the Convex tables behind them, the TypeScript types, the Zod validators, the admin forms, and the access rules are all derived from the same declaration.",
849
+ primaryCtaLabel: "Read the docs",
850
+ primaryCtaHref: "https://docs.vexcms.dev/guides/quickstart/",
851
+ },
852
+ {
853
+ blockType: BLOCK_SLUG_FEATURES,
854
+ blockName: "Features",
855
+ id: "features-features",
856
+ heading: "Everything you need to manage content",
857
+ subheading:
858
+ "Built on Convex's real-time infrastructure with a developer experience that doesn't compromise on power.",
859
+ features: [
860
+ {
861
+ title: "Real-Time by Default",
862
+ description:
863
+ "Every query is live. Content updates appear instantly across all connected clients — no polling, no webhooks.",
864
+ icon: "Zap",
865
+ },
866
+ {
867
+ title: "Type-Safe Schemas",
868
+ description:
869
+ "Define your collections with TypeScript. Vex generates Convex schemas, Zod validators, and typed queries automatically.",
870
+ icon: "Shield",
871
+ },
872
+ {
873
+ title: "Developer First",
874
+ description:
875
+ "Code-first configuration, CLI tooling, and a clean API. Build with the tools you already know and love.",
876
+ icon: "Code",
877
+ },
878
+ ],
879
+ },
880
+ {
881
+ blockType: BLOCK_SLUG_HOW_IT_WORKS,
882
+ blockName: "How It Works",
883
+ id: "features-how-it-works",
884
+ heading: "Get started in minutes",
885
+ subheading:
886
+ "From zero to a fully functional CMS in four steps. No boilerplate, no config files to wrestle with.",
887
+ steps: [
888
+ {
889
+ icon: "Terminal",
890
+ title: "Scaffold your project",
891
+ description:
892
+ "pnpm create vexcms@alpha gives you a Next.js app with Convex, Better Auth, and the admin panel already wired",
893
+ },
894
+ {
895
+ icon: "Code",
896
+ title: "Define your schema",
897
+ description:
898
+ "Use defineCollection() and field helpers to declare your content model in TypeScript. Vex generates your Convex schema, types, and queries automatically.",
899
+ },
900
+ {
901
+ icon: "LayoutGrid",
902
+ title: "Build with blocks",
903
+ description:
904
+ "Compose pages from typed content blocks. Each block is a config plus a renderer, edited from the admin panel",
905
+ },
906
+ {
907
+ icon: "Rocket",
908
+ title: "Deploy and go live",
909
+ description:
910
+ "Push to Convex and deploy your Next.js app. Real-time content updates flow to every connected client instantly — no cache invalidation needed.",
911
+ },
912
+ ],
913
+ },
914
+ {
915
+ blockType: BLOCK_SLUG_CTA,
916
+ blockName: "CTA",
917
+ id: "features-cta",
918
+ heading: "Start with a schema. Ship in an hour.",
919
+ subheading:
920
+ "Scaffold a Next.js + Convex project with authentication, the admin panel, and this marketing site already seeded.",
921
+ actions: [
922
+ { label: "Read the docs", href: "https://docs.vexcms.dev/guides/quickstart/" },
923
+ { label: "View on GitHub", href: "https://github.com/ianyimi/vex" },
924
+ ],
925
+ },
926
+ ],
927
+ })
928
+ created.push("page:features")
929
+ }
930
+
931
+ const existingRoadmapPage = await ctx.db
932
+ .query(TABLE_SLUG_PAGES)
933
+ .withIndex("by_slug", (q) => q.eq("slug", "roadmap"))
934
+ .first()
935
+ if (existingRoadmapPage) {
936
+ skipped.push("page:roadmap")
937
+ } else {
938
+ await ctx.db.insert(TABLE_SLUG_PAGES, {
939
+ title: "Roadmap",
940
+ slug: "roadmap",
941
+ blocks: [
942
+ {
943
+ blockType: BLOCK_SLUG_HERO,
944
+ blockName: "Hero",
945
+ id: "roadmap-hero",
946
+ variant: ["compact"],
947
+ heading: "Shipped, in progress, and being explored.",
948
+ subheading:
949
+ "VexCMS is under active development. Everything ships as 0.1.0-alpha until the core feature set is stable enough for a v0.1.0 release. This page is edited from the admin panel, not from a source file.",
950
+ primaryCtaLabel: "Read the docs",
951
+ primaryCtaHref: "https://docs.vexcms.dev/guides/quickstart/",
952
+ },
953
+ {
954
+ blockType: BLOCK_SLUG_ROADMAP,
955
+ blockName: "Roadmap",
956
+ id: "roadmap-roadmap",
957
+ heading: "Roadmap",
958
+ subheading:
959
+ "What we've shipped and what's coming next. VexCMS is actively developed and everything ships as 0.1.0-alpha.",
960
+ items: [
961
+ {
962
+ feature: "12 Field Types",
963
+ description:
964
+ "text, url, color, number, checkbox, date, select, relationship, array, group, blocks, and upload — no richtext, json, or tabs yet.",
965
+ status: ["shipped"],
966
+ },
967
+ {
968
+ feature: "Convex Schema Codegen",
969
+ description:
970
+ "vex dev / vex generate write your Convex schema, TypeScript types, and Zod validators from defineCollection() — no hand-written schema.ts.",
971
+ status: ["shipped"],
972
+ },
973
+ {
974
+ feature: "Real-Time Admin Panel",
975
+ description:
976
+ "DataTable with pagination, live totalDocs, and bulk operations — every list view is a Convex subscription.",
977
+ status: ["shipped"],
978
+ },
979
+ {
980
+ feature: "Media Library",
981
+ description:
982
+ "Convex file storage adapter with a searchable, paginated media picker built into every upload field.",
983
+ status: ["shipped"],
984
+ },
985
+ {
986
+ feature: "RBAC & Access Control",
987
+ description:
988
+ "Document-level access rules, indexed constraints that compile to withIndex ranges, per-call access.action/bypass overrides, and an anonRole fallback for public reads.",
989
+ status: ["shipped"],
990
+ },
991
+ {
992
+ feature: "Custom Theme System",
993
+ description:
994
+ "Database-driven themes with light/dark mode, 32 shadcn tokens per mode, and OKLCH color support — live-updates with zero page reload.",
995
+ status: ["shipped"],
996
+ },
997
+ {
998
+ feature: "Better Auth Integration",
999
+ description:
1000
+ "Email/password and OAuth out of the box, with organizations and API keys as opt-in plugins.",
1001
+ status: ["shipped"],
1002
+ },
1003
+ {
1004
+ feature: "CLI & Scaffolder",
1005
+ description:
1006
+ "vex dev, vex generate, and create-vexcms for instant project setup — bare or full marketing-site templates.",
1007
+ status: ["shipped"],
1008
+ },
1009
+ {
1010
+ feature: "Versioning & Drafts",
1011
+ description: "Draft/publish workflow with autosave and version history — in active development.",
1012
+ status: ["in-progress"],
1013
+ },
1014
+ {
1015
+ feature: "Live Preview",
1016
+ description:
1017
+ "Side-by-side preview of draft content against the real frontend before publishing — builds on the drafts infrastructure.",
1018
+ status: ["in-progress"],
1019
+ },
1020
+ {
1021
+ feature: "Form Builder",
1022
+ description:
1023
+ "Composable form fields beyond content editing, with generated validation and submission storage.",
1024
+ status: ["planned"],
1025
+ },
1026
+ {
1027
+ feature: "Field Input Consistency Pass",
1028
+ description:
1029
+ "Touch-ups across field inputs — starting with the relationship field — for consistent interaction patterns in the admin panel.",
1030
+ status: ["planned"],
1031
+ },
1032
+ {
1033
+ feature: "Richtext, JSON, Email & Textarea Fields",
1034
+ description:
1035
+ "Plate.js-powered rich text, plus structured JSON, email, and multi-line text inputs.",
1036
+ status: ["planned"],
1037
+ },
1038
+ {
1039
+ feature: "Team Management & API Keys",
1040
+ description:
1041
+ "Invite users, assign roles, and issue scoped read-only API tokens for external integrations.",
1042
+ status: ["planned"],
1043
+ },
1044
+ {
1045
+ feature: "Lifecycle Hooks",
1046
+ description:
1047
+ "beforeChange/afterChange hooks for custom side effects around document writes.",
1048
+ status: ["future"],
1049
+ },
1050
+ {
1051
+ feature: "Analytics Adapter",
1052
+ description:
1053
+ "Per-document and per-block metrics surfaced directly in the admin panel.",
1054
+ status: ["future"],
1055
+ },
1056
+ {
1057
+ feature: "Multi-Component Workspaces",
1058
+ description: "Compose several Convex components into one workspace.",
1059
+ status: ["future"],
1060
+ },
1061
+ {
1062
+ feature: "Plugin System",
1063
+ description: "Third-party extensions over collections, fields, and admin views.",
1064
+ status: ["future"],
1065
+ },
1066
+ {
1067
+ feature: "React Package Testing Suite",
1068
+ description:
1069
+ "Exportable Vitest suite from @vexcms/react for testing custom field components and admin extensions in consumer projects.",
1070
+ status: ["planned"],
1071
+ },
1072
+ ],
1073
+ },
1074
+ {
1075
+ blockType: BLOCK_SLUG_CTA,
1076
+ blockName: "CTA",
1077
+ id: "roadmap-cta",
1078
+ heading: "Start with a schema. Ship in an hour.",
1079
+ subheading:
1080
+ "Scaffold a Next.js + Convex project with authentication, the admin panel, and this marketing site already seeded.",
1081
+ actions: [
1082
+ { label: "Read the docs", href: "https://docs.vexcms.dev/guides/quickstart/" },
1083
+ { label: "View on GitHub", href: "https://github.com/ianyimi/vex" },
1084
+ ],
1085
+ },
1086
+ ],
1087
+ })
1088
+ created.push("page:roadmap")
1089
+ }
1090
+
646
1091
  return { created, skipped }
647
1092
  },
648
1093
  })