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.
- package/README.md +48 -14
- package/dist/index.js +1714 -1504
- package/package.json +2 -2
- package/templates/base-nextjs/README.md +18 -5
- package/templates/base-nextjs/convex/auth/index.ts +2 -2
- package/templates/base-nextjs/convex/auth/options.ts +27 -26
- package/templates/base-nextjs/convex/auth/schema.ts +51 -0
- package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
- package/templates/base-nextjs/convex/tsconfig.json +2 -2
- package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
- package/templates/base-nextjs/convex/vex/globals.ts +3 -2
- package/templates/base-nextjs/convex/vex/media.ts +3 -2
- package/templates/base-nextjs/convex/vex.schema.ts +1 -0
- package/templates/base-nextjs/convex/vex.ts +17 -4
- package/templates/base-nextjs/next.config.ts +52 -6
- package/templates/base-nextjs/package.json +18 -7
- package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
- package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +2 -2
- package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
- package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +8 -6
- package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +19 -0
- package/templates/base-nextjs/src/app/globals.css +10 -9
- package/templates/base-nextjs/src/app/layout.tsx +6 -0
- package/templates/base-nextjs/src/auth/access.ts +14 -2
- package/templates/base-nextjs/src/auth/serverUtils.ts +20 -6
- package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
- package/templates/base-nextjs/src/components/component-example.tsx +1 -1
- package/templates/base-nextjs/src/components/providers/server.tsx +16 -8
- package/templates/base-nextjs/src/vex.config.server.ts +30 -0
- package/templates/base-nextjs/src/vex.config.ts +14 -11
- package/templates/base-nextjs/src/vex.types.ts +2 -1
- package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
- package/templates/base-nextjs/src/vexcms/collections/users.ts +3 -1
- package/templates/marketing-site/convex/pages.ts +20 -1
- package/templates/marketing-site/convex/schema.ts +4 -4
- package/templates/marketing-site/convex/seed.ts +515 -70
- package/templates/marketing-site/convex/siteSettings.ts +1 -1
- package/templates/marketing-site/convex/theme.ts +3 -3
- package/templates/marketing-site/convex/vex.schema.ts +75 -38
- package/templates/marketing-site/public/favicon.svg +14 -0
- package/templates/marketing-site/public/favicons/apple-touch-icon.png +0 -0
- package/templates/marketing-site/public/favicons/favicon.ico +0 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +64 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/SiteLivePreviewProvider.tsx +31 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +63 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +50 -0
- package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +33 -0
- package/templates/marketing-site/src/app/(frontend)/layout.tsx +10 -24
- package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +32 -0
- package/templates/marketing-site/src/app/globals.css +413 -0
- package/templates/marketing-site/src/app/layout.tsx +85 -0
- package/templates/marketing-site/src/app/robots.ts +13 -0
- package/templates/marketing-site/src/app/sitemap.ts +18 -0
- package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
- package/templates/marketing-site/src/components/CodeHighlightContext.tsx +76 -0
- package/templates/marketing-site/src/components/CodePane.tsx +98 -0
- package/templates/marketing-site/src/components/Container.tsx +25 -0
- package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
- package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +54 -0
- package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
- package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
- package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
- package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
- package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
- package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
- package/templates/marketing-site/src/components/ThemeStyle.tsx +3 -3
- package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
- package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
- package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +47 -266
- package/templates/marketing-site/src/lib/codeHighlightKey.ts +27 -0
- package/templates/marketing-site/src/lib/highlight.ts +134 -0
- package/templates/marketing-site/src/lib/highlightClient.ts +37 -0
- package/templates/marketing-site/src/lib/metadata.ts +61 -33
- package/templates/marketing-site/src/lib/resolvePagePath.ts +8 -0
- package/templates/marketing-site/src/lib/vex.ts +18 -0
- package/templates/marketing-site/src/proxy.ts +179 -0
- package/templates/marketing-site/src/vex.config.server.ts +27 -0
- package/templates/marketing-site/src/vex.config.ts +27 -9
- package/templates/marketing-site/src/vex.types.ts +102 -76
- package/templates/marketing-site/src/vexcms/api.ts +2 -2
- package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -2
- package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
- package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
- package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
- package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -3
- package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
- package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
- package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
- package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +6 -3
- package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
- package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +10 -3
- package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
- package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +27 -12
- package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +174 -119
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +2 -1
- package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
- package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +256 -73
- package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
- package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
- package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
- package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
- package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
- package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
- package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
- package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
- package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
- package/templates/marketing-site/src/vexcms/collections/pages.ts +23 -1
- package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
- package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
- package/dist/index.mjs +0 -30976
- package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +0 -53
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +0 -25
- 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:
|
|
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: "
|
|
364
|
-
description: "
|
|
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:
|
|
395
|
+
blockType: BLOCK_SLUG_HEADER,
|
|
384
396
|
blockName: "Site Header",
|
|
385
397
|
id: "main-header",
|
|
386
|
-
logoText: "
|
|
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: "
|
|
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/
|
|
395
|
-
{ label: "Get
|
|
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:
|
|
433
|
+
blockType: BLOCK_SLUG_FOOTER,
|
|
415
434
|
blockName: "Site Footer",
|
|
416
435
|
id: "main-footer",
|
|
417
|
-
logoText: "
|
|
418
|
-
copyright: "
|
|
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: "
|
|
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/
|
|
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:
|
|
467
|
+
blockType: BLOCK_SLUG_HERO,
|
|
444
468
|
blockName: "Hero",
|
|
445
469
|
id: "home-hero",
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
"
|
|
451
|
-
primaryCtaLabel: "
|
|
452
|
-
primaryCtaHref: "/
|
|
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/
|
|
480
|
+
secondaryCtaHref: "https://github.com/ianyimi/vex",
|
|
455
481
|
},
|
|
456
482
|
{
|
|
457
|
-
blockType:
|
|
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
|
|
516
|
+
heading: "Everything comes from one schema.",
|
|
461
517
|
subheading:
|
|
462
|
-
"
|
|
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: "
|
|
521
|
+
title: "Convex-native codegen",
|
|
466
522
|
description:
|
|
467
|
-
"
|
|
468
|
-
icon: "
|
|
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: "
|
|
527
|
+
title: "End-to-end types",
|
|
472
528
|
description:
|
|
473
|
-
"
|
|
474
|
-
icon: "
|
|
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: "
|
|
533
|
+
title: "Real-time admin panel",
|
|
478
534
|
description:
|
|
479
|
-
"
|
|
480
|
-
icon: "
|
|
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:
|
|
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: "
|
|
489
|
-
subheading: "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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
|
|
575
|
-
status: ["
|
|
713
|
+
description: "Draft/publish workflow with autosave and version history — in active development.",
|
|
714
|
+
status: ["in-progress"],
|
|
576
715
|
},
|
|
577
716
|
{
|
|
578
|
-
feature: "
|
|
579
|
-
description:
|
|
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: "
|
|
729
|
+
feature: "Field Input Consistency Pass",
|
|
584
730
|
description:
|
|
585
|
-
"
|
|
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:
|
|
775
|
+
blockType: BLOCK_SLUG_FAQ,
|
|
597
776
|
blockName: "FAQ",
|
|
598
777
|
id: "home-faq",
|
|
599
|
-
heading: "
|
|
600
|
-
subheading: "
|
|
601
|
-
supportLink: "https://github.com/
|
|
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
|
|
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
|
-
"
|
|
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: "
|
|
793
|
+
question: "Do I need to know Convex?",
|
|
610
794
|
answer:
|
|
611
|
-
"
|
|
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: "
|
|
798
|
+
question: "Is it production ready?",
|
|
615
799
|
answer:
|
|
616
|
-
"
|
|
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: "
|
|
803
|
+
question: "Which frameworks does it support?",
|
|
620
804
|
answer:
|
|
621
|
-
"
|
|
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: "
|
|
808
|
+
question: "What does it cost?",
|
|
625
809
|
answer:
|
|
626
|
-
"
|
|
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:
|
|
815
|
+
blockType: BLOCK_SLUG_CTA,
|
|
632
816
|
blockName: "CTA",
|
|
633
817
|
id: "home-cta",
|
|
634
|
-
heading: "
|
|
635
|
-
subheading: "
|
|
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: "
|
|
638
|
-
{ label: "View on GitHub", href: "https://github.com/
|
|
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
|
})
|