@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,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
+ }
@@ -0,0 +1,107 @@
1
+ import type { MetadataRoute } from 'next';
2
+
3
+ import { getProductSeoEntries, getServedPageRoutes } from '../lib/forgecart';
4
+ import { resolveChannelLocales } from '../lib/locale/channel-locales-loader';
5
+ import { getPublicOrigin } from '../lib/seo/public-origin';
6
+ import { readScaffoldedRoutes } from '../lib/seo/scaffolded-routes';
7
+ import { createSitemapCache, sitemapWindowMs } from '../lib/seo/sitemap-cache';
8
+ import {
9
+ MAX_SITEMAP_URLS,
10
+ collectProductSitemapUrls,
11
+ mergeScaffoldedRoutes,
12
+ pageSitemapRoutes,
13
+ sitemapBudgetWarning,
14
+ staticSitemapUrls,
15
+ } from '../lib/seo/sitemap-entries';
16
+ import { STATIC_ROUTES } from '../seo/static-routes';
17
+
18
+ /**
19
+ * `sitemap.xml` (#1347, epic launch#54 W1-9) — the discovery half of the SEO
20
+ * contract, listing every URL this deployment is willing to be indexed at.
21
+ *
22
+ * FORCE-DYNAMIC for the same load-bearing reason as `robots.ts`: Next
23
+ * pre-renders metadata routes at build time, the storefront image is built
24
+ * once and handed its channel environment afterwards, and the scaffold is
25
+ * frozen at provisioning time (#1041). A build-time answer would freeze the
26
+ * BUILDER's empty environment — no origin, no channel, no catalog — into an
27
+ * empty sitemap for every store shipped from that image, permanently. The
28
+ * 60s window is held in-process by `createSitemapCache` for exactly the same
29
+ * reason ISR is refused here: `revalidate` would pre-render the first response
30
+ * at build time too, which is the identical failure wearing a TTL.
31
+ */
32
+ export const dynamic = 'force-dynamic';
33
+
34
+ /**
35
+ * An empty document without a public origin, and no `<lastmod>` claims — the
36
+ * same refusal `robots.ts` and every page's `robots` metadata make in that
37
+ * state. Every URL a sitemap lists is absolute, so a deployment with no origin
38
+ * has nothing truthful to put in one.
39
+ *
40
+ * A backend failure is deliberately NOT caught: an unreachable shop makes this
41
+ * route 5xx, which tells a crawler to come back. Serving an empty sitemap
42
+ * instead would assert that the catalog is empty — the one answer that is
43
+ * actively false, and the one a crawler would act on by dropping URLs.
44
+ */
45
+ const cache = createSitemapCache(build, { ttlMs: sitemapWindowMs });
46
+
47
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
48
+ return cache.get();
49
+ }
50
+
51
+ /**
52
+ * The refusal is inside the window rather than guarding it, because the origin
53
+ * is memoized for the life of the process: an unconfigured deployment stays
54
+ * unconfigured until it restarts, so caching its empty answer cannot make it
55
+ * any staler than the memo already has.
56
+ */
57
+ async function build(): Promise<MetadataRoute.Sitemap> {
58
+ const publicOrigin = getPublicOrigin();
59
+ if (publicOrigin === null) return [];
60
+
61
+ const { channel } = await resolveChannelLocales(null);
62
+ const { languageCodes, defaultLanguageCode } = channel;
63
+
64
+ // The row's own routes (`src/seo/static-routes.ts`, #3478), every page
65
+ // scaffolded since, and every ACF page the shop says it currently SERVES
66
+ // (#1934). Read per build rather than at module load, so a page created a
67
+ // moment ago is in the document on the next window — the reason
68
+ // `createStorefrontPage` writes a row at all, and the reason the ACF feed is
69
+ // a live query rather than a file: an ACF page is published through the API
70
+ // and writes nothing to this pod's disk.
71
+ //
72
+ // PRECEDENCE falls out of the merge and is worth naming: the base table wins
73
+ // over both, and within the appended list the FIRST claimant of a path wins —
74
+ // so a code-scaffolded page at `/pages/x` outranks an ACF page at the same
75
+ // slug. That is also how Next routes them (a literal `app/pages/x/page.tsx`
76
+ // shadows the dynamic `[slug]`), so the document cannot disagree with the
77
+ // router about which page an address serves.
78
+ const [scaffolded, servedPageRoutes] = await Promise.all([
79
+ readScaffoldedRoutes(),
80
+ getServedPageRoutes(defaultLanguageCode),
81
+ ]);
82
+ const routes = mergeScaffoldedRoutes(STATIC_ROUTES, [
83
+ ...scaffolded,
84
+ ...pageSitemapRoutes(servedPageRoutes),
85
+ ]);
86
+ const staticUrls = staticSitemapUrls(languageCodes, defaultLanguageCode, publicOrigin, routes);
87
+ const productUrls = await collectProductSitemapUrls(
88
+ async (skip, take) => getProductSeoEntries(defaultLanguageCode, { skip, take }),
89
+ defaultLanguageCode,
90
+ publicOrigin,
91
+ Math.max(0, MAX_SITEMAP_URLS - staticUrls.length),
92
+ );
93
+
94
+ const urls = [...staticUrls, ...productUrls];
95
+
96
+ // The one place this template writes to the console, and it earns it: a
97
+ // catalog that outgrows a single XML sitemap would otherwise stop being
98
+ // submitted for indexing in complete silence, inside the feature whose whole
99
+ // job is getting pages indexed. There is no logger in a scaffolded storefront
100
+ // and no channelId to tag — only the channel token, which is a credential and
101
+ // must never reach a log — so the pod's stdout is the channel, and the 60s
102
+ // window bounds this to at most one line a minute.
103
+ const warning = sitemapBudgetWarning(urls.length);
104
+ if (warning !== null) console.warn(warning);
105
+
106
+ return urls;
107
+ }
@@ -0,0 +1,102 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ /**
6
+ * DEV-ONLY storefront error beacon. Renders nothing.
7
+ *
8
+ * Registers `window.onerror` + `window.onunhandledrejection` and POSTs the failure
9
+ * detail to the same-origin Next API route `/__forge_beacon`, which forwards it
10
+ * server-side to the in-pod workspace-manager receiver. This surfaces the crash
11
+ * class the pod's dev-server stderr scan CANNOT see: a client-component runtime
12
+ * error or a hydration/SSR-boundary throw that never reaches the dev-server's
13
+ * output. The browser only ever talks SAME-ORIGIN — the API route is the only thing
14
+ * that knows the pod-internal receiver address.
15
+ *
16
+ * Production builds ship zero bytes of this: the literal `NODE_ENV` check is inlined
17
+ * by the bundler, so the effect body is dead-code-eliminated from `next build` /
18
+ * `next start` output — a deployed storefront registers no global error handlers and
19
+ * has no beacon API route reachable in prod (the route itself is dev-gated too).
20
+ *
21
+ * The beacon is best-effort and never blocks the page: the POST is fire-and-forget
22
+ * with `keepalive` so it survives an unload, and a delivery failure is swallowed —
23
+ * the page's own error boundary still renders. The full
24
+ * browser → API route → pod → supervisor-fold path is exercised at the e2e layer,
25
+ * not in this template.
26
+ *
27
+ * Every report carries the revision the PAGE WAS SERVED AT (#847), read out of the
28
+ * `forge-revision` meta tag the server wrote into this document rather than looked
29
+ * up fresh at throw time. That distinction is the entire point: a fresh lookup would
30
+ * answer whatever is live NOW, so a promote landing between serve and throw would
31
+ * make this report blame the new revision for the old one's crash — and the pod's
32
+ * same-revision fold would then mark a healthy tree DEGRADED. The tag cannot drift,
33
+ * because it was rendered with the markup that broke.
34
+ */
35
+ export function ForgeErrorBeacon() {
36
+ useEffect(() => {
37
+ if (process.env.NODE_ENV !== 'development') {
38
+ return;
39
+ }
40
+
41
+ /**
42
+ * The revision this document was SERVED at, or `undefined` when the server
43
+ * did not stamp one (production, or a pod that cannot resolve it — both
44
+ * normal). `'forge-revision'` is repeated here as a literal on purpose: the
45
+ * resolver lives in `src/server/forge/live-revision.ts`, which a client
46
+ * component must not import, so this is the same repeated-contract shape the
47
+ * `/__forge_beacon` URL already uses across this feature's producers.
48
+ *
49
+ * An absent tag, an absent attribute and an empty value all answer
50
+ * `undefined`, which `JSON.stringify` then omits from the body entirely —
51
+ * so an unstamped page sends exactly the payload it sent before, and the pod
52
+ * keeps its existing untagged behaviour instead of receiving a blank string
53
+ * it would have to interpret.
54
+ */
55
+ const readServedRevision = (): string | undefined => {
56
+ const content = document
57
+ .querySelector('meta[name="forge-revision"]')
58
+ ?.getAttribute('content');
59
+ if (!content) return undefined;
60
+ return content;
61
+ };
62
+
63
+ const send = (message: string, stack?: string) => {
64
+ const body = JSON.stringify({
65
+ message,
66
+ stack,
67
+ route: window.location.pathname,
68
+ source: 'browser',
69
+ atRevision: readServedRevision(),
70
+ });
71
+ // Same-origin POST; keepalive lets it survive a navigation/unload. A failed
72
+ // delivery is intentionally swallowed — the beacon is a backstop signal, not
73
+ // a hard dependency of the page.
74
+ void fetch('/__forge_beacon', {
75
+ method: 'POST',
76
+ headers: { 'content-type': 'application/json' },
77
+ body,
78
+ keepalive: true,
79
+ }).catch(() => undefined);
80
+ };
81
+
82
+ const onError = (event: ErrorEvent) => {
83
+ send(event.message || 'window.onerror', event.error?.stack);
84
+ };
85
+ const onRejection = (event: PromiseRejectionEvent) => {
86
+ const reason = event.reason;
87
+ const message =
88
+ reason instanceof Error ? reason.message : String(reason ?? 'unhandledrejection');
89
+ const stack = reason instanceof Error ? reason.stack : undefined;
90
+ send(message, stack);
91
+ };
92
+
93
+ window.addEventListener('error', onError);
94
+ window.addEventListener('unhandledrejection', onRejection);
95
+ return () => {
96
+ window.removeEventListener('error', onError);
97
+ window.removeEventListener('unhandledrejection', onRejection);
98
+ };
99
+ }, []);
100
+
101
+ return null;
102
+ }