@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,87 @@
1
+ /**
2
+ * DEV-ONLY same-origin error-beacon forwarder.
3
+ *
4
+ * The storefront's client beacon (`ForgeErrorBeacon`) POSTs a browser-surfaced
5
+ * runtime error here — same-origin, so the browser never learns the pod-internal
6
+ * receiver address. This handler forwards the body SERVER-side to the in-pod
7
+ * workspace-manager receiver at `http://127.0.0.1:<FORGE_BEACON_PORT>/__forge_beacon`,
8
+ * which folds it into the supervisor's runtime state so the shop's recovery brain
9
+ * can originate a fix for a crash the dev-server's stderr scan cannot see.
10
+ *
11
+ * Gated entirely on `NODE_ENV === 'development'`: a deployed `next start` storefront
12
+ * answers 404 here and forwards nothing — the whole beacon path ships only in the
13
+ * in-pod dev-server. Request access keeps it out of any static prerender so it
14
+ * behaves identically under `next dev` and a dev build.
15
+ *
16
+ * Folder name: `%5F%5Fforge_beacon` is URL-encoded `__forge_beacon`. The App Router
17
+ * treats a folder whose name starts with `_` as PRIVATE and leaves it out of routing
18
+ * entirely, so the literal spelling this route shipped with answered the not-found
19
+ * page to every POST — silently, because the client swallows delivery failures by
20
+ * design (#2103). The `%5F` escape is Next's documented way to serve a URL segment
21
+ * that really starts with an underscore, and only the FOLDER may change: the URL is
22
+ * a contract with the in-pod receiver (`StorefrontBeaconService`'s `BEACON_PATH`),
23
+ * which this template's server half (`src/instrumentation.ts`) posts to as well.
24
+ */
25
+
26
+ const BEACON_PORT = process.env.FORGE_BEACON_PORT ?? '3002';
27
+
28
+ /**
29
+ * The revision the reporting page was SERVED at, lifted out of the forwarded
30
+ * body for the log line below, or `null` when the report is untagged.
31
+ *
32
+ * Reads the body it is ALREADY forwarding verbatim rather than re-deriving
33
+ * anything: the value was stamped into the document at render time, and this
34
+ * handler is a forwarder, not a second opinion. The parse is guarded because a
35
+ * malformed body must not change what this route does — the forward still
36
+ * happens and the client still gets its 204, exactly as before. `JSON.parse` is
37
+ * the one throw on this path and it carries no code to classify, which is why it
38
+ * is caught here and nowhere else (`instrumentation.ts` swallows its own boot
39
+ * failure in the same template for the same reason); judging the payload stays
40
+ * the receiver's job.
41
+ */
42
+ function servedRevision(body: string): string | null {
43
+ try {
44
+ const parsed: unknown = JSON.parse(body);
45
+ if (typeof parsed !== 'object' || parsed === null) return null;
46
+ const value = (parsed as { atRevision?: unknown }).atRevision;
47
+ if (typeof value !== 'string' || value.length === 0) return null;
48
+ return value;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ export async function POST(request: Request): Promise<Response> {
55
+ if (process.env.NODE_ENV !== 'development') {
56
+ return new Response(null, { status: 404 });
57
+ }
58
+
59
+ const body = await request.text();
60
+ // Make the dev-server's output ring revision-attributable (#847): this line is
61
+ // written to the ring the supervisor retains and `TailServerLogs` serves, so a
62
+ // human or the recovery brain reading the ring can tell WHICH tree a
63
+ // browser-surfaced crash belongs to instead of guessing from timing.
64
+ //
65
+ // It deliberately carries the revision and NOTHING ELSE — no message, no stack,
66
+ // no route. The ambient scanner matches dev-server output case-insensitively
67
+ // against RUNTIME_ERROR_PATTERNS ('error:', 'typeerror', 'unhandled', …;
68
+ // `runtime-error-scanner.types.ts`), so echoing the reported message here would
69
+ // put the crash text INTO the stream that scanner reads and fold a second,
70
+ // independent ambient error for the very report already on its way to the
71
+ // receiver — a self-inflicted double-fire. An untagged report stays silent
72
+ // rather than logging a placeholder, which keeps today's output byte-identical
73
+ // for every producer that does not stamp (the server `onRequestError` path
74
+ // never reaches this route at all — it POSTs the receiver directly).
75
+ const revision = servedRevision(body);
76
+ if (revision !== null) console.log(`[forge-beacon browser rev=${revision}]`);
77
+ // Forward server-side to the loopback receiver. A delivery failure is swallowed —
78
+ // the beacon is a best-effort backstop, never a hard dependency of the page — but
79
+ // the client still gets a clean 204 so it never retries against a flapping pod.
80
+ await fetch(`http://127.0.0.1:${BEACON_PORT}/__forge_beacon`, {
81
+ method: 'POST',
82
+ headers: { 'content-type': 'application/json' },
83
+ body,
84
+ }).catch(() => undefined);
85
+
86
+ return new Response(null, { status: 204 });
87
+ }
@@ -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,23 @@
1
+ 'use client';
2
+
3
+ import { Button } from '../components/ui/button';
4
+
5
+ /**
6
+ * Route-level backstop boundary. The action layer never throws (typed
7
+ * `ActionResult` envelopes end at inline UI), so landing here means a RENDER
8
+ * failed — e.g. a Server Component read against an unreachable shop API.
9
+ * Recovery is a re-render via `reset()`.
10
+ */
11
+ export default function RouteError({ reset }: { error: Error; reset: () => void }) {
12
+ return (
13
+ <div className="mx-auto max-w-xl space-y-4 py-16 text-center">
14
+ <h1 className="text-2xl font-bold tracking-tight text-foreground">Something went wrong</h1>
15
+ <p className="text-muted-foreground">
16
+ The store could not load this page. Please try again in a moment.
17
+ </p>
18
+ <Button type="button" onClick={() => reset()}>
19
+ Try again
20
+ </Button>
21
+ </div>
22
+ );
23
+ }
@@ -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,156 @@
1
+ @import "tailwindcss";
2
+
3
+ /*
4
+ * ═══════════════════════════════════════════════════════════════════════════
5
+ * STOREFRONT THEME — the single brand mutation point (design-system contract)
6
+ * ═══════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Every component in this template speaks SEMANTIC shadcn/ui tokens (bg-card,
9
+ * text-muted-foreground, border-input, the Button / Badge / Alert variants,
10
+ * …), so the `:root` block below is the ONE place a brand lives. Restyling
11
+ * the storefront = editing those values — nothing else. Never scatter raw
12
+ * palette utilities (bg-gray-900, text-white, #hex) through components; if a
13
+ * component needs a color, it needs a TOKEN: declared once in `:root` and
14
+ * exposed to Tailwind through `@theme inline`, as the status tokens are.
15
+ *
16
+ * The daisyUI storefront and this one are rows of one template set. The table
17
+ * is how every daisyUI theme token reads here — the same brand decision under
18
+ * its shadcn/ui name:
19
+ *
20
+ * daisyUI theme token shadcn/ui token (this file)
21
+ * ─────────────────────────── ──────────────────────────────────────────
22
+ * --color-base-100 --card, --popover
23
+ * --color-base-200 --background
24
+ * --color-base-300 --border, --input
25
+ * --color-base-content --foreground (+ --card-foreground,
26
+ * --popover-foreground)
27
+ * --color-primary(-content) --primary(-foreground)
28
+ * --color-secondary(-content) --secondary(-foreground)
29
+ * --color-accent(-content) --accent(-foreground)
30
+ * --color-neutral(-content) --muted(-foreground)
31
+ * --color-error --destructive
32
+ * --color-success(-content) --success(-foreground) declared by this variant
33
+ * --color-warning(-content) --warning(-foreground) declared by this variant
34
+ * --color-info(-content) --info(-foreground) declared by this variant
35
+ * --radius-selector --radius-sm
36
+ * --radius-field --radius-md
37
+ * --radius-box --radius-lg (= --radius)
38
+ * --font-sans (@theme) --font-sans (@theme inline)
39
+ *
40
+ * shadcn/ui names no success, warning or info color, so this variant declares
41
+ * them beside --destructive, each with the -foreground that stays legible on
42
+ * it (the Alert's status variants fill with them). --ring has no daisyUI
43
+ * counterpart — daisyUI draws a focus outline in the control's own color — so
44
+ * it takes the primary ink.
45
+ *
46
+ * The baseline is the daisyUI storefront's own, deliberately UNBRANDED
47
+ * monochrome (near-black on soft gray), carried value for value through the
48
+ * table — with one deliberate exception, the muted pair. daisyUI's neutral is
49
+ * a high-contrast FILL; shadcn/ui's muted pair is the SUBDUED one every
50
+ * vendored component reads for quiet surfaces and secondary text (bg-muted,
51
+ * text-muted-foreground — an input's placeholder among them). The baseline
52
+ * keeps that role: --muted is a soft gray surface, and --muted-foreground the
53
+ * mid-gray ink the daisyUI template writes as text-base-content/60. Accent
54
+ * keeps daisyUI's dark highlight, which is also what a ghost or outline
55
+ * button turns on hover.
56
+ *
57
+ * Radius: daisyUI's selector and field radii are both 0.375rem and its box
58
+ * radius 0.5rem, so --radius is the box radius and the sm/md steps sit one
59
+ * notch below it. A brand font goes in `@theme inline`: swap the --font-sans
60
+ * stack (and add the font's `@font-face`/import above it).
61
+ */
62
+ :root {
63
+ /* Base — white surfaces over a soft-gray substrate, near-black text. */
64
+ --background: #f9fafb;
65
+ --foreground: #111827;
66
+ --card: #ffffff;
67
+ --card-foreground: #111827;
68
+ --popover: #ffffff;
69
+ --popover-foreground: #111827;
70
+ --border: #e5e7eb;
71
+ --input: #e5e7eb;
72
+
73
+ /* Actions — unbranded monochrome. */
74
+ --primary: #111827;
75
+ --primary-foreground: #ffffff;
76
+ --secondary: #374151;
77
+ --secondary-foreground: #ffffff;
78
+ --accent: #1f2937;
79
+ --accent-foreground: #ffffff;
80
+ --muted: #f3f4f6;
81
+ --muted-foreground: #6b7280;
82
+ --ring: #111827;
83
+
84
+ /* Status — conventional semantics, not brand. */
85
+ --destructive: #dc2626;
86
+ --success: #16a34a;
87
+ --success-foreground: #ffffff;
88
+ --warning: #d97706;
89
+ --warning-foreground: #ffffff;
90
+ --info: #2563eb;
91
+ --info-foreground: #ffffff;
92
+
93
+ /* Shape — the daisyUI storefront's box radius. */
94
+ --radius: 0.5rem;
95
+ }
96
+
97
+ /* The tokens as Tailwind theme values: bg-card, text-muted-foreground, rounded-md, font-sans, … */
98
+ @theme inline {
99
+ --color-background: var(--background);
100
+ --color-foreground: var(--foreground);
101
+ --color-card: var(--card);
102
+ --color-card-foreground: var(--card-foreground);
103
+ --color-popover: var(--popover);
104
+ --color-popover-foreground: var(--popover-foreground);
105
+ --color-primary: var(--primary);
106
+ --color-primary-foreground: var(--primary-foreground);
107
+ --color-secondary: var(--secondary);
108
+ --color-secondary-foreground: var(--secondary-foreground);
109
+ --color-muted: var(--muted);
110
+ --color-muted-foreground: var(--muted-foreground);
111
+ --color-accent: var(--accent);
112
+ --color-accent-foreground: var(--accent-foreground);
113
+ --color-destructive: var(--destructive);
114
+ --color-border: var(--border);
115
+ --color-input: var(--input);
116
+ --color-ring: var(--ring);
117
+ --color-success: var(--success);
118
+ --color-success-foreground: var(--success-foreground);
119
+ --color-warning: var(--warning);
120
+ --color-warning-foreground: var(--warning-foreground);
121
+ --color-info: var(--info);
122
+ --color-info-foreground: var(--info-foreground);
123
+
124
+ --radius-sm: calc(var(--radius) - 2px);
125
+ --radius-md: calc(var(--radius) - 2px);
126
+ --radius-lg: var(--radius);
127
+ --radius-xl: calc(var(--radius) + 4px);
128
+
129
+ /* Brand typography mutation point — swap the stack to give the store a font. */
130
+ --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
131
+ "Segoe UI Symbol", "Noto Color Emoji";
132
+ }
133
+
134
+ /* The shadcn/ui base layer: token-colored borders and focus outlines, the page on --background. */
135
+ @layer base {
136
+ * {
137
+ @apply border-border outline-ring/50;
138
+ }
139
+
140
+ body {
141
+ @apply bg-background text-foreground;
142
+ }
143
+ }
144
+
145
+ /*
146
+ * Base document behavior (structural — NOT part of the theme contract).
147
+ * A horizontal touchpad glide must never trigger the browser's swipe-back
148
+ * navigation: mid-checkout that discards the shopper's in-progress state
149
+ * (the refresh-resume path recovers the payment step, but preventing the
150
+ * accidental navigation is the real fix). The back button and keyboard
151
+ * navigation are untouched — only the overscroll gesture is consumed.
152
+ */
153
+ html,
154
+ body {
155
+ overscroll-behavior-x: none;
156
+ }
@@ -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-background font-sans text-foreground 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,53 @@
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 and the
42
+ * vendored components in `src/components/ui/`. The heading reads the name the
43
+ * document title carries, so a channel that has not named its store still
44
+ * renders one.
45
+ */
46
+ export default async function HomePage() {
47
+ const { shopName } = await getRequestLocale();
48
+ return (
49
+ <h1 className="text-4xl font-bold tracking-tight text-balance">
50
+ {pageTitle(null, shopName)}
51
+ </h1>
52
+ );
53
+ }
@@ -0,0 +1,24 @@
1
+ import { LocaleLink } from '../../../components/LocaleLink';
2
+ import { buttonVariants } from '../../../components/ui/button';
3
+ import { getRequestLocale } from '../../../lib/locale/request-binding';
4
+
5
+ /**
6
+ * What a visitor sees at a page URL that holds nothing: an unknown route, or a
7
+ * page whose content is not published (or not published YET). The copy does not
8
+ * distinguish them on purpose — "this is scheduled for Tuesday" is the
9
+ * merchant's information, not the public's.
10
+ */
11
+ export default async function AcfPageNotFound() {
12
+ const { binding } = await getRequestLocale();
13
+ return (
14
+ <div className="space-y-4">
15
+ <h1 className="text-2xl font-bold tracking-tight text-foreground">Page not found</h1>
16
+ <p className="text-muted-foreground">
17
+ We couldn&apos;t find that page. It may have been moved or taken down.
18
+ </p>
19
+ <LocaleLink href="/" locale={binding} className={buttonVariants()}>
20
+ Back to the shop
21
+ </LocaleLink>
22
+ </div>
23
+ );
24
+ }
@@ -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
+ }