create-nextblock 0.15.10 → 0.16.2

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 (266) hide show
  1. package/docker-template/.dockerignore +1 -1
  2. package/libs/db/tsconfig.lib.json +3 -3
  3. package/libs/editor/tsconfig.lib.json +3 -3
  4. package/libs/ui/tsconfig.lib.json +3 -3
  5. package/libs/utils/tsconfig.json +3 -3
  6. package/package.json +50 -50
  7. package/project.json +19 -19
  8. package/templates/nextblock-template/.dockerignore +1 -1
  9. package/templates/nextblock-template/.swcrc +30 -30
  10. package/templates/nextblock-template/AGENTS.md +9 -9
  11. package/templates/nextblock-template/CLAUDE.md +1 -1
  12. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  13. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  14. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
  15. package/templates/nextblock-template/app/ToasterProvider.tsx +26 -26
  16. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  17. package/templates/nextblock-template/app/actions/contactSellerActions.test.ts +280 -280
  18. package/templates/nextblock-template/app/actions/contactSellerActions.ts +222 -222
  19. package/templates/nextblock-template/app/actions/email-retry.test.ts +62 -62
  20. package/templates/nextblock-template/app/actions/email.ts +241 -241
  21. package/templates/nextblock-template/app/actions/formActions.ts +245 -245
  22. package/templates/nextblock-template/app/actions/interactions.ts +489 -489
  23. package/templates/nextblock-template/app/actions/threadActions.ts +166 -166
  24. package/templates/nextblock-template/app/actions/visibilityActions.ts +210 -210
  25. package/templates/nextblock-template/app/api/ai/seo/alt-text/route.ts +221 -0
  26. package/templates/nextblock-template/app/api/ai/seo/metadata/route.ts +186 -0
  27. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
  28. package/templates/nextblock-template/app/api/checkout/route.ts +162 -162
  29. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +3092 -2900
  30. package/templates/nextblock-template/app/api/mcp/route.ts +415 -415
  31. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  32. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  33. package/templates/nextblock-template/app/api/view/route.ts +114 -114
  34. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  35. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  36. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  37. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  38. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  39. package/templates/nextblock-template/app/checkout/page.tsx +57 -57
  40. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +558 -552
  41. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +27 -0
  42. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  43. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  44. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -304
  45. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +180 -3
  46. package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +171 -6
  47. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
  48. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  49. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  50. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +12 -3
  51. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  52. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
  53. package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
  54. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
  55. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  56. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  57. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  58. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  59. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
  60. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
  61. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
  62. package/templates/nextblock-template/app/cms/layout.tsx +101 -101
  63. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  64. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  65. package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +177 -2
  66. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  67. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -661
  68. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -404
  69. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -333
  70. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -87
  71. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -37
  72. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  73. package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +28 -0
  74. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +248 -5
  75. package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +33 -0
  76. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +240 -5
  77. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
  78. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  79. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  80. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  81. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  82. package/templates/nextblock-template/app/cms/revisions/actions.ts +332 -332
  83. package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
  84. package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
  85. package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
  86. package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
  87. package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
  88. package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
  89. package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
  90. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  91. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  92. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -227
  93. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  94. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  95. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  96. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
  97. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
  98. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
  99. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
  100. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  101. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  102. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  103. package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -0
  104. package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -0
  105. package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -0
  106. package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -0
  107. package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -0
  108. package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -0
  109. package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -0
  110. package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
  111. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  112. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  113. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  114. package/templates/nextblock-template/app/layout.tsx +671 -671
  115. package/templates/nextblock-template/app/lib/seo.ts +319 -319
  116. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  117. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  118. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -502
  119. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  120. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  121. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  122. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  123. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  124. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  125. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  126. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  127. package/templates/nextblock-template/app/providers.tsx +96 -96
  128. package/templates/nextblock-template/app/robots.ts +123 -0
  129. package/templates/nextblock-template/app/sitemap.ts +1 -1
  130. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -164
  131. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -57
  132. package/templates/nextblock-template/app/thread/layout.tsx +15 -15
  133. package/templates/nextblock-template/app/thread/page.tsx +98 -98
  134. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  135. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  136. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  137. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  138. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  139. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  140. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -312
  141. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  142. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  143. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -188
  144. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  145. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  146. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  147. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  148. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  149. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  150. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  151. package/templates/nextblock-template/components/Header.tsx +38 -38
  152. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  153. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  154. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -378
  155. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -426
  156. package/templates/nextblock-template/components/SiteScripts.tsx +56 -56
  157. package/templates/nextblock-template/components/StaffReplies.tsx +102 -102
  158. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  159. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  160. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  161. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  162. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  163. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  164. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  165. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -18
  166. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -20
  167. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -25
  168. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -385
  169. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  170. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -157
  171. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -34
  172. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -612
  173. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  174. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  175. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  176. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  177. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -32
  178. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  179. package/templates/nextblock-template/components/form-message.tsx +32 -32
  180. package/templates/nextblock-template/components/seo/GenerateMetaButton.tsx +137 -0
  181. package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +244 -0
  182. package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -0
  183. package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -0
  184. package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -0
  185. package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -0
  186. package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -0
  187. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  188. package/templates/nextblock-template/components/theme-icon.tsx +78 -78
  189. package/templates/nextblock-template/components/theme-switcher.tsx +85 -85
  190. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  191. package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -44
  192. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  193. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +77 -77
  194. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -372
  195. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -309
  196. package/templates/nextblock-template/docs/README.md +42 -42
  197. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12506 -12506
  198. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  199. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  200. package/templates/nextblock-template/index.d.ts +7 -7
  201. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  202. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +134 -134
  203. package/templates/nextblock-template/lib/blocks/blockColors.ts +175 -175
  204. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -761
  205. package/templates/nextblock-template/lib/blocks/inlineScriptNonce.ts +20 -20
  206. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -64
  207. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -0
  208. package/templates/nextblock-template/lib/cms/payments-reminder.ts +30 -23
  209. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -42
  210. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -162
  211. package/templates/nextblock-template/lib/config/email-settings.ts +323 -323
  212. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -57
  213. package/templates/nextblock-template/lib/cortex-ai/alt-text-request.ts +86 -0
  214. package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -0
  215. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -59
  216. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -39
  217. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -70
  218. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -93
  219. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -157
  220. package/templates/nextblock-template/lib/messages/threads.ts +579 -579
  221. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  222. package/templates/nextblock-template/lib/publishing/viewUrl.ts +26 -26
  223. package/templates/nextblock-template/lib/search/types.ts +27 -27
  224. package/templates/nextblock-template/lib/seo/alt-text-write-back.test.ts +154 -0
  225. package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -0
  226. package/templates/nextblock-template/lib/seo/block-content.ts +123 -0
  227. package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -0
  228. package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -0
  229. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +140 -0
  230. package/templates/nextblock-template/lib/seo/page-document.test.ts +350 -0
  231. package/templates/nextblock-template/lib/seo/page-document.ts +412 -0
  232. package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -0
  233. package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -0
  234. package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -0
  235. package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -0
  236. package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -0
  237. package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -0
  238. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +177 -167
  239. package/templates/nextblock-template/lib/site-scripts/revisions.ts +71 -71
  240. package/templates/nextblock-template/lib/site-scripts/types.ts +46 -46
  241. package/templates/nextblock-template/lib/site-url.test.ts +89 -89
  242. package/templates/nextblock-template/lib/site-url.ts +102 -102
  243. package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -124
  244. package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -31
  245. package/templates/nextblock-template/lib/themes/tokens.ts +143 -143
  246. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  247. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  248. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  249. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  250. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  251. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  252. package/templates/nextblock-template/next-env.d.ts +0 -1
  253. package/templates/nextblock-template/package.json +1 -1
  254. package/templates/nextblock-template/postcss.config.js +6 -6
  255. package/templates/nextblock-template/proxy.ts +240 -20
  256. package/templates/nextblock-template/scripts/backup.js +115 -115
  257. package/templates/nextblock-template/scripts/restore.js +385 -385
  258. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  259. package/templates/nextblock-template/tailwind.config.js +25 -25
  260. package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
  261. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  262. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  263. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
  264. package/templates/nextblock-template/tools/update.mjs +1303 -1303
  265. package/tsconfig.base.json +3 -3
  266. package/templates/nextblock-template/app/robots.txt/route.ts +0 -32
@@ -1,190 +1,190 @@
1
- export const UCP_VERSION = '2026-04-08';
2
- export const UCP_REST_BASE_PATH = '/ucp/v1';
3
- export const UCP_SHOPPING_SERVICE = 'dev.ucp.shopping';
4
- export const UCP_CAPABILITIES = {
5
- catalogSearch: 'dev.ucp.shopping.catalog.search',
6
- catalogLookup: 'dev.ucp.shopping.catalog.lookup',
7
- cart: 'dev.ucp.shopping.cart',
8
- checkout: 'dev.ucp.shopping.checkout',
9
- } as const;
10
-
11
- const UCP_SPEC_ROOT = `https://ucp.dev/${UCP_VERSION}`;
12
- const DEFAULT_PAGE_LIMIT = 10;
13
- const MAX_PAGE_LIMIT = 50;
14
-
15
- type JsonRecord = Record<string, unknown>;
16
- type UcpCapabilityName = (typeof UCP_CAPABILITIES)[keyof typeof UCP_CAPABILITIES];
17
-
18
- function asRecord(value: unknown): JsonRecord {
19
- return value && typeof value === 'object' && !Array.isArray(value)
20
- ? (value as JsonRecord)
21
- : {};
22
- }
23
-
24
- function asString(value: unknown) {
25
- return typeof value === 'string' && value.trim() ? value.trim() : null;
26
- }
27
-
28
- function toInteger(value: unknown, fallback: number) {
29
- const parsed =
30
- typeof value === 'string'
31
- ? Number.parseInt(value, 10)
32
- : typeof value === 'number'
33
- ? value
34
- : Number.NaN;
35
-
36
- return Number.isFinite(parsed) ? Math.round(parsed) : fallback;
37
- }
38
-
39
- function encodeCursor(offset: number) {
40
- return Buffer.from(JSON.stringify({ offset }), 'utf8').toString('base64url');
41
- }
42
-
43
- function decodeCursor(cursor: unknown) {
44
- const value = asString(cursor);
45
- if (!value) {
46
- return null;
47
- }
48
-
49
- try {
50
- const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
51
- const offset = toInteger(asRecord(parsed).offset, -1);
52
- return offset >= 0 ? offset : null;
53
- } catch {
54
- return null;
55
- }
56
- }
57
-
58
- export function normalizeUcpPagination(body: unknown) {
59
- const record = asRecord(body);
60
- const pagination = asRecord(record.pagination);
61
- const requestedLimit = toInteger(
62
- pagination.limit ?? pagination.page_size ?? record.limit,
63
- DEFAULT_PAGE_LIMIT
64
- );
65
- const limit = Math.min(Math.max(requestedLimit, 1), MAX_PAGE_LIMIT);
66
- const cursorOffset = decodeCursor(
67
- pagination.cursor ?? pagination.after ?? record.cursor ?? record.page_token
68
- );
69
- const offset = cursorOffset ?? Math.max(toInteger(pagination.offset ?? record.offset, 0), 0);
70
-
71
- return { limit, offset };
72
- }
73
-
74
- export function buildPaginationResponse(params: {
75
- limit: number;
76
- offset: number;
77
- count: number;
78
- totalCount?: number | null;
79
- }) {
80
- const nextOffset = params.offset + params.count;
81
- const hasNextPage =
82
- typeof params.totalCount === 'number'
83
- ? nextOffset < params.totalCount
84
- : params.count === params.limit;
85
-
86
- return {
87
- cursor: hasNextPage ? encodeCursor(nextOffset) : null,
88
- has_next_page: hasNextPage,
89
- total_count: params.totalCount ?? undefined,
90
- limit: params.limit,
91
- };
92
- }
93
-
94
- export function buildUcpMetadata(
95
- capabilityNames: UcpCapabilityName[] = [
96
- UCP_CAPABILITIES.catalogSearch,
97
- UCP_CAPABILITIES.catalogLookup,
98
- UCP_CAPABILITIES.cart,
99
- ],
100
- status: 'success' | 'error' = 'success'
101
- ): JsonRecord {
102
- return {
103
- version: UCP_VERSION,
104
- status,
105
- capabilities: capabilityNames.reduce<Record<string, Array<{ version: string }>>>(
106
- (accumulator, capabilityName) => {
107
- accumulator[capabilityName] = [{ version: UCP_VERSION }];
108
- return accumulator;
109
- },
110
- {}
111
- ),
112
- };
113
- }
114
-
115
- export function buildUcpProfile(baseUrl: string) {
116
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, '');
117
- const endpoint = `${normalizedBaseUrl}${UCP_REST_BASE_PATH}`;
118
-
119
- return {
120
- ucp: {
121
- version: UCP_VERSION,
122
- supported_versions: {
123
- [UCP_VERSION]: `${normalizedBaseUrl}/.well-known/ucp`,
124
- },
125
- services: {
126
- [UCP_SHOPPING_SERVICE]: [
127
- {
128
- version: UCP_VERSION,
129
- spec: `${UCP_SPEC_ROOT}/specification/overview`,
130
- transport: 'rest',
131
- schema: `${UCP_SPEC_ROOT}/services/shopping/rest.openapi.json`,
132
- endpoint,
133
- },
134
- ],
135
- },
136
- capabilities: {
137
- [UCP_CAPABILITIES.catalogSearch]: [
138
- {
139
- version: UCP_VERSION,
140
- spec: `${UCP_SPEC_ROOT}/specification/catalog/search`,
141
- schema: `${UCP_SPEC_ROOT}/schemas/shopping/catalog_search.json`,
142
- },
143
- ],
144
- [UCP_CAPABILITIES.catalogLookup]: [
145
- {
146
- version: UCP_VERSION,
147
- spec: `${UCP_SPEC_ROOT}/specification/catalog/lookup`,
148
- schema: `${UCP_SPEC_ROOT}/schemas/shopping/catalog_lookup.json`,
149
- },
150
- ],
151
- [UCP_CAPABILITIES.cart]: [
152
- {
153
- version: UCP_VERSION,
154
- spec: `${UCP_SPEC_ROOT}/specification/cart`,
155
- schema: `${UCP_SPEC_ROOT}/schemas/shopping/cart.json`,
156
- },
157
- ],
158
- },
159
- },
160
- business: {
161
- name: 'NextBlock',
162
- url: normalizedBaseUrl,
163
- },
164
- };
165
- }
166
-
167
- export function buildUcpBusinessError(params: {
168
- capability: UcpCapabilityName;
169
- code: string;
170
- content: string;
171
- severity?: 'recoverable' | 'unrecoverable';
172
- continueUrl?: string;
173
- }) {
174
- return {
175
- ucp: buildUcpMetadata([params.capability], 'error'),
176
- messages: [
177
- {
178
- type: 'error',
179
- code: params.code,
180
- content: params.content,
181
- severity: params.severity ?? 'unrecoverable',
182
- },
183
- ],
184
- continue_url: params.continueUrl,
185
- };
186
- }
187
-
188
- export function buildProtocolError(code: string, content: string) {
189
- return { code, content };
190
- }
1
+ export const UCP_VERSION = '2026-04-08';
2
+ export const UCP_REST_BASE_PATH = '/ucp/v1';
3
+ export const UCP_SHOPPING_SERVICE = 'dev.ucp.shopping';
4
+ export const UCP_CAPABILITIES = {
5
+ catalogSearch: 'dev.ucp.shopping.catalog.search',
6
+ catalogLookup: 'dev.ucp.shopping.catalog.lookup',
7
+ cart: 'dev.ucp.shopping.cart',
8
+ checkout: 'dev.ucp.shopping.checkout',
9
+ } as const;
10
+
11
+ const UCP_SPEC_ROOT = `https://ucp.dev/${UCP_VERSION}`;
12
+ const DEFAULT_PAGE_LIMIT = 10;
13
+ const MAX_PAGE_LIMIT = 50;
14
+
15
+ type JsonRecord = Record<string, unknown>;
16
+ type UcpCapabilityName = (typeof UCP_CAPABILITIES)[keyof typeof UCP_CAPABILITIES];
17
+
18
+ function asRecord(value: unknown): JsonRecord {
19
+ return value && typeof value === 'object' && !Array.isArray(value)
20
+ ? (value as JsonRecord)
21
+ : {};
22
+ }
23
+
24
+ function asString(value: unknown) {
25
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
26
+ }
27
+
28
+ function toInteger(value: unknown, fallback: number) {
29
+ const parsed =
30
+ typeof value === 'string'
31
+ ? Number.parseInt(value, 10)
32
+ : typeof value === 'number'
33
+ ? value
34
+ : Number.NaN;
35
+
36
+ return Number.isFinite(parsed) ? Math.round(parsed) : fallback;
37
+ }
38
+
39
+ function encodeCursor(offset: number) {
40
+ return Buffer.from(JSON.stringify({ offset }), 'utf8').toString('base64url');
41
+ }
42
+
43
+ function decodeCursor(cursor: unknown) {
44
+ const value = asString(cursor);
45
+ if (!value) {
46
+ return null;
47
+ }
48
+
49
+ try {
50
+ const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
51
+ const offset = toInteger(asRecord(parsed).offset, -1);
52
+ return offset >= 0 ? offset : null;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ export function normalizeUcpPagination(body: unknown) {
59
+ const record = asRecord(body);
60
+ const pagination = asRecord(record.pagination);
61
+ const requestedLimit = toInteger(
62
+ pagination.limit ?? pagination.page_size ?? record.limit,
63
+ DEFAULT_PAGE_LIMIT
64
+ );
65
+ const limit = Math.min(Math.max(requestedLimit, 1), MAX_PAGE_LIMIT);
66
+ const cursorOffset = decodeCursor(
67
+ pagination.cursor ?? pagination.after ?? record.cursor ?? record.page_token
68
+ );
69
+ const offset = cursorOffset ?? Math.max(toInteger(pagination.offset ?? record.offset, 0), 0);
70
+
71
+ return { limit, offset };
72
+ }
73
+
74
+ export function buildPaginationResponse(params: {
75
+ limit: number;
76
+ offset: number;
77
+ count: number;
78
+ totalCount?: number | null;
79
+ }) {
80
+ const nextOffset = params.offset + params.count;
81
+ const hasNextPage =
82
+ typeof params.totalCount === 'number'
83
+ ? nextOffset < params.totalCount
84
+ : params.count === params.limit;
85
+
86
+ return {
87
+ cursor: hasNextPage ? encodeCursor(nextOffset) : null,
88
+ has_next_page: hasNextPage,
89
+ total_count: params.totalCount ?? undefined,
90
+ limit: params.limit,
91
+ };
92
+ }
93
+
94
+ export function buildUcpMetadata(
95
+ capabilityNames: UcpCapabilityName[] = [
96
+ UCP_CAPABILITIES.catalogSearch,
97
+ UCP_CAPABILITIES.catalogLookup,
98
+ UCP_CAPABILITIES.cart,
99
+ ],
100
+ status: 'success' | 'error' = 'success'
101
+ ): JsonRecord {
102
+ return {
103
+ version: UCP_VERSION,
104
+ status,
105
+ capabilities: capabilityNames.reduce<Record<string, Array<{ version: string }>>>(
106
+ (accumulator, capabilityName) => {
107
+ accumulator[capabilityName] = [{ version: UCP_VERSION }];
108
+ return accumulator;
109
+ },
110
+ {}
111
+ ),
112
+ };
113
+ }
114
+
115
+ export function buildUcpProfile(baseUrl: string) {
116
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, '');
117
+ const endpoint = `${normalizedBaseUrl}${UCP_REST_BASE_PATH}`;
118
+
119
+ return {
120
+ ucp: {
121
+ version: UCP_VERSION,
122
+ supported_versions: {
123
+ [UCP_VERSION]: `${normalizedBaseUrl}/.well-known/ucp`,
124
+ },
125
+ services: {
126
+ [UCP_SHOPPING_SERVICE]: [
127
+ {
128
+ version: UCP_VERSION,
129
+ spec: `${UCP_SPEC_ROOT}/specification/overview`,
130
+ transport: 'rest',
131
+ schema: `${UCP_SPEC_ROOT}/services/shopping/rest.openapi.json`,
132
+ endpoint,
133
+ },
134
+ ],
135
+ },
136
+ capabilities: {
137
+ [UCP_CAPABILITIES.catalogSearch]: [
138
+ {
139
+ version: UCP_VERSION,
140
+ spec: `${UCP_SPEC_ROOT}/specification/catalog/search`,
141
+ schema: `${UCP_SPEC_ROOT}/schemas/shopping/catalog_search.json`,
142
+ },
143
+ ],
144
+ [UCP_CAPABILITIES.catalogLookup]: [
145
+ {
146
+ version: UCP_VERSION,
147
+ spec: `${UCP_SPEC_ROOT}/specification/catalog/lookup`,
148
+ schema: `${UCP_SPEC_ROOT}/schemas/shopping/catalog_lookup.json`,
149
+ },
150
+ ],
151
+ [UCP_CAPABILITIES.cart]: [
152
+ {
153
+ version: UCP_VERSION,
154
+ spec: `${UCP_SPEC_ROOT}/specification/cart`,
155
+ schema: `${UCP_SPEC_ROOT}/schemas/shopping/cart.json`,
156
+ },
157
+ ],
158
+ },
159
+ },
160
+ business: {
161
+ name: 'NextBlock',
162
+ url: normalizedBaseUrl,
163
+ },
164
+ };
165
+ }
166
+
167
+ export function buildUcpBusinessError(params: {
168
+ capability: UcpCapabilityName;
169
+ code: string;
170
+ content: string;
171
+ severity?: 'recoverable' | 'unrecoverable';
172
+ continueUrl?: string;
173
+ }) {
174
+ return {
175
+ ucp: buildUcpMetadata([params.capability], 'error'),
176
+ messages: [
177
+ {
178
+ type: 'error',
179
+ code: params.code,
180
+ content: params.content,
181
+ severity: params.severity ?? 'unrecoverable',
182
+ },
183
+ ],
184
+ continue_url: params.continueUrl,
185
+ };
186
+ }
187
+
188
+ export function buildProtocolError(code: string, content: string) {
189
+ return { code, content };
190
+ }
@@ -1,56 +1,56 @@
1
- import { describe, expect, it } from 'vitest';
2
- import {
3
- UCP_REST_BASE_PATH,
4
- UCP_VERSION,
5
- buildPaginationResponse,
6
- buildUcpProfile,
7
- normalizeUcpPagination,
8
- } from './protocol';
9
-
10
- describe('UCP server helpers', () => {
11
- it('advertises the REST shopping service and catalog/cart capabilities', () => {
12
- const profile = buildUcpProfile('https://example.com/');
13
-
14
- expect(profile.ucp.version).toBe(UCP_VERSION);
15
- expect(profile.ucp.services['dev.ucp.shopping'][0].endpoint).toBe(
16
- `https://example.com${UCP_REST_BASE_PATH}`
17
- );
18
- expect(profile.ucp.capabilities['dev.ucp.shopping.catalog.search'][0].schema).toContain(
19
- 'catalog_search.json'
20
- );
21
- expect(profile.ucp.capabilities['dev.ucp.shopping.catalog.lookup'][0].schema).toContain(
22
- 'catalog_lookup.json'
23
- );
24
- expect(profile.ucp.capabilities['dev.ucp.shopping.cart'][0].schema).toContain(
25
- 'cart.json'
26
- );
27
- });
28
-
29
- it('normalizes pagination limits and offsets', () => {
30
- expect(
31
- normalizeUcpPagination({
32
- pagination: {
33
- limit: 500,
34
- offset: 12,
35
- },
36
- })
37
- ).toEqual({ limit: 50, offset: 12 });
38
-
39
- expect(normalizeUcpPagination({ pagination: { limit: 0 } })).toEqual({
40
- limit: 1,
41
- offset: 0,
42
- });
43
- });
44
-
45
- it('returns an opaque cursor when another page exists', () => {
46
- const pagination = buildPaginationResponse({
47
- limit: 10,
48
- offset: 0,
49
- count: 10,
50
- totalCount: 11,
51
- });
52
-
53
- expect(pagination.has_next_page).toBe(true);
54
- expect(pagination.cursor).toEqual(expect.any(String));
55
- });
56
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ UCP_REST_BASE_PATH,
4
+ UCP_VERSION,
5
+ buildPaginationResponse,
6
+ buildUcpProfile,
7
+ normalizeUcpPagination,
8
+ } from './protocol';
9
+
10
+ describe('UCP server helpers', () => {
11
+ it('advertises the REST shopping service and catalog/cart capabilities', () => {
12
+ const profile = buildUcpProfile('https://example.com/');
13
+
14
+ expect(profile.ucp.version).toBe(UCP_VERSION);
15
+ expect(profile.ucp.services['dev.ucp.shopping'][0].endpoint).toBe(
16
+ `https://example.com${UCP_REST_BASE_PATH}`
17
+ );
18
+ expect(profile.ucp.capabilities['dev.ucp.shopping.catalog.search'][0].schema).toContain(
19
+ 'catalog_search.json'
20
+ );
21
+ expect(profile.ucp.capabilities['dev.ucp.shopping.catalog.lookup'][0].schema).toContain(
22
+ 'catalog_lookup.json'
23
+ );
24
+ expect(profile.ucp.capabilities['dev.ucp.shopping.cart'][0].schema).toContain(
25
+ 'cart.json'
26
+ );
27
+ });
28
+
29
+ it('normalizes pagination limits and offsets', () => {
30
+ expect(
31
+ normalizeUcpPagination({
32
+ pagination: {
33
+ limit: 500,
34
+ offset: 12,
35
+ },
36
+ })
37
+ ).toEqual({ limit: 50, offset: 12 });
38
+
39
+ expect(normalizeUcpPagination({ pagination: { limit: 0 } })).toEqual({
40
+ limit: 1,
41
+ offset: 0,
42
+ });
43
+ });
44
+
45
+ it('returns an opaque cursor when another page exists', () => {
46
+ const pagination = buildPaginationResponse({
47
+ limit: 10,
48
+ offset: 0,
49
+ count: 10,
50
+ totalCount: 11,
51
+ });
52
+
53
+ expect(pagination.has_next_page).toBe(true);
54
+ expect(pagination.cursor).toEqual(expect.any(String));
55
+ });
56
+ });