create-ampless 0.2.0-alpha.0

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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +229 -0
  5. package/dist/templates/_shared/RUNBOOK.md +178 -0
  6. package/dist/templates/_shared/amplify/auth/post-confirmation/handler.ts +4 -0
  7. package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +6 -0
  8. package/dist/templates/_shared/amplify/auth/resource.ts +8 -0
  9. package/dist/templates/_shared/amplify/backend.ts +29 -0
  10. package/dist/templates/_shared/amplify/data/get-published-post.js +33 -0
  11. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +52 -0
  12. package/dist/templates/_shared/amplify/data/list-published-posts.js +57 -0
  13. package/dist/templates/_shared/amplify/data/resource.ts +30 -0
  14. package/dist/templates/_shared/amplify/events/dispatcher/handler.ts +4 -0
  15. package/dist/templates/_shared/amplify/events/dispatcher/resource.ts +12 -0
  16. package/dist/templates/_shared/amplify/events/processor-trusted/handler.ts +12 -0
  17. package/dist/templates/_shared/amplify/events/processor-trusted/resource.ts +14 -0
  18. package/dist/templates/_shared/amplify/events/processor-untrusted/handler.ts +10 -0
  19. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +9 -0
  20. package/dist/templates/_shared/amplify/functions/api-key-renewer/handler.ts +4 -0
  21. package/dist/templates/_shared/amplify/functions/api-key-renewer/resource.ts +12 -0
  22. package/dist/templates/_shared/amplify/storage/resource.ts +7 -0
  23. package/dist/templates/_shared/app/(admin)/admin/layout.tsx +4 -0
  24. package/dist/templates/_shared/app/(admin)/admin/media/page.tsx +4 -0
  25. package/dist/templates/_shared/app/(admin)/admin/page.tsx +4 -0
  26. package/dist/templates/_shared/app/(admin)/admin/posts/[postId]/page.tsx +4 -0
  27. package/dist/templates/_shared/app/(admin)/admin/posts/new/page.tsx +4 -0
  28. package/dist/templates/_shared/app/(admin)/admin/posts/page.tsx +4 -0
  29. package/dist/templates/_shared/app/(admin)/admin/sites/[siteId]/page.tsx +5 -0
  30. package/dist/templates/_shared/app/(admin)/admin/sites/[siteId]/theme/page.tsx +6 -0
  31. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +5 -0
  32. package/dist/templates/_shared/app/api/media/[...path]/route.ts +5 -0
  33. package/dist/templates/_shared/app/globals.css +114 -0
  34. package/dist/templates/_shared/app/layout.tsx +48 -0
  35. package/dist/templates/_shared/app/login/page.tsx +4 -0
  36. package/dist/templates/_shared/app/providers.tsx +13 -0
  37. package/dist/templates/_shared/app/site/[siteId]/[slug]/page.tsx +10 -0
  38. package/dist/templates/_shared/app/site/[siteId]/feed.xml/route.ts +5 -0
  39. package/dist/templates/_shared/app/site/[siteId]/og/[slug]/route.ts +6 -0
  40. package/dist/templates/_shared/app/site/[siteId]/page.tsx +10 -0
  41. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +5 -0
  42. package/dist/templates/_shared/app/site/[siteId]/sitemap.xml/route.ts +5 -0
  43. package/dist/templates/_shared/app/site/[siteId]/tag/[tag]/page.tsx +10 -0
  44. package/dist/templates/_shared/cms.config.ts +110 -0
  45. package/dist/templates/_shared/components/i18n-provider.tsx +7 -0
  46. package/dist/templates/_shared/components/lightbox-content.tsx +69 -0
  47. package/dist/templates/_shared/components/site-chrome/collapsible-sidebar.tsx +54 -0
  48. package/dist/templates/_shared/components/site-chrome/mobile-menu.tsx +68 -0
  49. package/dist/templates/_shared/components/site-chrome/site-footer.tsx +43 -0
  50. package/dist/templates/_shared/components/site-chrome/site-header.tsx +94 -0
  51. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +81 -0
  52. package/dist/templates/_shared/components/tag-list.tsx +25 -0
  53. package/dist/templates/_shared/components.json +21 -0
  54. package/dist/templates/_shared/lib/admin-site-client.ts +10 -0
  55. package/dist/templates/_shared/lib/admin-site.ts +8 -0
  56. package/dist/templates/_shared/lib/admin.ts +24 -0
  57. package/dist/templates/_shared/lib/ampless.ts +23 -0
  58. package/dist/templates/_shared/lib/amplify-server.ts +7 -0
  59. package/dist/templates/_shared/lib/amplify.ts +9 -0
  60. package/dist/templates/_shared/lib/auth-server.ts +11 -0
  61. package/dist/templates/_shared/lib/cn.ts +5 -0
  62. package/dist/templates/_shared/lib/i18n.ts +31 -0
  63. package/dist/templates/_shared/lib/kv-provider.ts +7 -0
  64. package/dist/templates/_shared/lib/media.ts +6 -0
  65. package/dist/templates/_shared/lib/posts-provider.ts +7 -0
  66. package/dist/templates/_shared/lib/posts-public.ts +19 -0
  67. package/dist/templates/_shared/lib/posts.ts +12 -0
  68. package/dist/templates/_shared/lib/seo.ts +8 -0
  69. package/dist/templates/_shared/lib/site-settings.ts +8 -0
  70. package/dist/templates/_shared/lib/storage.ts +7 -0
  71. package/dist/templates/_shared/lib/theme-actions.ts +5 -0
  72. package/dist/templates/_shared/lib/theme-active.ts +8 -0
  73. package/dist/templates/_shared/lib/theme-config.ts +10 -0
  74. package/dist/templates/_shared/lib/upload.ts +6 -0
  75. package/dist/templates/_shared/middleware.ts +13 -0
  76. package/dist/templates/_shared/next.config.mjs +11 -0
  77. package/dist/templates/_shared/package.json +63 -0
  78. package/dist/templates/_shared/postcss.config.mjs +5 -0
  79. package/dist/templates/_shared/themes-registry.ts +38 -0
  80. package/dist/templates/_shared/tsconfig.json +23 -0
  81. package/dist/templates/blog/README.md +52 -0
  82. package/dist/templates/blog/index.ts +29 -0
  83. package/dist/templates/blog/manifest.ts +144 -0
  84. package/dist/templates/blog/pages/feed.ts +31 -0
  85. package/dist/templates/blog/pages/home.tsx +108 -0
  86. package/dist/templates/blog/pages/post.tsx +94 -0
  87. package/dist/templates/blog/pages/sitemap.ts +30 -0
  88. package/dist/templates/blog/pages/tag.tsx +76 -0
  89. package/dist/templates/blog/tokens.css +54 -0
  90. package/dist/templates/corporate/README.md +20 -0
  91. package/dist/templates/corporate/index.ts +25 -0
  92. package/dist/templates/corporate/manifest.ts +94 -0
  93. package/dist/templates/corporate/pages/feed.ts +29 -0
  94. package/dist/templates/corporate/pages/home.tsx +130 -0
  95. package/dist/templates/corporate/pages/post.tsx +96 -0
  96. package/dist/templates/corporate/pages/sitemap.ts +28 -0
  97. package/dist/templates/corporate/pages/tag.tsx +81 -0
  98. package/dist/templates/corporate/tokens.css +47 -0
  99. package/dist/templates/dads/README.md +35 -0
  100. package/dist/templates/dads/index.ts +25 -0
  101. package/dist/templates/dads/manifest.ts +84 -0
  102. package/dist/templates/dads/pages/feed.ts +29 -0
  103. package/dist/templates/dads/pages/home.tsx +126 -0
  104. package/dist/templates/dads/pages/post.tsx +102 -0
  105. package/dist/templates/dads/pages/sitemap.ts +28 -0
  106. package/dist/templates/dads/pages/tag.tsx +86 -0
  107. package/dist/templates/dads/tokens.css +67 -0
  108. package/dist/templates/docs/README.md +27 -0
  109. package/dist/templates/docs/index.ts +25 -0
  110. package/dist/templates/docs/manifest.ts +89 -0
  111. package/dist/templates/docs/pages/feed.ts +29 -0
  112. package/dist/templates/docs/pages/home.tsx +88 -0
  113. package/dist/templates/docs/pages/post.tsx +96 -0
  114. package/dist/templates/docs/pages/sitemap.ts +28 -0
  115. package/dist/templates/docs/pages/tag.tsx +79 -0
  116. package/dist/templates/docs/tokens.css +55 -0
  117. package/dist/templates/landing/README.md +25 -0
  118. package/dist/templates/landing/index.ts +25 -0
  119. package/dist/templates/landing/manifest.ts +118 -0
  120. package/dist/templates/landing/pages/feed.ts +31 -0
  121. package/dist/templates/landing/pages/home.tsx +123 -0
  122. package/dist/templates/landing/pages/post.tsx +95 -0
  123. package/dist/templates/landing/pages/sitemap.ts +28 -0
  124. package/dist/templates/landing/pages/tag.tsx +85 -0
  125. package/dist/templates/landing/tokens.css +47 -0
  126. package/dist/templates/minimal/README.md +52 -0
  127. package/dist/templates/minimal/index.ts +25 -0
  128. package/dist/templates/minimal/manifest.ts +35 -0
  129. package/dist/templates/minimal/pages/feed.ts +31 -0
  130. package/dist/templates/minimal/pages/home.tsx +44 -0
  131. package/dist/templates/minimal/pages/post.tsx +65 -0
  132. package/dist/templates/minimal/pages/sitemap.ts +30 -0
  133. package/dist/templates/minimal/pages/tag.tsx +46 -0
  134. package/dist/templates/minimal/tokens.css +46 -0
  135. package/package.json +41 -0
@@ -0,0 +1,54 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { Menu } from 'lucide-react'
5
+ import { Sheet, SheetContent, SheetTrigger } from '@ampless/runtime/ui'
6
+
7
+ interface Props {
8
+ /** Sidebar content — typically `<SiteSidebar ... />`. */
9
+ children: React.ReactNode
10
+ /** Label on the toggle button (mobile only). */
11
+ label?: string
12
+ className?: string
13
+ }
14
+
15
+ /**
16
+ * Wraps a sidebar so it slides in from the left as a Sheet drawer on
17
+ * small screens, and renders inline / sticky at `lg` and above. The
18
+ * wrapped content can still be a server component (passed as children
19
+ * and rendered as-is); only the open/closed toggle lives client-side.
20
+ *
21
+ * The children render twice — once inline (visible only at lg+) and
22
+ * once inside the Sheet (mounted only when open, portaled to body) —
23
+ * which is fine because they're already-evaluated React elements with
24
+ * no per-instance state of their own.
25
+ */
26
+ export function CollapsibleSidebar({ children, label = 'Menu', className }: Props) {
27
+ const [open, setOpen] = useState(false)
28
+ return (
29
+ <div className={className}>
30
+ <Sheet open={open} onOpenChange={setOpen}>
31
+ <SheetTrigger
32
+ aria-label={label}
33
+ className="flex w-full items-center gap-2 rounded-md border bg-[var(--card)] px-4 py-2 text-sm font-medium hover:bg-[var(--accent)] lg:hidden"
34
+ >
35
+ <Menu className="h-4 w-4" />
36
+ <span>{label}</span>
37
+ </SheetTrigger>
38
+ <SheetContent
39
+ side="left"
40
+ className="w-72 overflow-y-auto px-6 py-12"
41
+ onClick={(e) => {
42
+ // SiteSidebar is a server component, so we can't wrap each
43
+ // Link in <SheetClose>. Detect anchor clicks via delegation
44
+ // instead and close the sheet on navigation.
45
+ if ((e.target as HTMLElement).closest('a')) setOpen(false)
46
+ }}
47
+ >
48
+ {children}
49
+ </SheetContent>
50
+ </Sheet>
51
+ <div className="hidden lg:block">{children}</div>
52
+ </div>
53
+ )
54
+ }
@@ -0,0 +1,68 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import Link from 'next/link'
5
+ import { isTagListUrl, type LinkListItem } from 'ampless'
6
+ import { Sheet, SheetClose, SheetContent, SheetTrigger } from '@ampless/runtime/ui'
7
+
8
+ interface Props {
9
+ items: LinkListItem[]
10
+ className?: string
11
+ }
12
+
13
+ /**
14
+ * Mobile nav for SiteHeader. Animated hamburger toggle that opens a
15
+ * right-side Sheet drawer with the linkList. The button morphs from
16
+ * three lines to an X via CSS transforms when the sheet is open. ESC,
17
+ * overlay click, and link click all close the drawer.
18
+ *
19
+ * Tag references collapse to plain text — same rule as the desktop
20
+ * header. Sidebars are the right surface for tag-driven post lists.
21
+ */
22
+ export function MobileMenu({ items, className }: Props) {
23
+ const [open, setOpen] = useState(false)
24
+ return (
25
+ <Sheet open={open} onOpenChange={setOpen}>
26
+ <SheetTrigger
27
+ aria-label="Menu"
28
+ className={`group relative inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-[var(--accent)] ${className ?? ''}`}
29
+ >
30
+ {/* Three lines that morph into an X. Radix sets `data-state`
31
+ ("open" / "closed") on this trigger element; the spans
32
+ below tap into it via `group-data-[state=open]:`. */}
33
+ <span className="sr-only">Menu</span>
34
+ <span aria-hidden className="block h-4 w-5 relative">
35
+ <span className="absolute left-0 top-0 h-0.5 w-5 bg-current origin-center transition-transform duration-200 group-data-[state=open]:translate-y-[7px] group-data-[state=open]:rotate-45" />
36
+ <span className="absolute left-0 top-1.5 h-0.5 w-5 bg-current transition-opacity duration-200 group-data-[state=open]:opacity-0" />
37
+ <span className="absolute left-0 top-3 h-0.5 w-5 bg-current origin-center transition-transform duration-200 group-data-[state=open]:-translate-y-[7px] group-data-[state=open]:-rotate-45" />
38
+ </span>
39
+ </SheetTrigger>
40
+ <SheetContent side="right" className="w-72">
41
+ <nav className="flex flex-col gap-1 px-6 py-16 text-base">
42
+ {items.map((item, i) => {
43
+ if (isTagListUrl(item.url)) {
44
+ return (
45
+ <span
46
+ key={i}
47
+ className="px-2 py-3 text-muted-foreground"
48
+ >
49
+ {item.label}
50
+ </span>
51
+ )
52
+ }
53
+ return (
54
+ <SheetClose asChild key={i}>
55
+ <Link
56
+ href={item.url}
57
+ className="rounded-md px-2 py-3 text-foreground hover:bg-[var(--accent)] hover:text-[var(--primary)]"
58
+ >
59
+ {item.label}
60
+ </Link>
61
+ </SheetClose>
62
+ )
63
+ })}
64
+ </nav>
65
+ </SheetContent>
66
+ </Sheet>
67
+ )
68
+ }
@@ -0,0 +1,43 @@
1
+ import Link from 'next/link'
2
+ import { parseLinkList, isTagListUrl } from 'ampless'
3
+
4
+ interface Props {
5
+ links: string | undefined
6
+ /** Optional below-links text (copyright, tagline). */
7
+ legend?: React.ReactNode
8
+ className?: string
9
+ }
10
+
11
+ /**
12
+ * Theme-agnostic footer rendering a `linkList` plus optional legend
13
+ * below. Same tag: handling rule as the header — collapse to plain
14
+ * text rather than blow out the chrome with post lists.
15
+ */
16
+ export function SiteFooter({ links, legend, className }: Props) {
17
+ const items = parseLinkList(links)
18
+ return (
19
+ <footer className={`border-t px-6 py-8 ${className ?? ''}`}>
20
+ <div className="mx-auto max-w-5xl space-y-4">
21
+ {items.length > 0 && (
22
+ <nav className="flex flex-wrap gap-x-6 gap-y-2 text-sm text-muted-foreground">
23
+ {items.map((item, i) => {
24
+ if (isTagListUrl(item.url)) {
25
+ return <span key={i}>{item.label}</span>
26
+ }
27
+ return (
28
+ <Link
29
+ key={i}
30
+ href={item.url}
31
+ className="hover:text-[var(--primary)]"
32
+ >
33
+ {item.label}
34
+ </Link>
35
+ )
36
+ })}
37
+ </nav>
38
+ )}
39
+ {legend && <div className="text-xs text-muted-foreground">{legend}</div>}
40
+ </div>
41
+ </footer>
42
+ )
43
+ }
@@ -0,0 +1,94 @@
1
+ import Link from 'next/link'
2
+ import { parseLinkList, isTagListUrl } from 'ampless'
3
+ import { MobileMenu } from './mobile-menu'
4
+
5
+ interface Props {
6
+ /** JSON-stringified linkList from theme.values.<key>. */
7
+ links: string | undefined
8
+ /** Optional logo image URL. When set, rendered as an <img>; the
9
+ * siteName is used as alt text. Empty falls back to siteName text. */
10
+ logoUrl?: string
11
+ /** Site name. Used as the brand text when no logo, and as alt
12
+ * text on the logo image. */
13
+ siteName?: string
14
+ /** Tailwind classes for the brand text wrapper (only when no logo). */
15
+ brandClassName?: string
16
+ /** Tailwind classes for the logo `<img>`. Defaults to a 32px-tall
17
+ * auto-width sizing — themes can override for taller / specific
18
+ * branding placements. */
19
+ logoClassName?: string
20
+ className?: string
21
+ }
22
+
23
+ /**
24
+ * Theme-agnostic header that consumes a `linkList` value (JSON string
25
+ * from a theme manifest field). Renders a logo image when `logoUrl`
26
+ * is set; otherwise shows `siteName` text. Themes pick the brand
27
+ * styling (font size / weight) via `brandClassName` so each theme can
28
+ * keep its own typographic identity.
29
+ *
30
+ * Responsive: the regular `<nav>` is hidden below `md` and replaced
31
+ * by a hamburger toggle (MobileMenu) that drops a panel overlay below
32
+ * the header.
33
+ *
34
+ * `tag:<name>` URLs in the link list collapse to plain text — header
35
+ * isn't the right surface for inline post lists; use SiteSidebar for
36
+ * tag-driven nav.
37
+ */
38
+ export function SiteHeader({
39
+ links,
40
+ logoUrl,
41
+ siteName,
42
+ brandClassName,
43
+ logoClassName = 'h-8 w-auto',
44
+ className,
45
+ }: Props) {
46
+ const items = parseLinkList(links)
47
+ const trimmedLogo = logoUrl?.trim()
48
+ return (
49
+ <header
50
+ className={`relative flex items-center justify-between border-b px-6 py-4 ${className ?? ''}`}
51
+ >
52
+ <Link
53
+ href="/"
54
+ className={
55
+ trimmedLogo
56
+ ? 'inline-flex items-center'
57
+ : (brandClassName ?? 'font-semibold hover:text-[var(--primary)]')
58
+ }
59
+ >
60
+ {trimmedLogo ? (
61
+ // eslint-disable-next-line @next/next/no-img-element
62
+ <img src={trimmedLogo} alt={siteName ?? ''} className={logoClassName} />
63
+ ) : (
64
+ (siteName ?? 'Home')
65
+ )}
66
+ </Link>
67
+ {items.length > 0 && (
68
+ <>
69
+ <nav className="hidden items-center gap-5 text-sm md:flex">
70
+ {items.map((item, i) => {
71
+ if (isTagListUrl(item.url)) {
72
+ return (
73
+ <span key={i} className="text-muted-foreground">
74
+ {item.label}
75
+ </span>
76
+ )
77
+ }
78
+ return (
79
+ <Link
80
+ key={i}
81
+ href={item.url}
82
+ className="text-foreground hover:text-[var(--primary)]"
83
+ >
84
+ {item.label}
85
+ </Link>
86
+ )
87
+ })}
88
+ </nav>
89
+ <MobileMenu items={items} className="md:hidden" />
90
+ </>
91
+ )}
92
+ </header>
93
+ )
94
+ }
@@ -0,0 +1,81 @@
1
+ import Link from 'next/link'
2
+ import { parseLinkList, isTagListUrl } from 'ampless'
3
+ import { listPostsByTag } from '@/lib/posts-public'
4
+
5
+ interface Props {
6
+ links: string | undefined
7
+ siteId: string
8
+ className?: string
9
+ }
10
+
11
+ /**
12
+ * Sidebar nav with tag expansion. For docs-style sites: a `linkList`
13
+ * entry whose URL is `tag:<name>` is rendered as a labelled section
14
+ * with every published post tagged `<name>` listed underneath. Plain
15
+ * URLs render as a single link in the same flat list.
16
+ *
17
+ * Each tag: entry triggers one AppSync query. With ~10 tag sections
18
+ * in a sidebar that's 10 queries per render; revalidation should be
19
+ * paired with `force-dynamic` on the page so fresh content shows up
20
+ * after publish events.
21
+ */
22
+ export async function SiteSidebar({ links, siteId, className }: Props) {
23
+ const items = parseLinkList(links)
24
+ if (items.length === 0) return null
25
+
26
+ // Resolve tag: entries up front so we render after all data is in.
27
+ const sections = await Promise.all(
28
+ items.map(async (item) => {
29
+ const tagRef = isTagListUrl(item.url)
30
+ if (!tagRef) return { type: 'link' as const, label: item.label, url: item.url }
31
+ const { items: posts } = await listPostsByTag(tagRef.tag, { siteId, limit: 50 })
32
+ return {
33
+ type: 'tagSection' as const,
34
+ label: item.label,
35
+ tag: tagRef.tag,
36
+ posts: posts.map((p) => ({ slug: p.slug, title: p.title })),
37
+ }
38
+ })
39
+ )
40
+
41
+ return (
42
+ <aside className={`space-y-6 ${className ?? ''}`}>
43
+ {sections.map((section, i) => {
44
+ if (section.type === 'link') {
45
+ return (
46
+ <Link
47
+ key={i}
48
+ href={section.url}
49
+ className="block text-sm font-medium hover:text-[var(--primary)]"
50
+ >
51
+ {section.label}
52
+ </Link>
53
+ )
54
+ }
55
+ return (
56
+ <div key={i}>
57
+ <p className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
58
+ {section.label}
59
+ </p>
60
+ <ul className="space-y-1">
61
+ {section.posts.length === 0 ? (
62
+ <li className="text-xs text-muted-foreground">No posts.</li>
63
+ ) : (
64
+ section.posts.map((post) => (
65
+ <li key={post.slug}>
66
+ <Link
67
+ href={`/${post.slug}`}
68
+ className="block text-sm text-foreground hover:text-[var(--primary)]"
69
+ >
70
+ {post.title}
71
+ </Link>
72
+ </li>
73
+ ))
74
+ )}
75
+ </ul>
76
+ </div>
77
+ )
78
+ })}
79
+ </aside>
80
+ )
81
+ }
@@ -0,0 +1,25 @@
1
+ import Link from 'next/link'
2
+
3
+ interface TagListProps {
4
+ tags?: string[] | null
5
+ className?: string
6
+ }
7
+
8
+ // Renders post tags as chip-style links to /tag/[tag]. Pure-server, no JS.
9
+ export function TagList({ tags, className }: TagListProps) {
10
+ if (!tags?.length) return null
11
+ return (
12
+ <ul className={`flex flex-wrap gap-2 ${className ?? ''}`}>
13
+ {tags.map((tag) => (
14
+ <li key={tag}>
15
+ <Link
16
+ href={`/tag/${encodeURIComponent(tag)}`}
17
+ className="inline-block rounded-full border px-3 py-0.5 text-xs text-muted-foreground hover:border-foreground hover:text-foreground"
18
+ >
19
+ #{tag}
20
+ </Link>
21
+ </li>
22
+ ))}
23
+ </ul>
24
+ )
25
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/cn",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,10 @@
1
+ // Back-compat shim. Client-side admin-site helpers moved to
2
+ // `@ampless/admin` (L2 extraction). Existing call sites
3
+ // (`readAdminSiteIdFromCookie`, the `ADMIN_SITE_COOKIE` constant) keep
4
+ // working through this shim — the cms.config registration is performed
5
+ // inside the admin's <AdminProviders> bootstrap.
6
+
7
+ export {
8
+ ADMIN_SITE_COOKIE,
9
+ readAdminSiteIdFromCookie,
10
+ } from '@ampless/admin/components'
@@ -0,0 +1,8 @@
1
+ // Back-compat shim. Server-side admin-site helpers moved to
2
+ // `@ampless/admin` (L2 extraction). New code should call the same
3
+ // methods on the `admin` instance directly.
4
+
5
+ import { admin } from './admin'
6
+
7
+ export const currentAdminSiteId = admin.currentAdminSiteId.bind(admin)
8
+ export const adminSiteOptions = admin.adminSiteOptions.bind(admin)
@@ -0,0 +1,24 @@
1
+ // Wired-up admin UI factory. Single source of truth for the admin
2
+ // library — every admin route shell, API route shell, and form
3
+ // re-export shim imports the `admin` value from here.
4
+ //
5
+ // L2 architectural change (admin extraction): admin UI now lives in
6
+ // `@ampless/admin`. This module wires the project's
7
+ // `amplify_outputs.json`, `cms.config`, and `ampless` runtime into a
8
+ // single `Admin` instance.
9
+
10
+ import outputs from '../amplify_outputs.json'
11
+ import cmsConfig from '@/cms.config'
12
+ import { createAdmin } from '@ampless/admin'
13
+ import { ampless } from './ampless'
14
+
15
+ export const admin = createAdmin({
16
+ outputs,
17
+ cmsConfig,
18
+ ampless,
19
+ locale: (cmsConfig as { locale?: string }).locale ?? 'en',
20
+ })
21
+
22
+ // Convenience: the server-side translation helper. Client components
23
+ // should use `useT()` from `@/components/i18n-provider` instead.
24
+ export const t = admin.t
@@ -0,0 +1,23 @@
1
+ // Wired-up ampless runtime instance. Single source of truth for the
2
+ // public-side library — every route handler, dispatcher, and theme
3
+ // component imports the `ampless` value from here.
4
+ //
5
+ // L1 architectural change (runtime extraction): public-side
6
+ // behaviour now lives in `@ampless/runtime`. This module wires the
7
+ // project's `amplify_outputs.json`, `cms.config`, and themes registry
8
+ // into a single `Ampless` instance.
9
+ //
10
+ // Admin-side modules (post providers, kv-provider, auth, etc.) stay
11
+ // in `templates/_shared/lib/` for now — they move into `@ampless/admin`
12
+ // in L2.
13
+
14
+ import outputs from '../amplify_outputs.json'
15
+ import cmsConfig from '@/cms.config'
16
+ import { themes, DEFAULT_THEME } from '@/themes-registry'
17
+ import { createAmpless } from '@ampless/runtime'
18
+
19
+ export const ampless = createAmpless({
20
+ outputs,
21
+ cmsConfig,
22
+ themes: { themes, defaultTheme: DEFAULT_THEME },
23
+ })
@@ -0,0 +1,7 @@
1
+ // Back-compat shim. The Amplify SSR server runner moved to
2
+ // `@ampless/admin` (L2 extraction). Existing call sites that import
3
+ // `runWithAmplifyServerContext` from here keep working.
4
+
5
+ import { admin } from './admin'
6
+
7
+ export const { runWithAmplifyServerContext } = admin.amplifyServer
@@ -0,0 +1,9 @@
1
+ // Back-compat shim. The Amplify SDK is now configured by the admin's
2
+ // `<AdminProviders>` bootstrap (mounted by the layout factory in
3
+ // `@ampless/admin/pages`), so most call sites no longer need to call
4
+ // `configureAmplify()` themselves. Kept as a no-op so any lingering
5
+ // `import '@/lib/amplify'` side-effect imports stay safe.
6
+
7
+ export function configureAmplify() {
8
+ // intentionally empty — admin bootstrap handles this
9
+ }
@@ -0,0 +1,11 @@
1
+ // Back-compat shim. Auth helpers moved to `@ampless/admin` (L2
2
+ // extraction). New code should call `admin.getServerSession` /
3
+ // `admin.isAdmin` / `admin.isEditor` directly.
4
+
5
+ import { admin } from './admin'
6
+
7
+ export type { ServerSession } from '@ampless/admin'
8
+
9
+ export const getServerSession = admin.getServerSession.bind(admin)
10
+ export const isAdmin = admin.isAdmin.bind(admin)
11
+ export const isEditor = admin.isEditor.bind(admin)
@@ -0,0 +1,5 @@
1
+ // Back-compat shim. `cn()` moved to `@ampless/runtime/ui` (L2
2
+ // extension) so admin + theme-side site-chrome share one source of
3
+ // truth. New code should import `cn` from `@ampless/runtime/ui`.
4
+
5
+ export { cn } from '@ampless/runtime/ui'
@@ -0,0 +1,31 @@
1
+ // Back-compat shim. Admin i18n moved to `@ampless/admin` (L2
2
+ // extraction). The dictionary is bound at admin-factory time in
3
+ // `lib/admin.ts`; this file keeps the existing call sites (`t(...)`,
4
+ // `getLocale()`, `getDictionary()`) working.
5
+ //
6
+ // New code should use `admin.t` (from `@/lib/admin`) or the client-side
7
+ // `useT()` (from `@/components/i18n-provider`).
8
+
9
+ import { admin } from './admin'
10
+ import { getDictionary as adminGetDictionary, type Dictionary, type Locale } from '@ampless/admin'
11
+
12
+ export type { Dictionary, Locale }
13
+ export const FALLBACK_LOCALE: Locale = 'en'
14
+
15
+ export function getLocale(): Locale {
16
+ return admin.locale
17
+ }
18
+
19
+ export function getDictionary(locale: Locale = admin.locale): Dictionary {
20
+ return adminGetDictionary(locale)
21
+ }
22
+
23
+ export const t = admin.t
24
+
25
+ export function translate(
26
+ _dict: Dictionary,
27
+ key: string,
28
+ vars?: Record<string, string | number>
29
+ ): string {
30
+ return admin.t(key, vars)
31
+ }
@@ -0,0 +1,7 @@
1
+ // Back-compat shim. KvStore provider installation moved to
2
+ // `@ampless/admin` (L2 extraction). The install runs automatically
3
+ // inside the admin's <AdminProviders> bootstrap, so a side-effect
4
+ // `import '@/lib/kv-provider'` is no longer required — kept as a
5
+ // safe no-op for legacy callers.
6
+
7
+ export {}
@@ -0,0 +1,6 @@
1
+ // Back-compat shim. Media URL helper moved to `@ampless/admin` (L2
2
+ // extraction). The client-side state is registered inside the admin's
3
+ // <AdminProviders> bootstrap; existing call sites
4
+ // (`publicMediaUrl(...)`) keep working through this shim.
5
+
6
+ export { publicMediaUrl } from '@ampless/admin/components'
@@ -0,0 +1,7 @@
1
+ // Back-compat shim. Admin Posts provider installation moved to
2
+ // `@ampless/admin` (L2 extraction). The install runs automatically
3
+ // inside the admin's <AdminProviders> bootstrap, so a side-effect
4
+ // `import '@/lib/posts-provider'` is no longer required — kept as a
5
+ // safe no-op for legacy callers.
6
+
7
+ export {}
@@ -0,0 +1,19 @@
1
+ // Back-compat shim. Implementation moved to `@ampless/runtime` (L1
2
+ // extraction). Theme files keep importing from `@/lib/posts-public`
3
+ // unchanged.
4
+ //
5
+ // New code should import from `@/lib/ampless` directly:
6
+ // import { ampless } from '@/lib/ampless'
7
+ // const posts = await ampless.listPublishedPosts({ siteId })
8
+
9
+ import { ampless } from './ampless'
10
+
11
+ export const listPublishedPosts = ampless.listPublishedPosts.bind(ampless)
12
+ export const getPublishedPost = ampless.getPublishedPost.bind(ampless)
13
+ export const listPostsByTag = ampless.listPostsByTag.bind(ampless)
14
+
15
+ export type {
16
+ ListPostsOptions,
17
+ ListPostsByTagOptions,
18
+ ListPostsResult,
19
+ } from '@ampless/runtime'
@@ -0,0 +1,12 @@
1
+ // Back-compat shim. Body rendering + format converters moved to
2
+ // `@ampless/runtime` (L1 extraction). The renderer is reachable
3
+ // directly via `ampless.renderBody`, but theme files and the admin
4
+ // post form still import these names from `@/lib/posts`.
5
+
6
+ export {
7
+ renderBody,
8
+ tiptapToHtml,
9
+ markdownToHtml,
10
+ tiptapToMarkdown,
11
+ htmlToMarkdown,
12
+ } from '@ampless/runtime'
@@ -0,0 +1,8 @@
1
+ // Back-compat shim. SEO metadata aggregation moved to
2
+ // `@ampless/runtime`. New code should call `ampless.postMetadata` /
3
+ // `ampless.siteMetadata` directly.
4
+
5
+ import { ampless } from './ampless'
6
+
7
+ export const postMetadata = ampless.postMetadata.bind(ampless)
8
+ export const siteMetadata = ampless.siteMetadata.bind(ampless)
@@ -0,0 +1,8 @@
1
+ // Back-compat shim. Site-settings loader moved to `@ampless/runtime`.
2
+ // New code should call `ampless.loadSiteSettings` directly.
3
+
4
+ import { ampless } from './ampless'
5
+
6
+ export const loadSiteSettings = ampless.loadSiteSettings.bind(ampless)
7
+
8
+ export type { EffectiveSiteSettings } from '@ampless/runtime'
@@ -0,0 +1,7 @@
1
+ // Back-compat shim. Storage URL helpers moved to `@ampless/runtime`.
2
+ // New code should call `ampless.publicAssetUrl` / `ampless.isStorageConfigured` directly.
3
+
4
+ import { ampless } from './ampless'
5
+
6
+ export const publicAssetUrl = ampless.publicAssetUrl.bind(ampless)
7
+ export const isStorageConfigured = ampless.isStorageConfigured.bind(ampless)
@@ -0,0 +1,5 @@
1
+ // Back-compat shim. Theme cache invalidation Server Action moved to
2
+ // `@ampless/admin`. The `'use server'` directive lives on the
3
+ // underlying admin module; this file is a plain re-export.
4
+
5
+ export { invalidateSiteSettingsCache } from '@ampless/admin/components'
@@ -0,0 +1,8 @@
1
+ // Back-compat shim. Active-theme resolution moved to `@ampless/runtime`.
2
+ // New code should call `ampless.resolveActiveTheme` directly.
3
+
4
+ import { ampless } from './ampless'
5
+
6
+ export const resolveActiveTheme = ampless.resolveActiveTheme.bind(ampless)
7
+
8
+ export type { ResolvedTheme } from '@ampless/runtime'
@@ -0,0 +1,10 @@
1
+ // Back-compat shim. Theme-config loader moved to `@ampless/runtime`.
2
+ // New code should call `ampless.loadThemeConfig` / `ampless.renderThemeCss` directly.
3
+
4
+ import { ampless } from './ampless'
5
+
6
+ export const loadThemeConfig = ampless.loadThemeConfig.bind(ampless)
7
+
8
+ export { renderThemeCss } from '@ampless/runtime'
9
+
10
+ export type { EffectiveThemeConfig } from '@ampless/runtime'
@@ -0,0 +1,6 @@
1
+ // Back-compat shim. Upload helper moved to `@ampless/admin` (L2
2
+ // extraction). Existing call sites
3
+ // (`uploadProcessedImage(...)`, `sanitizeName(...)`) keep working
4
+ // through this shim.
5
+
6
+ export { uploadProcessedImage, sanitizeName } from '@ampless/admin/components'