@forgecart/cli 2.202610280649.0 → 2.202610280926.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 (218) hide show
  1. package/dist/src/commands/init-questions.d.ts +12 -11
  2. package/dist/src/commands/init-questions.js +15 -23
  3. package/dist/src/commands/init-questions.js.map +1 -1
  4. package/dist/src/commands/init.d.ts +13 -13
  5. package/dist/src/commands/init.js +75 -144
  6. package/dist/src/commands/init.js.map +1 -1
  7. package/dist/src/commands/refresh.d.ts +3 -3
  8. package/dist/src/commands/refresh.js +4 -14
  9. package/dist/src/commands/refresh.js.map +1 -1
  10. package/dist/src/commands/template-manifest.d.ts +2 -5
  11. package/dist/src/commands/template-manifest.js +1 -1
  12. package/dist/src/commands/template-manifest.js.map +1 -1
  13. package/dist/src/commands/type/init.input.d.ts +1 -1
  14. package/dist/src/commands/type/init.types.d.ts +4 -4
  15. package/dist/src/template/template-set.d.ts +17 -27
  16. package/dist/src/template/template-set.js +25 -42
  17. package/dist/src/template/template-set.js.map +1 -1
  18. package/dist/src/template/template-unavailable-error.d.ts +3 -4
  19. package/dist/src/template/template-unavailable-error.js +3 -4
  20. package/dist/src/template/template-unavailable-error.js.map +1 -1
  21. package/dist/src/template/type/template-set.types.d.ts +14 -17
  22. package/dist/src/template/type/template-set.types.js +6 -1
  23. package/dist/src/template/type/template-set.types.js.map +1 -1
  24. package/dist/src/template/util/template-identity.util.d.ts +1 -3
  25. package/dist/src/template/util/template-identity.util.js +0 -5
  26. package/dist/src/template/util/template-identity.util.js.map +1 -1
  27. package/package.json +1 -1
  28. package/templates/storefront-blank/.forgecartignore +2 -0
  29. package/templates/storefront-blank/Procfile +1 -0
  30. package/templates/storefront-blank/README.md +86 -0
  31. package/templates/storefront-blank/SEO-MIGRATION.md +718 -0
  32. package/templates/storefront-blank/_gitignore +21 -0
  33. package/templates/storefront-blank/next.config.js +105 -0
  34. package/templates/storefront-blank/package.json +35 -0
  35. package/templates/storefront-blank/postcss.config.js +5 -0
  36. package/templates/storefront-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
  37. package/templates/storefront-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
  38. package/templates/storefront-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
  39. package/templates/storefront-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
  40. package/templates/storefront-blank/src/app/error.tsx +21 -0
  41. package/templates/storefront-blank/src/app/global-error.tsx +23 -0
  42. package/templates/storefront-blank/src/app/globals.css +110 -0
  43. package/templates/storefront-blank/src/app/layout.tsx +102 -0
  44. package/templates/storefront-blank/src/app/page.tsx +52 -0
  45. package/templates/storefront-blank/src/app/pages/[slug]/not-found.tsx +23 -0
  46. package/templates/storefront-blank/src/app/pages/[slug]/page.tsx +115 -0
  47. package/templates/storefront-blank/src/app/ping/route.ts +21 -0
  48. package/templates/storefront-blank/src/app/robots.ts +69 -0
  49. package/templates/storefront-blank/src/app/sitemap.ts +107 -0
  50. package/templates/storefront-blank/src/components/ForgeErrorBeacon.tsx +102 -0
  51. package/templates/storefront-blank/src/components/ForgeTracker.tsx +479 -0
  52. package/templates/storefront-blank/src/components/ForgecartDesigner.tsx +43 -0
  53. package/templates/storefront-blank/src/components/LocaleLink.tsx +49 -0
  54. package/templates/storefront-blank/src/instrumentation.ts +109 -0
  55. package/templates/storefront-blank/src/lib/account/account-link.ts +76 -0
  56. package/templates/storefront-blank/src/lib/account/account-state.ts +152 -0
  57. package/templates/storefront-blank/src/lib/account/register-state.ts +133 -0
  58. package/templates/storefront-blank/src/lib/account/reset-password-state.ts +111 -0
  59. package/templates/storefront-blank/src/lib/account/verify-state.ts +56 -0
  60. package/templates/storefront-blank/src/lib/account-actions.ts +76 -0
  61. package/templates/storefront-blank/src/lib/account-session.ts +114 -0
  62. package/templates/storefront-blank/src/lib/action-result.ts +30 -0
  63. package/templates/storefront-blank/src/lib/asset-alt.ts +34 -0
  64. package/templates/storefront-blank/src/lib/backend-actions.ts +20 -0
  65. package/templates/storefront-blank/src/lib/backend-client.ts +47 -0
  66. package/templates/storefront-blank/src/lib/cart-context.tsx +236 -0
  67. package/templates/storefront-blank/src/lib/checkout-session.ts +185 -0
  68. package/templates/storefront-blank/src/lib/content/page-metadata.ts +113 -0
  69. package/templates/storefront-blank/src/lib/content/render-fields.tsx +256 -0
  70. package/templates/storefront-blank/src/lib/content/resolve-page.ts +143 -0
  71. package/templates/storefront-blank/src/lib/error-messages.ts +118 -0
  72. package/templates/storefront-blank/src/lib/experiments.ts +333 -0
  73. package/templates/storefront-blank/src/lib/forgecart.ts +464 -0
  74. package/templates/storefront-blank/src/lib/format.ts +89 -0
  75. package/templates/storefront-blank/src/lib/identify-forward.ts +152 -0
  76. package/templates/storefront-blank/src/lib/locale/channel-locales-loader.ts +169 -0
  77. package/templates/storefront-blank/src/lib/locale/channel-locales-map.ts +46 -0
  78. package/templates/storefront-blank/src/lib/locale/channel-locales.ts +191 -0
  79. package/templates/storefront-blank/src/lib/locale/grammar.ts +194 -0
  80. package/templates/storefront-blank/src/lib/locale/localized-path.ts +55 -0
  81. package/templates/storefront-blank/src/lib/locale/middleware-plan.ts +107 -0
  82. package/templates/storefront-blank/src/lib/locale/request-binding.ts +80 -0
  83. package/templates/storefront-blank/src/lib/locale/request-locale.ts +66 -0
  84. package/templates/storefront-blank/src/lib/marketing-params.ts +213 -0
  85. package/templates/storefront-blank/src/lib/money.ts +50 -0
  86. package/templates/storefront-blank/src/lib/seo/alternates.ts +123 -0
  87. package/templates/storefront-blank/src/lib/seo/json-ld.ts +266 -0
  88. package/templates/storefront-blank/src/lib/seo/metadata.ts +419 -0
  89. package/templates/storefront-blank/src/lib/seo/noindex.ts +223 -0
  90. package/templates/storefront-blank/src/lib/seo/public-origin.ts +166 -0
  91. package/templates/storefront-blank/src/lib/seo/redirect-plan.ts +86 -0
  92. package/templates/storefront-blank/src/lib/seo/resolve-path.ts +107 -0
  93. package/templates/storefront-blank/src/lib/seo/scaffolded-routes.ts +83 -0
  94. package/templates/storefront-blank/src/lib/seo/sidecar.ts +75 -0
  95. package/templates/storefront-blank/src/lib/seo/site-verification.ts +98 -0
  96. package/templates/storefront-blank/src/lib/seo/sitemap-cache.ts +114 -0
  97. package/templates/storefront-blank/src/lib/seo/sitemap-entries.ts +310 -0
  98. package/templates/storefront-blank/src/lib/session-actions.ts +61 -0
  99. package/templates/storefront-blank/src/lib/session-cookies.ts +98 -0
  100. package/templates/storefront-blank/src/lib/shop-config.ts +51 -0
  101. package/templates/storefront-blank/src/lib/shop-session.ts +204 -0
  102. package/templates/storefront-blank/src/lib/track-forward.ts +200 -0
  103. package/templates/storefront-blank/src/lib/uuid.ts +19 -0
  104. package/templates/storefront-blank/src/middleware.ts +379 -0
  105. package/templates/storefront-blank/src/seo/redirects.ts +44 -0
  106. package/templates/storefront-blank/src/seo/static-routes.ts +12 -0
  107. package/templates/storefront-blank/src/server/app.module.ts +18 -0
  108. package/templates/storefront-blank/src/server/backend-api.ts +21 -0
  109. package/templates/storefront-blank/src/server/backend-method.decorator.ts +23 -0
  110. package/templates/storefront-blank/src/server/bootstrap.ts +122 -0
  111. package/templates/storefront-blank/src/server/forge/live-revision.ts +158 -0
  112. package/templates/storefront-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
  113. package/templates/storefront-blank/src/server/forgecart/forgecart.module.ts +9 -0
  114. package/templates/storefront-blank/src/server/runner.ts +90 -0
  115. package/templates/storefront-blank/src/server/types.ts +36 -0
  116. package/templates/storefront-blank/tsconfig.json +25 -0
  117. package/templates/storefront-blank-sdk-floor.json +1174 -0
  118. package/templates/storefront-shadcn-blank/.forgecartignore +2 -0
  119. package/templates/storefront-shadcn-blank/Procfile +1 -0
  120. package/templates/storefront-shadcn-blank/README.md +93 -0
  121. package/templates/storefront-shadcn-blank/SEO-MIGRATION.md +718 -0
  122. package/templates/storefront-shadcn-blank/_gitignore +21 -0
  123. package/templates/storefront-shadcn-blank/components.json +20 -0
  124. package/templates/storefront-shadcn-blank/next.config.js +105 -0
  125. package/templates/storefront-shadcn-blank/package.json +42 -0
  126. package/templates/storefront-shadcn-blank/postcss.config.js +5 -0
  127. package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/identify/route.ts +205 -0
  128. package/templates/storefront-shadcn-blank/src/app/%5F%5Ffc/track/route.ts +189 -0
  129. package/templates/storefront-shadcn-blank/src/app/%5F%5Fforge_beacon/route.ts +87 -0
  130. package/templates/storefront-shadcn-blank/src/app/api/%5F%5Fbackend/methods/route.ts +27 -0
  131. package/templates/storefront-shadcn-blank/src/app/error.tsx +23 -0
  132. package/templates/storefront-shadcn-blank/src/app/global-error.tsx +23 -0
  133. package/templates/storefront-shadcn-blank/src/app/globals.css +156 -0
  134. package/templates/storefront-shadcn-blank/src/app/layout.tsx +102 -0
  135. package/templates/storefront-shadcn-blank/src/app/page.tsx +53 -0
  136. package/templates/storefront-shadcn-blank/src/app/pages/[slug]/not-found.tsx +24 -0
  137. package/templates/storefront-shadcn-blank/src/app/pages/[slug]/page.tsx +115 -0
  138. package/templates/storefront-shadcn-blank/src/app/ping/route.ts +21 -0
  139. package/templates/storefront-shadcn-blank/src/app/robots.ts +69 -0
  140. package/templates/storefront-shadcn-blank/src/app/sitemap.ts +107 -0
  141. package/templates/storefront-shadcn-blank/src/components/ForgeErrorBeacon.tsx +102 -0
  142. package/templates/storefront-shadcn-blank/src/components/ForgeTracker.tsx +479 -0
  143. package/templates/storefront-shadcn-blank/src/components/ForgecartDesigner.tsx +43 -0
  144. package/templates/storefront-shadcn-blank/src/components/LocaleLink.tsx +49 -0
  145. package/templates/storefront-shadcn-blank/src/components/ui/alert.tsx +75 -0
  146. package/templates/storefront-shadcn-blank/src/components/ui/badge.tsx +40 -0
  147. package/templates/storefront-shadcn-blank/src/components/ui/button.tsx +64 -0
  148. package/templates/storefront-shadcn-blank/src/components/ui/card.tsx +28 -0
  149. package/templates/storefront-shadcn-blank/src/components/ui/input.tsx +26 -0
  150. package/templates/storefront-shadcn-blank/src/components/ui/label.tsx +22 -0
  151. package/templates/storefront-shadcn-blank/src/components/ui/native-select.tsx +27 -0
  152. package/templates/storefront-shadcn-blank/src/components/ui/skeleton.tsx +21 -0
  153. package/templates/storefront-shadcn-blank/src/components/ui/utils.ts +16 -0
  154. package/templates/storefront-shadcn-blank/src/instrumentation.ts +109 -0
  155. package/templates/storefront-shadcn-blank/src/lib/account/account-link.ts +76 -0
  156. package/templates/storefront-shadcn-blank/src/lib/account/account-state.ts +152 -0
  157. package/templates/storefront-shadcn-blank/src/lib/account/register-state.ts +133 -0
  158. package/templates/storefront-shadcn-blank/src/lib/account/reset-password-state.ts +111 -0
  159. package/templates/storefront-shadcn-blank/src/lib/account/verify-state.ts +56 -0
  160. package/templates/storefront-shadcn-blank/src/lib/account-actions.ts +76 -0
  161. package/templates/storefront-shadcn-blank/src/lib/account-session.ts +114 -0
  162. package/templates/storefront-shadcn-blank/src/lib/action-result.ts +30 -0
  163. package/templates/storefront-shadcn-blank/src/lib/asset-alt.ts +34 -0
  164. package/templates/storefront-shadcn-blank/src/lib/backend-actions.ts +20 -0
  165. package/templates/storefront-shadcn-blank/src/lib/backend-client.ts +47 -0
  166. package/templates/storefront-shadcn-blank/src/lib/cart-context.tsx +236 -0
  167. package/templates/storefront-shadcn-blank/src/lib/checkout-session.ts +185 -0
  168. package/templates/storefront-shadcn-blank/src/lib/content/page-metadata.ts +113 -0
  169. package/templates/storefront-shadcn-blank/src/lib/content/render-fields.tsx +256 -0
  170. package/templates/storefront-shadcn-blank/src/lib/content/resolve-page.ts +143 -0
  171. package/templates/storefront-shadcn-blank/src/lib/error-messages.ts +118 -0
  172. package/templates/storefront-shadcn-blank/src/lib/experiments.ts +333 -0
  173. package/templates/storefront-shadcn-blank/src/lib/forgecart.ts +464 -0
  174. package/templates/storefront-shadcn-blank/src/lib/format.ts +89 -0
  175. package/templates/storefront-shadcn-blank/src/lib/identify-forward.ts +152 -0
  176. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-loader.ts +169 -0
  177. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales-map.ts +46 -0
  178. package/templates/storefront-shadcn-blank/src/lib/locale/channel-locales.ts +191 -0
  179. package/templates/storefront-shadcn-blank/src/lib/locale/grammar.ts +194 -0
  180. package/templates/storefront-shadcn-blank/src/lib/locale/localized-path.ts +55 -0
  181. package/templates/storefront-shadcn-blank/src/lib/locale/middleware-plan.ts +107 -0
  182. package/templates/storefront-shadcn-blank/src/lib/locale/request-binding.ts +80 -0
  183. package/templates/storefront-shadcn-blank/src/lib/locale/request-locale.ts +66 -0
  184. package/templates/storefront-shadcn-blank/src/lib/marketing-params.ts +213 -0
  185. package/templates/storefront-shadcn-blank/src/lib/money.ts +50 -0
  186. package/templates/storefront-shadcn-blank/src/lib/seo/alternates.ts +123 -0
  187. package/templates/storefront-shadcn-blank/src/lib/seo/json-ld.ts +266 -0
  188. package/templates/storefront-shadcn-blank/src/lib/seo/metadata.ts +419 -0
  189. package/templates/storefront-shadcn-blank/src/lib/seo/noindex.ts +223 -0
  190. package/templates/storefront-shadcn-blank/src/lib/seo/public-origin.ts +166 -0
  191. package/templates/storefront-shadcn-blank/src/lib/seo/redirect-plan.ts +86 -0
  192. package/templates/storefront-shadcn-blank/src/lib/seo/resolve-path.ts +107 -0
  193. package/templates/storefront-shadcn-blank/src/lib/seo/scaffolded-routes.ts +83 -0
  194. package/templates/storefront-shadcn-blank/src/lib/seo/sidecar.ts +75 -0
  195. package/templates/storefront-shadcn-blank/src/lib/seo/site-verification.ts +98 -0
  196. package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-cache.ts +114 -0
  197. package/templates/storefront-shadcn-blank/src/lib/seo/sitemap-entries.ts +310 -0
  198. package/templates/storefront-shadcn-blank/src/lib/session-actions.ts +61 -0
  199. package/templates/storefront-shadcn-blank/src/lib/session-cookies.ts +98 -0
  200. package/templates/storefront-shadcn-blank/src/lib/shop-config.ts +51 -0
  201. package/templates/storefront-shadcn-blank/src/lib/shop-session.ts +204 -0
  202. package/templates/storefront-shadcn-blank/src/lib/track-forward.ts +200 -0
  203. package/templates/storefront-shadcn-blank/src/lib/uuid.ts +19 -0
  204. package/templates/storefront-shadcn-blank/src/middleware.ts +379 -0
  205. package/templates/storefront-shadcn-blank/src/seo/redirects.ts +44 -0
  206. package/templates/storefront-shadcn-blank/src/seo/static-routes.ts +12 -0
  207. package/templates/storefront-shadcn-blank/src/server/app.module.ts +18 -0
  208. package/templates/storefront-shadcn-blank/src/server/backend-api.ts +21 -0
  209. package/templates/storefront-shadcn-blank/src/server/backend-method.decorator.ts +23 -0
  210. package/templates/storefront-shadcn-blank/src/server/bootstrap.ts +122 -0
  211. package/templates/storefront-shadcn-blank/src/server/forge/live-revision.ts +158 -0
  212. package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart-client.factory.ts +73 -0
  213. package/templates/storefront-shadcn-blank/src/server/forgecart/forgecart.module.ts +9 -0
  214. package/templates/storefront-shadcn-blank/src/server/runner.ts +90 -0
  215. package/templates/storefront-shadcn-blank/src/server/types.ts +36 -0
  216. package/templates/storefront-shadcn-blank/tsconfig.json +25 -0
  217. package/templates/storefront-shadcn-blank-sdk-floor.json +1174 -0
  218. package/templates/template-set.json +15 -0
@@ -0,0 +1,27 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ import { getBackend } from '../../../../server/bootstrap';
4
+
5
+ /**
6
+ * DEV-ONLY registry listing — the agent's verification surface for backend
7
+ * features. After authoring a method, the agent probes this route and
8
+ * confirms the new name is listed; a boot failure (the api-map drift check)
9
+ * surfaces here as a 500 carrying the actionable message.
10
+ *
11
+ * Gated on `NODE_ENV === 'development'`: the literal check is inlined by
12
+ * the bundler, so a production build answers 404 and dead-code-eliminates
13
+ * the body. Method NAMES are the only thing exposed — never inputs,
14
+ * outputs, or configuration.
15
+ */
16
+ export async function GET(): Promise<NextResponse> {
17
+ if (process.env.NODE_ENV !== 'development') {
18
+ return NextResponse.json({ error: 'not found' }, { status: 404 });
19
+ }
20
+ try {
21
+ const { registry } = await getBackend();
22
+ return NextResponse.json({ methods: [...registry.keys()].sort() });
23
+ } catch (error) {
24
+ const reason = error instanceof Error ? error.message : String(error);
25
+ return NextResponse.json({ error: reason }, { status: 500 });
26
+ }
27
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Route-level backstop boundary. The action layer never throws (typed
5
+ * `ActionResult` envelopes end at inline UI), so landing here means a RENDER
6
+ * failed — e.g. a Server Component read against an unreachable shop API.
7
+ * Recovery is a re-render via `reset()`.
8
+ */
9
+ export default function RouteError({ reset }: { error: Error; reset: () => void }) {
10
+ return (
11
+ <div className="mx-auto max-w-xl space-y-4 py-16 text-center">
12
+ <h1 className="text-2xl font-bold tracking-tight text-base-content">Something went wrong</h1>
13
+ <p className="text-base-content/60">
14
+ The store could not load this page. Please try again in a moment.
15
+ </p>
16
+ <button type="button" className="btn btn-primary" onClick={() => reset()}>
17
+ Try again
18
+ </button>
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Root backstop: replaces the whole document when the root layout itself
5
+ * fails to render. Must own its own <html>/<body> — the layout that normally
6
+ * provides them is the thing that crashed. Styled inline (no globals.css —
7
+ * that import lives in the crashed layout).
8
+ */
9
+ export default function GlobalError({ reset }: { error: Error; reset: () => void }) {
10
+ return (
11
+ <html lang="en">
12
+ <body
13
+ style={{ fontFamily: 'system-ui, sans-serif', padding: '4rem 1rem', textAlign: 'center' }}
14
+ >
15
+ <h1>Something went wrong</h1>
16
+ <p>The store is temporarily unavailable. Please try again in a moment.</p>
17
+ <button type="button" onClick={() => reset()} style={{ padding: '0.5rem 1.25rem' }}>
18
+ Try again
19
+ </button>
20
+ </body>
21
+ </html>
22
+ );
23
+ }
@@ -0,0 +1,110 @@
1
+ @import "tailwindcss";
2
+
3
+ @plugin "daisyui" {
4
+ themes: false;
5
+ }
6
+
7
+ /*
8
+ * ═══════════════════════════════════════════════════════════════════════════
9
+ * STOREFRONT THEME — the single brand mutation point (design-system contract)
10
+ * ═══════════════════════════════════════════════════════════════════════════
11
+ *
12
+ * Every component in this template speaks SEMANTIC daisyUI classes
13
+ * (bg-base-100, btn-primary, badge-secondary, alert, steps, …), so the block
14
+ * below is the ONE place a brand lives. Restyling the storefront = editing
15
+ * these tokens — nothing else. Never scatter raw palette utilities
16
+ * (bg-gray-900, text-white, #hex) through components; if a component needs a
17
+ * color, it needs a TOKEN.
18
+ *
19
+ * Token groups and what they drive:
20
+ * --color-base-* page substrate (200), raised surfaces (100), borders
21
+ * (300) and default text (base-content)
22
+ * --color-primary the brand action color: CTAs (btn-primary), active
23
+ * selections, the cart count badge
24
+ * --color-secondary supporting emphasis (subscription badges, secondary
25
+ * chips)
26
+ * --color-accent reserved highlight — used sparingly by new
27
+ * components; keep it distinct from primary
28
+ * --color-neutral high-contrast neutral fills (rarely needed directly)
29
+ * info/success/warning/ status semantics (alerts, validators, error text) —
30
+ * error conventional hues, usually NOT part of a rebrand
31
+ * --radius-* selector = small controls (checkbox/tabs), field =
32
+ * inputs/buttons, box = cards/modals
33
+ * --size-*, --border control density and border weight
34
+ * --depth, --noise daisyUI effect toggles (flat baseline: 0)
35
+ *
36
+ * The checkout surfaces consume the same set (steps, fieldset/validator
37
+ * inputs, radio-card selection rings, alert) — the block enumerates every
38
+ * variable daisyUI components resolve, so no class ever falls back outside
39
+ * the contract.
40
+ *
41
+ * This baseline is deliberately UNBRANDED (near-black monochrome on soft
42
+ * gray): it is the neutral canvas an agent rebrands per store. Extension
43
+ * points this contract keeps open: additional themes are further
44
+ * `@plugin "daisyui/theme"` blocks (a dark theme adds `prefersdark: true`),
45
+ * and the visual editor writes these same tokens ("theme main colors" =
46
+ * primary / secondary / accent here).
47
+ *
48
+ * A brand font goes in the `@theme` block after the theme: swap the
49
+ * `--font-sans` stack (and add the font's `@font-face`/import above it).
50
+ */
51
+ @plugin "daisyui/theme" {
52
+ name: "storefront";
53
+ default: true;
54
+ color-scheme: light;
55
+
56
+ /* Base — white surfaces over a soft-gray substrate, near-black text. */
57
+ --color-base-100: #ffffff;
58
+ --color-base-200: #f9fafb;
59
+ --color-base-300: #e5e7eb;
60
+ --color-base-content: #111827;
61
+
62
+ /* Actions — unbranded monochrome. */
63
+ --color-primary: #111827;
64
+ --color-primary-content: #ffffff;
65
+ --color-secondary: #374151;
66
+ --color-secondary-content: #ffffff;
67
+ --color-accent: #1f2937;
68
+ --color-accent-content: #ffffff;
69
+ --color-neutral: #111827;
70
+ --color-neutral-content: #f9fafb;
71
+
72
+ /* Status — conventional semantics, not brand. */
73
+ --color-info: #2563eb;
74
+ --color-info-content: #ffffff;
75
+ --color-success: #16a34a;
76
+ --color-success-content: #ffffff;
77
+ --color-warning: #d97706;
78
+ --color-warning-content: #ffffff;
79
+ --color-error: #dc2626;
80
+ --color-error-content: #ffffff;
81
+
82
+ /* Shape — matches the template's previous rounded-md/lg scale. */
83
+ --radius-selector: 0.375rem;
84
+ --radius-field: 0.375rem;
85
+ --radius-box: 0.5rem;
86
+ --size-selector: 0.25rem;
87
+ --size-field: 0.25rem;
88
+ --border: 1px;
89
+ --depth: 0;
90
+ --noise: 0;
91
+ }
92
+
93
+ /* Brand typography mutation point — swap the stack to give the store a font. */
94
+ @theme {
95
+ --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
96
+ "Segoe UI Symbol", "Noto Color Emoji";
97
+ }
98
+
99
+ /*
100
+ * Base document behavior (structural — NOT part of the theme contract).
101
+ * A horizontal touchpad glide must never trigger the browser's swipe-back
102
+ * navigation: mid-checkout that discards the shopper's in-progress state
103
+ * (the refresh-resume path recovers the payment step, but preventing the
104
+ * accidental navigation is the real fix). The back button and keyboard
105
+ * navigation are untouched — only the overscroll gesture is consumed.
106
+ */
107
+ html,
108
+ body {
109
+ overscroll-behavior-x: none;
110
+ }
@@ -0,0 +1,102 @@
1
+ import type { Metadata } from 'next';
2
+ import { notFound, permanentRedirect } from 'next/navigation';
3
+ import { Suspense, type ReactNode } from 'react';
4
+
5
+ import { ForgeErrorBeacon } from '../components/ForgeErrorBeacon';
6
+ import { ForgeTracker } from '../components/ForgeTracker';
7
+ import { ForgecartDesigner } from '../components/ForgecartDesigner';
8
+ import { getRequestLocale } from '../lib/locale/request-binding';
9
+ import { shellMetadata } from '../lib/seo/metadata';
10
+ import { getSiteVerifications } from '../lib/seo/site-verification';
11
+ import { FORGE_REVISION_META, resolveLiveRevision } from '../server/forge/live-revision';
12
+
13
+ import './globals.css';
14
+
15
+ /**
16
+ * The deployment-wide metadata floor.
17
+ *
18
+ * Resolved per REQUEST rather than declared as a static `metadata` object,
19
+ * because everything in it arrives in the environment of the running server
20
+ * rather than of the machine that built the bundle: the public origin from
21
+ * `.env` (see `lib/seo/public-origin.ts`) and the shop name from the channel
22
+ * itself. A build-time answer would bake the builder's empty environment into
23
+ * every store shipped from that image.
24
+ *
25
+ * The title is the STORE's name, so a merchant who scaffolds a page and writes
26
+ * no metadata gets their own store in the browser tab. Route-specific claims
27
+ * live on the routes; this is only the floor, and it deliberately carries no
28
+ * canonical (it does not know which route it is wrapping).
29
+ *
30
+ * The site-verification placements (#1530) are read on the same request cache
31
+ * and handed to the SAME composer, so `lib/seo/metadata.ts` stays the only
32
+ * thing in this template that turns a placement into a `<meta>`. They ride the
33
+ * FLOOR rather than a route, because the verifier chooses which page it fetches
34
+ * and a tag on only some of them is a verification that works until it does not.
35
+ *
36
+ * It also carries the DEV-ONLY revision stamp (#847), written where the page is
37
+ * RENDERED: the browser beacon reads it back at throw time, so a promote landing
38
+ * between serve and throw cannot make the report blame the new revision for the
39
+ * old one's crash. Dev-gated by the literal `NODE_ENV` check, so a production
40
+ * build eliminates the resolution and a deployed storefront leaks no revision.
41
+ * Unresolvable is a NORMAL answer and means no tag.
42
+ */
43
+ export async function generateMetadata(): Promise<Metadata> {
44
+ const { shopName, channelResolved } = await getRequestLocale();
45
+ const siteVerifications = await getSiteVerifications();
46
+ const shell = shellMetadata({ shopName, channelResolved, siteVerifications });
47
+ if (process.env.NODE_ENV !== 'development') return shell;
48
+ const revision = resolveLiveRevision();
49
+ if (revision === null) return shell;
50
+ // MERGED into the shell's `other`, never written over it: that key carries the
51
+ // site-verification placements (#1530), and a storefront pod serves through
52
+ // `npm run dev`, so an overwrite here would strip the provider's tag from
53
+ // every page its verifier can fetch.
54
+ return { ...shell, other: { ...shell.other, [FORGE_REVISION_META]: revision } };
55
+ }
56
+
57
+ /**
58
+ * The root layout of a blank storefront (#3480) — the platform's shell with no
59
+ * example storefront on it: no header, no footer navigation and no cart. What a
60
+ * blank workspace builds on is all here: the document language, the locale
61
+ * contract, the metadata floor above and the invisible client plumbing.
62
+ *
63
+ * DYNAMIC on every route, by design (D8). It reads the request locale to write
64
+ * `<html lang>`, and `lang` is an attribute on the document root: structurally
65
+ * outside every Suspense boundary, so it cannot come from a streamed hole.
66
+ * `next.config.js` carries the reasoning — Cache Components is off because the
67
+ * escape hatch it would force (a `<Suspense>` above `<body>`) is exactly what
68
+ * makes the two throws below unable to set a status.
69
+ *
70
+ * Those throws are the locale contract's teeth. Raised HERE, in the shell,
71
+ * they reach the one place Next assigns a status: `permanentRedirect` gives a
72
+ * real 308 with a `Location`, and `notFound` a real 404. Raised from inside
73
+ * any boundary they would resolve as client-rendered content instead, and the
74
+ * response would be a 200 carrying a 404 page — a soft-404, which a crawler
75
+ * indexes rather than drops.
76
+ */
77
+ export default async function RootLayout({ children }: { children: ReactNode }) {
78
+ const { decision, binding } = await getRequestLocale();
79
+ if (decision.kind === 'redirect') permanentRedirect(decision.to);
80
+ if (decision.kind === 'notFound') notFound();
81
+
82
+ return (
83
+ <html lang={binding.locale}>
84
+ <body className="min-h-screen bg-base-200 text-base-content antialiased">
85
+ <main className="mx-auto max-w-6xl px-4 py-10">{children}</main>
86
+ {/* Invisible client plumbing. The tracker reads usePathname(), which
87
+ needs a boundary of its own — null fallback, these render nothing.
88
+ This must stay INSIDE <body>: a boundary above it would make the
89
+ shell resumable and cost the statuses above. */}
90
+ <Suspense fallback={null}>
91
+ <ForgecartDesigner />
92
+ <ForgeErrorBeacon />
93
+ <ForgeTracker
94
+ enabled={Boolean(
95
+ process.env.FORGECART_SHOP_API_URL && process.env.FORGECART_CHANNEL_TOKEN,
96
+ )}
97
+ />
98
+ </Suspense>
99
+ </body>
100
+ </html>
101
+ );
102
+ }
@@ -0,0 +1,52 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ import { getRequestLocale } from '../lib/locale/request-binding';
4
+ import { staticPathsByLocale } from '../lib/seo/alternates';
5
+ import { pageTitle, routeMetadata } from '../lib/seo/metadata';
6
+ import type { RouteSearchParams } from '../lib/seo/noindex';
7
+
8
+ /**
9
+ * The landing surface is indexable — unless the URL carries query state (an
10
+ * experiment pin, a preview) that makes it a VIEW of the page competing with
11
+ * the page's own address.
12
+ *
13
+ * `title: null` because the store's landing page has no name of its own: it IS
14
+ * the store, so it takes the shop name alone rather than composing something
15
+ * like `Home | Acme`. The description is the shell's, inherited by omission.
16
+ */
17
+ export async function generateMetadata({
18
+ searchParams,
19
+ }: {
20
+ searchParams: Promise<RouteSearchParams>;
21
+ }): Promise<Metadata> {
22
+ const [resolvedSearchParams, { binding, languageCodes, shopName, channelResolved }] =
23
+ await Promise.all([searchParams, getRequestLocale()]);
24
+ return routeMetadata({
25
+ binding,
26
+ shopName,
27
+ channelResolved,
28
+ // The canonical is built from the BASE path, so `/?fc-exp=hero:b` declares
29
+ // `/` as its address — the query-state copy points at the page it is a view
30
+ // of, which is the whole reason those URLs are noindex rather than ignored.
31
+ pathname: '/',
32
+ pathsByLocale: staticPathsByLocale(languageCodes, '/'),
33
+ searchParams: resolvedSearchParams,
34
+ title: null,
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Home — the blank storefront's one page (#3480): the store's name as its one
40
+ * heading, and nothing else. No example content to delete: everything the
41
+ * merchant builds starts here, on the shell's platform plumbing. The heading
42
+ * reads the name the document title carries, so a channel that has not named
43
+ * its store still renders one.
44
+ */
45
+ export default async function HomePage() {
46
+ const { shopName } = await getRequestLocale();
47
+ return (
48
+ <h1 className="text-4xl font-bold tracking-tight text-balance">
49
+ {pageTitle(null, shopName)}
50
+ </h1>
51
+ );
52
+ }
@@ -0,0 +1,23 @@
1
+ import { LocaleLink } from '../../../components/LocaleLink';
2
+ import { getRequestLocale } from '../../../lib/locale/request-binding';
3
+
4
+ /**
5
+ * What a visitor sees at a page URL that holds nothing: an unknown route, or a
6
+ * page whose content is not published (or not published YET). The copy does not
7
+ * distinguish them on purpose — "this is scheduled for Tuesday" is the
8
+ * merchant's information, not the public's.
9
+ */
10
+ export default async function AcfPageNotFound() {
11
+ const { binding } = await getRequestLocale();
12
+ return (
13
+ <div className="space-y-4">
14
+ <h1 className="text-2xl font-bold tracking-tight text-base-content">Page not found</h1>
15
+ <p className="text-base-content/60">
16
+ We couldn&apos;t find that page. It may have been moved or taken down.
17
+ </p>
18
+ <LocaleLink href="/" locale={binding} className="btn btn-primary">
19
+ Back to the shop
20
+ </LocaleLink>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,115 @@
1
+ import type { Metadata } from 'next';
2
+ import { notFound } from 'next/navigation';
3
+ import { cache } from 'react';
4
+
5
+ import { pageContentClaims } from '../../../lib/content/page-metadata';
6
+ import { PageFields } from '../../../lib/content/render-fields';
7
+ import { resolvePage } from '../../../lib/content/resolve-page';
8
+ import {
9
+ getPageByRoute,
10
+ getPageEntries,
11
+ type PageEntry,
12
+ type PageGroup,
13
+ } from '../../../lib/forgecart';
14
+ import { getRequestLocale } from '../../../lib/locale/request-binding';
15
+ import { routeMetadata } from '../../../lib/seo/metadata';
16
+ import type { RouteSearchParams } from '../../../lib/seo/noindex';
17
+ import { PAGE_ROUTE_PREFIX } from '../../../lib/seo/sitemap-entries';
18
+
19
+ /**
20
+ * The merchant's own pages (#1934, epic launch#50), served at the route the
21
+ * shop API minted for the page definition.
22
+ *
23
+ * Locale-prefixed addresses land here too and cost nothing: the route tree is
24
+ * FLAT by law (#1041) and the middleware rewrites `/de/pages/x` onto this same
25
+ * file with the locale carried on a request header, so `/pages/x` and every
26
+ * `/<xx>/pages/x` are one route with one decision.
27
+ *
28
+ * FORCE-DYNAMIC, explicitly. "An unpublished page answers 404 on the NEXT
29
+ * request" is this feature's contract, and a contract must not rest on the
30
+ * incidental fact that some module downstream happens to read `headers()`. No
31
+ * `revalidate`, no `use cache`: a window here would serve a page the merchant
32
+ * has already taken down, which is the one failure this route may not have.
33
+ */
34
+ export const dynamic = 'force-dynamic';
35
+
36
+ /**
37
+ * The page's definition AND its visible record, read once per request.
38
+ *
39
+ * Both halves together, because the 404 decision needs both and Next invokes
40
+ * `generateMetadata` and the page as separate calls — without React's request
41
+ * cache the two would resolve independently, and a page unpublished between
42
+ * them would emit indexable metadata for a URL that then 404s.
43
+ *
44
+ * The entries read is skipped when nothing is mounted at the route: there is no
45
+ * definition code to ask about, and the resolution's answer is already settled.
46
+ */
47
+ const pageForRequest = cache(
48
+ async (route: string): Promise<{ group: PageGroup | null; entries: PageEntry[] }> => {
49
+ const group = await getPageByRoute(route);
50
+ if (group === null) return { group: null, entries: [] };
51
+ return { group, entries: await getPageEntries(group.code) };
52
+ },
53
+ );
54
+
55
+ /**
56
+ * A page's metadata comes from the same resolution the route acts on, so the
57
+ * head and the status can never disagree about what this URL is.
58
+ *
59
+ * Its CONTENT claims — title, description, social card, alternates and
60
+ * indexability — are the record's per-language SEO sidecar (#1375), decided in
61
+ * `lib/content/page-metadata.ts` by the rules the product route uses: the
62
+ * definition's `seoSources` mapping and the merchant's stored overrides arrive
63
+ * already resolved into each language, and a language with no copy of its own
64
+ * renders as a fallback that claims nothing (noindex, no canonical, never an
65
+ * hreflang alternate). This file only dispatches.
66
+ */
67
+ export async function generateMetadata({
68
+ params,
69
+ searchParams,
70
+ }: {
71
+ params: Promise<{ slug: string }>;
72
+ searchParams: Promise<RouteSearchParams>;
73
+ }): Promise<Metadata> {
74
+ const [{ slug }, resolvedSearchParams, { binding, languageCodes, shopName, channelResolved }] =
75
+ await Promise.all([params, searchParams, getRequestLocale()]);
76
+ const { group, entries } = await pageForRequest(slug);
77
+ const resolution = resolvePage({ requestedRoute: slug, group, entries });
78
+ const pathname = `${PAGE_ROUTE_PREFIX}/${slug}`;
79
+
80
+ return routeMetadata({
81
+ binding,
82
+ shopName,
83
+ channelResolved,
84
+ pathname,
85
+ searchParams: resolvedSearchParams,
86
+ ...pageContentClaims(resolution, { locale: binding.locale, languageCodes, pathname }),
87
+ });
88
+ }
89
+
90
+ /**
91
+ * The resolution runs in the page SHELL, above any boundary, and that placement
92
+ * is the whole correctness of this route's status codes: `notFound()` can only
93
+ * set a status where Next assigns one, and raised from inside a Suspense
94
+ * boundary the shell has already flushed a 200 — a crawler then INDEXES the
95
+ * not-found page instead of dropping it (the same law `products/[slug]`
96
+ * carries, measured there).
97
+ *
98
+ * There is no boundary at all here, and that follows from the same fact: the
99
+ * two reads ARE this page, so streaming a skeleton would buy nothing while the
100
+ * only content loaded.
101
+ */
102
+ export default async function AcfPage({ params }: { params: Promise<{ slug: string }> }) {
103
+ const { slug } = await params;
104
+ const { group, entries } = await pageForRequest(slug);
105
+ const resolution = resolvePage({ requestedRoute: slug, group, entries });
106
+
107
+ if (resolution.kind === 'notFound') notFound();
108
+
109
+ return (
110
+ <article className="space-y-8">
111
+ <h1 className="text-3xl font-bold tracking-tight text-balance">{resolution.group.name}</h1>
112
+ <PageFields definitions={resolution.group.fieldDefinitions} entry={resolution.entry} />
113
+ </article>
114
+ );
115
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Liveness ping for the in-pod dev-server readiness probe.
3
+ *
4
+ * Returns a static 200 with no layout, data fetch, or session. The workspace's
5
+ * readiness probe hits THIS route (not `/`) on a fast cadence while the store is
6
+ * STARTING and then a slow liveness cadence forever — and `/` renders the
7
+ * cart-aware layout, whose `activeOrder` call MATERIALISES an active order on every
8
+ * request. Probing `/` therefore minted a throwaway order on every poll; probing a
9
+ * route handler renders nothing and touches no session, so readiness polling stays
10
+ * side-effect-free.
11
+ *
12
+ * Request access keeps it out of any static prerender so a production build serves
13
+ * it on demand exactly like `next dev` does — the probe always gets a fresh 200.
14
+ */
15
+
16
+ export function GET(): Response {
17
+ return new Response('ok', {
18
+ status: 200,
19
+ headers: { 'content-type': 'text/plain' },
20
+ });
21
+ }
@@ -0,0 +1,69 @@
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ import { NOINDEX_PATHS, NOINDEX_QUERY_KEYS } from '../lib/seo/noindex';
4
+ import { getPublicOrigin } from '../lib/seo/public-origin';
5
+
6
+ /**
7
+ * `robots.txt` (#1347, epic launch#54 W1-9) — the crawler-facing half of the
8
+ * indexability contract in `lib/seo/noindex.ts`.
9
+ *
10
+ * FORCE-DYNAMIC, and that is load-bearing rather than cautious. Next
11
+ * pre-renders metadata routes at build time by default, and a storefront is
12
+ * built once then run with its per-channel environment supplied afterwards. A
13
+ * build-time answer would freeze the BUILDER's environment — which never has a
14
+ * channel's public origin — into `Disallow: /` for every store shipped from
15
+ * that image, permanently, with no way to correct it short of a rebuild. The
16
+ * scaffold is frozen at provisioning time (#1041), so "permanently" is literal.
17
+ */
18
+ export const dynamic = 'force-dynamic';
19
+
20
+ /**
21
+ * Infrastructure routes that are never a destination for a human or a crawler:
22
+ * the tracking relay, the error beacon, the SDK proxy and the health probe.
23
+ *
24
+ * Deliberately NOT derived from the locale grammar's `UNPREFIXED_FIRST_SEGMENTS`
25
+ * despite the overlap. That set answers "which first segments are not a
26
+ * language?" and correctly contains `sitemap.xml`, `robots.txt`, `_next` and
27
+ * `favicon.ico` — every one of which a crawler MUST be allowed to fetch.
28
+ * Sharing one list would make the next infrastructure route silently
29
+ * uncrawlable, or a crawlable asset silently unprefixed.
30
+ */
31
+ const INFRASTRUCTURE_DISALLOW: readonly string[] = [
32
+ '/api/',
33
+ '/__fc/',
34
+ '/__forge_beacon',
35
+ '/ping',
36
+ ];
37
+
38
+ /**
39
+ * Without a public origin the storefront asserts nothing, so `robots.txt`
40
+ * refuses the whole tree. With one, it mirrors the noindex set — the page
41
+ * rules are DERIVED from `noindex.ts` rather than restated, because a
42
+ * `robots.txt` that disagrees with the pages' own `robots` metadata is the
43
+ * exact drift this module exists to prevent.
44
+ *
45
+ * The `Sitemap:` line rides the origin BRANCH, not merely the origin value: the
46
+ * URL it names has to be absolute, and the document it points at is empty
47
+ * without an origin. Announcing an empty sitemap is a claim about the catalog;
48
+ * announcing nothing is the absence of one, which is all a deployment that has
49
+ * not been told its own address is entitled to say.
50
+ */
51
+ export default function robots(): MetadataRoute.Robots {
52
+ const publicOrigin = getPublicOrigin();
53
+ if (publicOrigin === null) {
54
+ return { rules: { userAgent: '*', disallow: '/' } };
55
+ }
56
+
57
+ return {
58
+ rules: {
59
+ userAgent: '*',
60
+ allow: '/',
61
+ disallow: [
62
+ ...NOINDEX_PATHS,
63
+ ...INFRASTRUCTURE_DISALLOW,
64
+ ...NOINDEX_QUERY_KEYS.map((key) => `/*?${key}=`),
65
+ ],
66
+ },
67
+ sitemap: `${publicOrigin}/sitemap.xml`,
68
+ };
69
+ }