@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,64 @@
1
+ import { Slot } from '@radix-ui/react-slot';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import type { ComponentProps } from 'react';
4
+
5
+ import { cn } from './utils';
6
+
7
+ /**
8
+ * The shadcn/ui button. `buttonVariants` is exported on its own so a link
9
+ * styled as a button keeps its own element — `<LocaleLink className={
10
+ * buttonVariants()}>` stays one crawlable `<a>` — and `asChild` hands the
11
+ * classes to whatever single child it wraps instead.
12
+ *
13
+ * Every colour is a token of the `:root` block in `src/app/globals.css`; the
14
+ * template has no destructive action, so the variant shadcn/ui paints with a
15
+ * raw `text-white` is not vendored.
16
+ */
17
+ const buttonVariants = cva(
18
+ "inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
23
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
24
+ outline:
25
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
26
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
27
+ link: 'text-primary underline-offset-4 hover:underline',
28
+ },
29
+ size: {
30
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
31
+ sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
32
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
33
+ icon: 'size-9',
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ variant: 'default',
38
+ size: 'default',
39
+ },
40
+ },
41
+ );
42
+
43
+ function Button({
44
+ className,
45
+ variant,
46
+ size,
47
+ asChild = false,
48
+ ...props
49
+ }: ComponentProps<'button'> &
50
+ VariantProps<typeof buttonVariants> & {
51
+ asChild?: boolean;
52
+ }) {
53
+ const Comp = asChild ? Slot : 'button';
54
+
55
+ return (
56
+ <Comp
57
+ data-slot="button"
58
+ className={cn(buttonVariants({ variant, size, className }))}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ export { Button, buttonVariants };
@@ -0,0 +1,28 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ /**
6
+ * The shadcn/ui card: a raised `bg-card` surface with its border and radius.
7
+ * The template's cards carry their own headings — a product tile's `<h3>`, a
8
+ * cart panel's `<h2>` — so the heading level stays the page's decision, and
9
+ * `CardContent` pads whatever sits inside.
10
+ */
11
+ function Card({ className, ...props }: ComponentProps<'div'>) {
12
+ return (
13
+ <div
14
+ data-slot="card"
15
+ className={cn(
16
+ 'flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm',
17
+ className,
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+
24
+ function CardContent({ className, ...props }: ComponentProps<'div'>) {
25
+ return <div data-slot="card-content" className={cn('px-6', className)} {...props} />;
26
+ }
27
+
28
+ export { Card, CardContent };
@@ -0,0 +1,26 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ /**
6
+ * The shadcn/ui text input. An invalid field is marked by `aria-invalid` —
7
+ * the attribute a screen reader announces is the one that paints the
8
+ * destructive border, so the two can never disagree.
9
+ */
10
+ function Input({ className, type, ...props }: ComponentProps<'input'>) {
11
+ return (
12
+ <input
13
+ type={type}
14
+ data-slot="input"
15
+ className={cn(
16
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
17
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
18
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { Input };
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import * as LabelPrimitive from '@radix-ui/react-label';
4
+ import type { ComponentProps } from 'react';
5
+
6
+ import { cn } from './utils';
7
+
8
+ /** The shadcn/ui form label, on Radix's label primitive. */
9
+ function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {
10
+ return (
11
+ <LabelPrimitive.Root
12
+ data-slot="label"
13
+ className={cn(
14
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Label };
@@ -0,0 +1,27 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ /**
6
+ * A native `<select>` dressed as a shadcn/ui field. The checkout's country
7
+ * select stays the browser's own control — it takes part in the form's
8
+ * `required` validation and in the platform's address autofill, which a
9
+ * custom listbox would have to rebuild — and only borrows the input's
10
+ * border, focus ring and `aria-invalid` treatment.
11
+ */
12
+ function NativeSelect({ className, ...props }: ComponentProps<'select'>) {
13
+ return (
14
+ <select
15
+ data-slot="native-select"
16
+ className={cn(
17
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
18
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
19
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20',
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+
27
+ export { NativeSelect };
@@ -0,0 +1,21 @@
1
+ import type { ComponentProps } from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ /**
6
+ * The shadcn/ui loading placeholder. It pulses on `bg-muted`, the quiet
7
+ * surface, rather than on `bg-accent`: this variant's accent carries the
8
+ * daisyUI baseline's strong highlight, which would turn every loading block
9
+ * near-black.
10
+ */
11
+ function Skeleton({ className, ...props }: ComponentProps<'div'>) {
12
+ return (
13
+ <div
14
+ data-slot="skeleton"
15
+ className={cn('animate-pulse rounded-md bg-muted', className)}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Skeleton };
@@ -0,0 +1,16 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /**
5
+ * shadcn/ui's class helper: joins conditional classes (`clsx`) and lets a
6
+ * later utility win over an earlier one of the same group (`tailwind-merge`),
7
+ * so a component's own classes can be overridden from its `className`.
8
+ *
9
+ * It lives beside the vendored components rather than under `src/lib/`: the
10
+ * library holds the storefront's logic, which every row of the template set
11
+ * shares byte for byte, and this helper belongs to the shadcn/ui components
12
+ * alone. `components.json` points the shadcn/ui CLI's `utils` alias here.
13
+ */
14
+ export function cn(...inputs: ClassValue[]): string {
15
+ return twMerge(clsx(inputs));
16
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * DEV-ONLY server-error beacon via Next's `onRequestError` instrumentation hook.
3
+ *
4
+ * This is the PRIMARY server-side crash-detection surface for the in-pod dev-server:
5
+ * when a Server Component, a server action, or a route handler THROWS while rendering
6
+ * a request under `next dev`, Next invokes `onRequestError` with the structured error
7
+ * + request + routing context. The hook POSTs that detail SERVER-side, straight to the
8
+ * in-pod workspace-manager loopback receiver at
9
+ * `http://127.0.0.1:<FORGE_BEACON_PORT>/__forge_beacon`, which folds it into the
10
+ * supervisor's runtime state so the shop's recovery brain can originate a fix for a
11
+ * crash the `/ping` liveness probe cannot see (the dev-server process stays up and
12
+ * `/ping` stays 200, but `/` 500s per request).
13
+ *
14
+ * This complements the browser beacon (`ForgeErrorBeacon`): that one catches
15
+ * CLIENT-component / hydration throws that surface only in the browser; this one
16
+ * catches SERVER render throws that surface only in the dev-server. Both POST the
17
+ * same `/__forge_beacon` shape — the only difference is `source` (`'browser'` vs
18
+ * `'server'`).
19
+ *
20
+ * Gated entirely on `NODE_ENV === 'development'`: a deployed `next start` storefront
21
+ * registers no beacon (the literal check is inlined by the bundler, so the body is
22
+ * dead-code-eliminated from a production build) and the loopback receiver does not
23
+ * exist outside the pod. Next compiles `instrumentation.ts` for BOTH the Node and Edge
24
+ * runtimes; the loopback POST is a Node-only concern, so the hook also gates on the
25
+ * Node runtime — the Edge bundle then carries an inert no-op.
26
+ *
27
+ * The hook NEVER throws out of itself: a failed delivery is swallowed (the beacon is a
28
+ * best-effort backstop, never a hard dependency of the request), so a flapping receiver
29
+ * can never mask or replace the underlying request error Next is already reporting.
30
+ *
31
+ * The error's own `code` rides along verbatim — a fact, never a verdict: a request
32
+ * that races a recompile evaluates Turbopack's stub for a module that no longer parses
33
+ * and throws `code: 'MODULE_UNPARSABLE'`, and the receiver reads that code to leave a
34
+ * BUILD failure to the build detectors instead of folding it as a render crash.
35
+ */
36
+
37
+ const BEACON_PORT = process.env.FORGE_BEACON_PORT ?? '3002';
38
+
39
+ /**
40
+ * Server-start hook: warm-boot the embedded NestJS backend (`src/server/`)
41
+ * so the first `sdk.backend` invocation doesn't pay the context creation.
42
+ *
43
+ * Warm-up ONLY — correctness never depends on it: the dispatch path's lazy
44
+ * `getBackend()` is what re-boots the context after dev-mode (HMR) edits,
45
+ * and a boot failure here is deliberately swallowed so a drifted api-map
46
+ * can never crash the server start — the first invocation (and the dev
47
+ * methods route) reports the same error as a typed envelope instead. The
48
+ * dynamic import keeps the Edge bundle inert (Nest is Node-only), and a
49
+ * no-env boot (image pre-warm) succeeds because clients are only built at
50
+ * invocation time.
51
+ */
52
+ export async function register(): Promise<void> {
53
+ // POSITIVE if-block, never an early return: webpack's parser dead-code-
54
+ // eliminates a statically-false `if` BODY (NEXT_RUNTIME is DefinePlugin-
55
+ // substituted per bundle), but it does NOT track reachability past a
56
+ // `return` — with the guard inverted, the EDGE instrumentation bundle
57
+ // (compiled because middleware.ts exists) chases this import into
58
+ // @nestjs/*'s node-builtin requires and `next build --webpack` dies with
59
+ // five Module-not-found errors ('stream', 'os', 'perf_hooks'; every
60
+ // publish-button CI run since this import landed). Turbopack prunes the
61
+ // dead branch either way — only the webpack publish build sees the
62
+ // difference.
63
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
64
+ try {
65
+ const { getBackend } = await import('./server/bootstrap');
66
+ await getBackend();
67
+ } catch {
68
+ // Surfaced by the first sdk.backend invocation and /api/__backend/methods.
69
+ }
70
+ }
71
+ }
72
+
73
+ interface OnRequestErrorRequest {
74
+ path: string;
75
+ method: string;
76
+ headers: { [key: string]: string | string[] | undefined };
77
+ }
78
+
79
+ interface OnRequestErrorContext {
80
+ routerKind: string;
81
+ routePath: string;
82
+ routeType: string;
83
+ }
84
+
85
+ export async function onRequestError(
86
+ error: unknown,
87
+ request: OnRequestErrorRequest,
88
+ context: OnRequestErrorContext,
89
+ ): Promise<void> {
90
+ if (process.env.NODE_ENV !== 'development' || process.env.NEXT_RUNTIME !== 'nodejs') {
91
+ return;
92
+ }
93
+
94
+ const err = error as { message?: string; digest?: string; code?: unknown } | undefined;
95
+ const body = JSON.stringify({
96
+ message: typeof err?.message === 'string' ? err.message : String(error),
97
+ digest: typeof err?.digest === 'string' ? err.digest : undefined,
98
+ code: typeof err?.code === 'string' ? err.code : undefined,
99
+ route: request.path,
100
+ routeType: context.routeType,
101
+ source: 'server',
102
+ });
103
+
104
+ await fetch(`http://127.0.0.1:${BEACON_PORT}/__forge_beacon`, {
105
+ method: 'POST',
106
+ headers: { 'content-type': 'application/json' },
107
+ body,
108
+ }).catch(() => undefined);
109
+ }
@@ -0,0 +1,76 @@
1
+ import type { RouteSearchParams } from '../seo/noindex';
2
+
3
+ /**
4
+ * The anatomy of a ONE-SHOT account link (#1472).
5
+ *
6
+ * The platform mails a shopper two kinds of link and both have the same shape:
7
+ * this storefront's own origin, a fixed route, and a single-use token in the
8
+ * query — `<origin>/verify?token=…` and `<origin>/reset-password?token=…`. The
9
+ * `?token=` spelling is the CONTRACT between the mail templates and these
10
+ * pages (a merchant may have customized the mail body, so the parameter name
11
+ * cannot be changed on one side alone); the platform supplies the origin, this
12
+ * module reads what arrives.
13
+ *
14
+ * PURE BY CONTRACT: no Next imports, no `process.env`, no fetch, no I/O — the
15
+ * same constraint `lib/locale/grammar.ts` and `lib/seo/noindex.ts` carry, for
16
+ * the same reason. `tool/storefront-template-spec` imports this file directly
17
+ * by relative path from OUTSIDE the scaffold, so anything reachable from here
18
+ * must resolve without the template's Next toolchain. A rule that decides what
19
+ * a shopper sees after clicking a one-shot credential has to be a table of
20
+ * vectors rather than something only a running server can exercise. The
21
+ * `RouteSearchParams` import is type-only and names another pure module, so it
22
+ * erases and drags nothing in.
23
+ */
24
+
25
+ /**
26
+ * The query parameter every account link carries.
27
+ *
28
+ * Named once so the two pages and their vectors cannot disagree about the
29
+ * spelling the platform's mail templates emit.
30
+ */
31
+ export const ACCOUNT_TOKEN_PARAM = 'token';
32
+
33
+ /** What the shop API answered when a token was actually presented to it. */
34
+ export type AccountCallOutcome = { kind: 'ok' } | { kind: 'error'; code: string };
35
+
36
+ /**
37
+ * Everything a link render has to account for: the API's answer, or the fact
38
+ * that there was no usable token to ask about in the first place.
39
+ *
40
+ * `no-token` is not an error the API returned — it is a property of the URL —
41
+ * and keeping it in the same union is what lets ONE total function decide the
42
+ * whole page, instead of a page-level branch plus a function that covers only
43
+ * the half of the cases that reached the network.
44
+ */
45
+ export type AccountLinkOutcome = AccountCallOutcome | { kind: 'no-token' };
46
+
47
+ /**
48
+ * The single usable token in this URL, or null.
49
+ *
50
+ * Three ways to get null, each deliberate:
51
+ *
52
+ * - ABSENT — the page was opened without a link (a typed URL, or a mail
53
+ * client that dropped the query).
54
+ * - BLANK — `?token=` or `?token=%20`. Surrounding whitespace is trimmed
55
+ * because a mail client wrapping a long URL can deliver it padded and the
56
+ * platform's tokens never contain whitespace; a value that is nothing but
57
+ * padding is not a token.
58
+ * - REPEATED — `?token=a&token=b` arrives as an array, and is REFUSED rather
59
+ * than resolved to the first value. This is a one-shot credential: picking
60
+ * one of two candidates would SPEND a real token on a guess about which one
61
+ * the shopper meant, and the one that loses the guess is unrecoverable.
62
+ * Nothing legitimate produces a repeat, so the honest answer is that this
63
+ * URL carries no token the page may act on.
64
+ */
65
+ export function readLinkToken(searchParams: RouteSearchParams): string | null {
66
+ const raw = searchParams[ACCOUNT_TOKEN_PARAM];
67
+ if (typeof raw === 'string') return trimmedOrNull(raw);
68
+ if (!Array.isArray(raw)) return null;
69
+ if (raw.length !== 1) return null;
70
+ return trimmedOrNull(raw[0]);
71
+ }
72
+
73
+ function trimmedOrNull(value: string): string | null {
74
+ const trimmed = value.trim();
75
+ return trimmed === '' ? null : trimmed;
76
+ }
@@ -0,0 +1,152 @@
1
+ import type { AccountCallOutcome } from './account-link';
2
+
3
+ /**
4
+ * What `/account` renders, as a value (#1093).
5
+ *
6
+ * PURE BY CONTRACT, for the reasons `account-link.ts` states. The panel owns the
7
+ * COPY; this module owns which view a given answer of the shop API IS, so every
8
+ * decision the sign-in surface makes is a table of vectors and the panel is a
9
+ * render.
10
+ *
11
+ * The page is a static shell and the session lives on the shopper's own socket,
12
+ * so nothing here is known at render time: the server paints `loading`, and the
13
+ * panel asks the socket who is signed in once the cart provider has armed it.
14
+ */
15
+
16
+ /** The shopper a signed-in session belongs to, as the account page greets them. */
17
+ export interface SignedInCustomer {
18
+ readonly firstName: string;
19
+ readonly lastName: string;
20
+ readonly emailAddress: string;
21
+ }
22
+
23
+ /**
24
+ * What the shop API answered when asked who this socket's session belongs to.
25
+ *
26
+ * `customer: null` is an ordinary answer, not an error: a socket carrying no
27
+ * session, and an anonymous cart session, both belong to nobody.
28
+ */
29
+ export type ActiveCustomerOutcome =
30
+ | { kind: 'ok'; customer: SignedInCustomer | null }
31
+ | { kind: 'error'; code: string };
32
+
33
+ /** The call a refusal answered — what the panel says went wrong. */
34
+ export type AccountStep = 'greeting' | 'sign-in' | 'sign-out';
35
+
36
+ /** A refusal the panel renders, with the call it answered. */
37
+ export interface AccountRefusal {
38
+ readonly step: AccountStep;
39
+ readonly code: string;
40
+ }
41
+
42
+ /**
43
+ * The views of the account page — each `state` is the `data-fc-account-state`
44
+ * the panel stamps.
45
+ *
46
+ * - `loading` — the server render, and every client render until the
47
+ * socket has been asked. A skeleton, never the form: a
48
+ * returning customer must land on their greeting without a
49
+ * flash of the sign-in form first.
50
+ * - `signed-out` — the sign-in form, with the refusal that put it there, if
51
+ * any.
52
+ * - `signed-in` — the greeting and the way out.
53
+ */
54
+ export type AccountView =
55
+ | { readonly state: 'loading' }
56
+ | { readonly state: 'signed-out'; readonly refusal: AccountRefusal | null }
57
+ | { readonly state: 'signed-in'; readonly customer: SignedInCustomer };
58
+
59
+ /** The view the server renders. */
60
+ export const LOADING_VIEW: AccountView = { state: 'loading' };
61
+
62
+ /**
63
+ * The codes that mean the session the socket presented is OVER: the API holds
64
+ * no session for the token (`SESSION_NOT_FOUND`), or the request carried none
65
+ * it could use (`UNAUTHORIZED` — the guard's answer for a token that resolves
66
+ * to no session).
67
+ *
68
+ * A session that is over is a shopper who is signed out, not a fault: the
69
+ * greeting renders the form without a message, and a sign-out that finds the
70
+ * session already gone has done what it was asked.
71
+ */
72
+ const SESSION_GONE_CODES: ReadonlySet<string> = new Set(['UNAUTHORIZED', 'SESSION_NOT_FOUND']);
73
+
74
+ /**
75
+ * The refusal of a sign-in whose session greets nobody: the credentials were
76
+ * accepted, but the session belongs to no customer of THIS store — the code
77
+ * `/verify`'s `wrong-store` state reads for the same situation.
78
+ */
79
+ const NO_CUSTOMER_CODE = 'CUSTOMER_NOT_FOUND';
80
+
81
+ /** The refusal that a confirmation mail answers. */
82
+ const NOT_VERIFIED_CODE = 'NOT_VERIFIED';
83
+
84
+ /** Whether a refusal says the session it was asked about no longer exists. */
85
+ export function isSessionGone(code: string): boolean {
86
+ return SESSION_GONE_CODES.has(code);
87
+ }
88
+
89
+ /**
90
+ * The view the page lands in when it asks who is signed in on arrival.
91
+ *
92
+ * A session that greets nobody — none on the socket, an anonymous cart session,
93
+ * or one that is over — is the sign-in form with nothing to apologise for. Any
94
+ * other refusal is shown: the form still renders, because a shopper who cannot
95
+ * be greeted can still try to sign in.
96
+ */
97
+ export function greetingView(outcome: ActiveCustomerOutcome): AccountView {
98
+ if (outcome.kind === 'ok' && outcome.customer !== null) {
99
+ return { state: 'signed-in', customer: outcome.customer };
100
+ }
101
+ if (outcome.kind === 'ok' || isSessionGone(outcome.code)) {
102
+ return { state: 'signed-out', refusal: null };
103
+ }
104
+ return { state: 'signed-out', refusal: { step: 'greeting', code: outcome.code } };
105
+ }
106
+
107
+ /**
108
+ * The view an ACCEPTED sign-in lands in, from the greeting asked right after it.
109
+ *
110
+ * Unlike {@link greetingView}, "nobody is signed in" is never a silent answer
111
+ * here: the shopper has just been told yes, so a session that greets nobody is
112
+ * a failed sign-in and is rendered as one. A session that is gone the moment it
113
+ * was minted is the #1089 signature — a socket still speaking for the session
114
+ * it opened with — and is shown with its code rather than folded away.
115
+ */
116
+ export function signInView(outcome: ActiveCustomerOutcome): AccountView {
117
+ if (outcome.kind === 'error') {
118
+ return { state: 'signed-out', refusal: { step: 'sign-in', code: outcome.code } };
119
+ }
120
+ if (outcome.customer === null) {
121
+ return { state: 'signed-out', refusal: { step: 'sign-in', code: NO_CUSTOMER_CODE } };
122
+ }
123
+ return { state: 'signed-in', customer: outcome.customer };
124
+ }
125
+
126
+ /** The view a REFUSED sign-in lands in. */
127
+ export function signInRefusedView(code: string): AccountView {
128
+ return { state: 'signed-out', refusal: { step: 'sign-in', code } };
129
+ }
130
+
131
+ /**
132
+ * The view a sign-out lands in. Always `signed-out` — the session helper has
133
+ * left this tab without a session whatever the API answered — and a refusal is
134
+ * shown only when the API could not confirm the session ended.
135
+ */
136
+ export function signedOutView(outcome: AccountCallOutcome): AccountView {
137
+ if (outcome.kind === 'ok') return { state: 'signed-out', refusal: null };
138
+ return { state: 'signed-out', refusal: { step: 'sign-out', code: outcome.code } };
139
+ }
140
+
141
+ /**
142
+ * Whether the view offers to send the confirmation mail again: only after a
143
+ * sign-in the API refused because the account was never confirmed. Without it
144
+ * that shopper has nowhere to go — the mail they lost is the only key.
145
+ */
146
+ export function offersVerificationResend(view: AccountView): boolean {
147
+ return (
148
+ view.state === 'signed-out' &&
149
+ view.refusal?.step === 'sign-in' &&
150
+ view.refusal.code === NOT_VERIFIED_CODE
151
+ );
152
+ }