@tokenoftrust/storefront-runner 1.3.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 (310) hide show
  1. package/LICENSE +58 -0
  2. package/README.md +42 -0
  3. package/apps/storefront/astro.config.mjs +66 -0
  4. package/apps/storefront/dev-plugins/dev-loop-state.mjs +147 -0
  5. package/apps/storefront/dev-plugins/tenant-hot-reload.mjs +238 -0
  6. package/apps/storefront/env.d.ts +162 -0
  7. package/apps/storefront/migrations/0000_baseline.sql +54 -0
  8. package/apps/storefront/migrations/0001_products_fts.sql +9 -0
  9. package/apps/storefront/migrations/README.md +54 -0
  10. package/apps/storefront/migrations/meta/0000_snapshot.json +347 -0
  11. package/apps/storefront/migrations/meta/0001_snapshot.json +347 -0
  12. package/apps/storefront/migrations/meta/_journal.json +20 -0
  13. package/apps/storefront/package.json +42 -0
  14. package/apps/storefront/public/brand/northwind-logo-dark.svg +4 -0
  15. package/apps/storefront/public/brand/northwind-logo-light.svg +4 -0
  16. package/apps/storefront/public/brand/northwind-og.svg +13 -0
  17. package/apps/storefront/public/favicon.svg +5 -0
  18. package/apps/storefront/public/js/dashboard-team.js +147 -0
  19. package/apps/storefront/public/js/dashboard-vendor-search.js +115 -0
  20. package/apps/storefront/public/shared/commerce-chrome.css +369 -0
  21. package/apps/storefront/src/components/Badge.astro +34 -0
  22. package/apps/storefront/src/components/Breadcrumbs.astro +45 -0
  23. package/apps/storefront/src/components/CollectionCard.astro +56 -0
  24. package/apps/storefront/src/components/EmptyState.astro +55 -0
  25. package/apps/storefront/src/components/Img.astro +68 -0
  26. package/apps/storefront/src/components/ProductCard.astro +228 -0
  27. package/apps/storefront/src/components/Section.astro +39 -0
  28. package/apps/storefront/src/components/Seo.astro +70 -0
  29. package/apps/storefront/src/components/WidgetFrame.astro +34 -0
  30. package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +531 -0
  31. package/apps/storefront/src/components/admin/AdminCustomersTab.astro +597 -0
  32. package/apps/storefront/src/components/admin/AdminFoxyCartSettingsPanel.astro +227 -0
  33. package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +465 -0
  34. package/apps/storefront/src/components/admin/AdminProductsTab.astro +901 -0
  35. package/apps/storefront/src/components/admin/AdminReportingTab.astro +435 -0
  36. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +782 -0
  37. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +1223 -0
  38. package/apps/storefront/src/components/auth/AuthBadge.astro +44 -0
  39. package/apps/storefront/src/components/chrome/AnnouncementBar.astro +108 -0
  40. package/apps/storefront/src/components/chrome/MegaMenu.astro +26 -0
  41. package/apps/storefront/src/components/chrome/NavDropdown.astro +201 -0
  42. package/apps/storefront/src/components/chrome/SiteFooter.astro +201 -0
  43. package/apps/storefront/src/components/chrome/SiteHeader.astro +234 -0
  44. package/apps/storefront/src/components/chrome/UtilityNav.astro +79 -0
  45. package/apps/storefront/src/components/commerce/ArticleCard.astro +242 -0
  46. package/apps/storefront/src/components/commerce/BrandGrid.astro +175 -0
  47. package/apps/storefront/src/components/commerce/FoxyLoader.astro +23 -0
  48. package/apps/storefront/src/components/commerce/FoxySidecartStyles.astro +61 -0
  49. package/apps/storefront/src/components/commerce/PriceDisplay.astro +91 -0
  50. package/apps/storefront/src/components/commerce/ProductCarousel.astro +197 -0
  51. package/apps/storefront/src/components/commerce/ProductGrid.astro +75 -0
  52. package/apps/storefront/src/components/commerce/RatingStars.astro +58 -0
  53. package/apps/storefront/src/components/commerce/ReviewList.astro +54 -0
  54. package/apps/storefront/src/components/commerce/ReviewSummary.astro +44 -0
  55. package/apps/storefront/src/components/commerce/TagFilterCloud.astro +149 -0
  56. package/apps/storefront/src/components/compliance/AdultSignatureNotice.astro +37 -0
  57. package/apps/storefront/src/components/compliance/ComplianceNotice.astro +99 -0
  58. package/apps/storefront/src/components/compliance/ExciseTaxNotice.astro +45 -0
  59. package/apps/storefront/src/components/compliance/NicotineWarning.astro +33 -0
  60. package/apps/storefront/src/components/compliance/PactActNotice.astro +35 -0
  61. package/apps/storefront/src/components/compliance/Prop65Warning.astro +80 -0
  62. package/apps/storefront/src/components/compliance/PurchaseLimitNotice.astro +38 -0
  63. package/apps/storefront/src/components/compliance/ShippingRestrictionNotice.astro +35 -0
  64. package/apps/storefront/src/components/compliance/StateEligibilityNotice.astro +53 -0
  65. package/apps/storefront/src/components/home/Hero.astro +164 -0
  66. package/apps/storefront/src/components/islands/AgeGate.tsx +109 -0
  67. package/apps/storefront/src/components/islands/ImageGallery.tsx +237 -0
  68. package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +73 -0
  69. package/apps/storefront/src/components/islands/MobileNav.tsx +239 -0
  70. package/apps/storefront/src/components/islands/QuickView.tsx +138 -0
  71. package/apps/storefront/src/components/islands/RecentlyViewed.tsx +122 -0
  72. package/apps/storefront/src/components/islands/SavedList.tsx +99 -0
  73. package/apps/storefront/src/components/islands/SearchTypeahead.tsx +248 -0
  74. package/apps/storefront/src/components/islands/VariantSelector.tsx +375 -0
  75. package/apps/storefront/src/components/marketing/CardGrid.astro +72 -0
  76. package/apps/storefront/src/components/marketing/CodeSample.astro +20 -0
  77. package/apps/storefront/src/components/marketing/CtaBand.astro +24 -0
  78. package/apps/storefront/src/components/marketing/Faq.astro +37 -0
  79. package/apps/storefront/src/components/marketing/Integrations.astro +63 -0
  80. package/apps/storefront/src/components/marketing/MarketingBlocks.astro +53 -0
  81. package/apps/storefront/src/components/marketing/MarketingCtas.astro +48 -0
  82. package/apps/storefront/src/components/marketing/MarketingHero.astro +72 -0
  83. package/apps/storefront/src/components/marketing/ProofStrip.astro +37 -0
  84. package/apps/storefront/src/components/marketing/SplitCompare.astro +53 -0
  85. package/apps/storefront/src/components/marketing/Steps.astro +36 -0
  86. package/apps/storefront/src/components/marketing/Testimonials.astro +38 -0
  87. package/apps/storefront/src/components/marketing/TrustBar.astro +22 -0
  88. package/apps/storefront/src/components/membership/IllustrativeLabel.astro +56 -0
  89. package/apps/storefront/src/components/membership/TierCards.astro +164 -0
  90. package/apps/storefront/src/components/membership/TierComparisonTable.astro +148 -0
  91. package/apps/storefront/src/components/plp/ActiveFilters.astro +79 -0
  92. package/apps/storefront/src/components/plp/FacetSidebar.astro +199 -0
  93. package/apps/storefront/src/components/plp/Pagination.astro +99 -0
  94. package/apps/storefront/src/components/plp/SortSelect.astro +64 -0
  95. package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +95 -0
  96. package/apps/storefront/src/components/subscription/HowItWorksSteps.astro +67 -0
  97. package/apps/storefront/src/components/subscription/ManageSubscriptionEntry.astro +67 -0
  98. package/apps/storefront/src/components/subscription/SavingsExplainer.astro +101 -0
  99. package/apps/storefront/src/config/adminTargetTenant.ts +28 -0
  100. package/apps/storefront/src/config/devTenantSeed.ts +78 -0
  101. package/apps/storefront/src/config/devTenants.ts +16 -0
  102. package/apps/storefront/src/config/kvTenantResolver.ts +87 -0
  103. package/apps/storefront/src/config/resolver.ts +102 -0
  104. package/apps/storefront/src/config/tenants.ts +87 -0
  105. package/apps/storefront/src/layouts/Layout.astro +368 -0
  106. package/apps/storefront/src/lib/admin/README.md +122 -0
  107. package/apps/storefront/src/lib/admin/ai/approvalMode.ts +21 -0
  108. package/apps/storefront/src/lib/admin/ai/askEnvelope.ts +48 -0
  109. package/apps/storefront/src/lib/admin/ai/audit.ts +14 -0
  110. package/apps/storefront/src/lib/admin/ai/index.ts +6 -0
  111. package/apps/storefront/src/lib/admin/ai/operationalRuns.ts +155 -0
  112. package/apps/storefront/src/lib/admin/ai/recommendation.ts +20 -0
  113. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +172 -0
  114. package/apps/storefront/src/lib/admin/checkoutStyleTarget.ts +65 -0
  115. package/apps/storefront/src/lib/admin/envelope.ts +10 -0
  116. package/apps/storefront/src/lib/admin/evidence/evidenceReference.ts +32 -0
  117. package/apps/storefront/src/lib/admin/memoizeByEnvelope.ts +27 -0
  118. package/apps/storefront/src/lib/admin/orderClient.ts +146 -0
  119. package/apps/storefront/src/lib/admin/seed.ts +54 -0
  120. package/apps/storefront/src/lib/admin/services/mock/aiWorkflows.ts +59 -0
  121. package/apps/storefront/src/lib/admin/services/mock/customers.ts +86 -0
  122. package/apps/storefront/src/lib/admin/services/mock/evidence.ts +99 -0
  123. package/apps/storefront/src/lib/admin/services/mock/foxyCartSettings.ts +78 -0
  124. package/apps/storefront/src/lib/admin/services/mock/fulfillment.ts +62 -0
  125. package/apps/storefront/src/lib/admin/services/mock/index.ts +12 -0
  126. package/apps/storefront/src/lib/admin/services/mock/orders.ts +184 -0
  127. package/apps/storefront/src/lib/admin/services/mock/products.ts +105 -0
  128. package/apps/storefront/src/lib/admin/services/mock/reporting.ts +51 -0
  129. package/apps/storefront/src/lib/admin/services/mock/settings.ts +70 -0
  130. package/apps/storefront/src/lib/admin/services/mock/tenants.ts +29 -0
  131. package/apps/storefront/src/lib/admin/services/orders.ts +300 -0
  132. package/apps/storefront/src/lib/admin/viewmodels/aiWorkflows.ts +67 -0
  133. package/apps/storefront/src/lib/admin/viewmodels/customers.ts +56 -0
  134. package/apps/storefront/src/lib/admin/viewmodels/fulfillment.ts +49 -0
  135. package/apps/storefront/src/lib/admin/viewmodels/orders.ts +92 -0
  136. package/apps/storefront/src/lib/admin/viewmodels/products.ts +74 -0
  137. package/apps/storefront/src/lib/admin/viewmodels/reporting.ts +34 -0
  138. package/apps/storefront/src/lib/admin/viewmodels/settings.ts +64 -0
  139. package/apps/storefront/src/lib/adminTenantEnvelope.ts +90 -0
  140. package/apps/storefront/src/lib/analytics/index.ts +47 -0
  141. package/apps/storefront/src/lib/auth/brokerAssertion.ts +136 -0
  142. package/apps/storefront/src/lib/auth/devAuth.ts +90 -0
  143. package/apps/storefront/src/lib/auth/gatePage.ts +81 -0
  144. package/apps/storefront/src/lib/auth/identityToken.ts +341 -0
  145. package/apps/storefront/src/lib/auth/loginGate.ts +119 -0
  146. package/apps/storefront/src/lib/auth/route.ts +146 -0
  147. package/apps/storefront/src/lib/auth/session.ts +203 -0
  148. package/apps/storefront/src/lib/auth/totAccessClient.ts +503 -0
  149. package/apps/storefront/src/lib/basePath.ts +41 -0
  150. package/apps/storefront/src/lib/cfImage.ts +55 -0
  151. package/apps/storefront/src/lib/chrome/model.ts +244 -0
  152. package/apps/storefront/src/lib/chrome/parseConfig.ts +34 -0
  153. package/apps/storefront/src/lib/colors.ts +97 -0
  154. package/apps/storefront/src/lib/commerce/foxyCartSettings.ts +253 -0
  155. package/apps/storefront/src/lib/content-edit/applyDiff.ts +91 -0
  156. package/apps/storefront/src/lib/content-edit/client.ts +230 -0
  157. package/apps/storefront/src/lib/content-edit/index.ts +14 -0
  158. package/apps/storefront/src/lib/content-edit/route.ts +25 -0
  159. package/apps/storefront/src/lib/content-edit/types.ts +19 -0
  160. package/apps/storefront/src/lib/cspStaticHashes.ts +24 -0
  161. package/apps/storefront/src/lib/d1/catalog.ts +366 -0
  162. package/apps/storefront/src/lib/dashboard/staffAdmission.ts +43 -0
  163. package/apps/storefront/src/lib/dashboard/staffVendorAccess.ts +449 -0
  164. package/apps/storefront/src/lib/dashboard/tenantSelection.ts +136 -0
  165. package/apps/storefront/src/lib/dashboard/vendorQuery.ts +61 -0
  166. package/apps/storefront/src/lib/demoGate.ts +113 -0
  167. package/apps/storefront/src/lib/dev/activityIngestToken.ts +78 -0
  168. package/apps/storefront/src/lib/dev/activityStore.ts +72 -0
  169. package/apps/storefront/src/lib/dev/ai-widget-client.js +308 -0
  170. package/apps/storefront/src/lib/dev/apiBase.ts +14 -0
  171. package/apps/storefront/src/lib/dev/cliSignInCode.ts +155 -0
  172. package/apps/storefront/src/lib/dev/devDraft.ts +133 -0
  173. package/apps/storefront/src/lib/dev/hostedCockpit.ts +169 -0
  174. package/apps/storefront/src/lib/dev/livePreview.ts +77 -0
  175. package/apps/storefront/src/lib/dev/runtimeStore.ts +120 -0
  176. package/apps/storefront/src/lib/dev/storeStatus.ts +142 -0
  177. package/apps/storefront/src/lib/diag/redact.ts +20 -0
  178. package/apps/storefront/src/lib/edgeCache.ts +128 -0
  179. package/apps/storefront/src/lib/email/magicLinkInviteEmail.ts +62 -0
  180. package/apps/storefront/src/lib/env.ts +60 -0
  181. package/apps/storefront/src/lib/fixtures/sampleProducts.ts +130 -0
  182. package/apps/storefront/src/lib/format.ts +38 -0
  183. package/apps/storefront/src/lib/foxyCommerce.ts +49 -0
  184. package/apps/storefront/src/lib/imageAttrs.ts +55 -0
  185. package/apps/storefront/src/lib/jsonld.ts +166 -0
  186. package/apps/storefront/src/lib/maintenance.ts +71 -0
  187. package/apps/storefront/src/lib/membership/model.ts +97 -0
  188. package/apps/storefront/src/lib/memoryKv.ts +32 -0
  189. package/apps/storefront/src/lib/messaging/messagesClient.ts +143 -0
  190. package/apps/storefront/src/lib/placeholder.ts +99 -0
  191. package/apps/storefront/src/lib/previewReload.ts +125 -0
  192. package/apps/storefront/src/lib/priceBuckets.ts +33 -0
  193. package/apps/storefront/src/lib/rawChrome.ts +505 -0
  194. package/apps/storefront/src/lib/rawMarketingHtml.ts +136 -0
  195. package/apps/storefront/src/lib/search/index.ts +74 -0
  196. package/apps/storefront/src/lib/sections.ts +129 -0
  197. package/apps/storefront/src/lib/securityHeaders.ts +31 -0
  198. package/apps/storefront/src/lib/storyblok/content-model.ts +289 -0
  199. package/apps/storefront/src/lib/storyblok/provider.ts +607 -0
  200. package/apps/storefront/src/lib/subscription/model.ts +68 -0
  201. package/apps/storefront/src/lib/tenantRouting.ts +91 -0
  202. package/apps/storefront/src/lib/tot/ToTClient.ts +177 -0
  203. package/apps/storefront/src/lib/tot/d1Client.ts +62 -0
  204. package/apps/storefront/src/lib/tot/query.ts +249 -0
  205. package/apps/storefront/src/lib/tot/totClientInterface.ts +30 -0
  206. package/apps/storefront/src/lib/url.ts +142 -0
  207. package/apps/storefront/src/lib/wishlist.ts +63 -0
  208. package/apps/storefront/src/middleware/index.ts +455 -0
  209. package/apps/storefront/src/pages/404.astro +61 -0
  210. package/apps/storefront/src/pages/[...slug].astro +90 -0
  211. package/apps/storefront/src/pages/admin.astro +1675 -0
  212. package/apps/storefront/src/pages/api/admin/orders/[id].ts +53 -0
  213. package/apps/storefront/src/pages/api/admin/orders.ts +71 -0
  214. package/apps/storefront/src/pages/api/auth/logout.ts +21 -0
  215. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +107 -0
  216. package/apps/storefront/src/pages/api/auth/send.ts +85 -0
  217. package/apps/storefront/src/pages/api/auth/verify.ts +135 -0
  218. package/apps/storefront/src/pages/api/cache-purge.ts +44 -0
  219. package/apps/storefront/src/pages/api/checkout-style.css.ts +43 -0
  220. package/apps/storefront/src/pages/api/cli/policy.ts +70 -0
  221. package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +164 -0
  222. package/apps/storefront/src/pages/api/csp-report.ts +25 -0
  223. package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +124 -0
  224. package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +120 -0
  225. package/apps/storefront/src/pages/api/dev/activity.ts +124 -0
  226. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +73 -0
  227. package/apps/storefront/src/pages/api/dev/diff.ts +68 -0
  228. package/apps/storefront/src/pages/api/dev/file.ts +57 -0
  229. package/apps/storefront/src/pages/api/dev/locate.ts +125 -0
  230. package/apps/storefront/src/pages/api/dev/publish.ts +128 -0
  231. package/apps/storefront/src/pages/api/dev/tree.ts +70 -0
  232. package/apps/storefront/src/pages/api/dev/write.ts +116 -0
  233. package/apps/storefront/src/pages/api/newsletter.ts +104 -0
  234. package/apps/storefront/src/pages/api/request-access.ts +172 -0
  235. package/apps/storefront/src/pages/auth/login.astro +149 -0
  236. package/apps/storefront/src/pages/auth/magic.astro +105 -0
  237. package/apps/storefront/src/pages/capabilities.astro +292 -0
  238. package/apps/storefront/src/pages/collections/[handle].astro +177 -0
  239. package/apps/storefront/src/pages/collections/index.astro +59 -0
  240. package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +100 -0
  241. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +154 -0
  242. package/apps/storefront/src/pages/dashboard/index.astro +160 -0
  243. package/apps/storefront/src/pages/index.astro +290 -0
  244. package/apps/storefront/src/pages/llms.txt.ts +92 -0
  245. package/apps/storefront/src/pages/products/[handle].astro +402 -0
  246. package/apps/storefront/src/pages/robots.txt.ts +24 -0
  247. package/apps/storefront/src/pages/saved.astro +35 -0
  248. package/apps/storefront/src/pages/search.astro +150 -0
  249. package/apps/storefront/src/pages/sitemap.xml.ts +78 -0
  250. package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +1280 -0
  251. package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +229 -0
  252. package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +144 -0
  253. package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +116 -0
  254. package/apps/storefront/src/pages/style-guide/index.astro +98 -0
  255. package/apps/storefront/src/pages/tenants/[id]/[...path].ts +57 -0
  256. package/apps/storefront/src/styles/global.css +292 -0
  257. package/apps/storefront/src/themes/reference.ts +68 -0
  258. package/apps/storefront/src/themes/schema.ts +111 -0
  259. package/apps/storefront/src/themes/tenants/index.ts +51 -0
  260. package/apps/storefront/tsconfig.json +16 -0
  261. package/apps/storefront/vitest.config.ts +22 -0
  262. package/package.json +26 -0
  263. package/packages/public-runtime/LICENSE +58 -0
  264. package/packages/public-runtime/package.json +22 -0
  265. package/packages/public-runtime/src/binary-path.ts +68 -0
  266. package/packages/public-runtime/src/capabilities.ts +89 -0
  267. package/packages/public-runtime/src/catalog-api.ts +81 -0
  268. package/packages/public-runtime/src/catalog-d1.ts +192 -0
  269. package/packages/public-runtime/src/checkout-style-publish.ts +200 -0
  270. package/packages/public-runtime/src/checkout-style.ts +375 -0
  271. package/packages/public-runtime/src/csp.ts +109 -0
  272. package/packages/public-runtime/src/customization-preview.ts +197 -0
  273. package/packages/public-runtime/src/customization-reconcile.ts +63 -0
  274. package/packages/public-runtime/src/customization-runtime.ts +157 -0
  275. package/packages/public-runtime/src/customization-scripts.ts +64 -0
  276. package/packages/public-runtime/src/customization-versioning.ts +95 -0
  277. package/packages/public-runtime/src/foxy-orders.ts +136 -0
  278. package/packages/public-runtime/src/foxy.ts +196 -0
  279. package/packages/public-runtime/src/hot-standby-readiness.ts +140 -0
  280. package/packages/public-runtime/src/index.ts +34 -0
  281. package/packages/public-runtime/src/product.ts +159 -0
  282. package/packages/public-runtime/src/search.ts +45 -0
  283. package/packages/public-runtime/src/tenant-assets.ts +444 -0
  284. package/packages/public-runtime/src/tenant.ts +384 -0
  285. package/packages/public-runtime/tsconfig.json +8 -0
  286. package/packages/public-runtime/vitest.config.ts +8 -0
  287. package/pnpm-workspace.yaml +7 -0
  288. package/scripts/build/copy-tenant-assets.mjs +69 -0
  289. package/scripts/dev/ai-edit.mjs +183 -0
  290. package/scripts/dev/checkout-watch.mjs +116 -0
  291. package/scripts/dev/file-browser.mjs +159 -0
  292. package/scripts/dev/file-writer.mjs +141 -0
  293. package/scripts/dev/git-status.mjs +171 -0
  294. package/scripts/dev/locate-handler.mjs +163 -0
  295. package/scripts/dev/locate.mjs +112 -0
  296. package/scripts/dev/port-check.mjs +48 -0
  297. package/scripts/dev/safe-path.mjs +109 -0
  298. package/scripts/dev/shot.mjs +46 -0
  299. package/scripts/dev/tenant-source-root.mjs +76 -0
  300. package/scripts/dev/unified-diff.mjs +81 -0
  301. package/scripts/tot-dev.mjs +376 -0
  302. package/tenants/README.md +14 -0
  303. package/tenants/home/content/chrome.json +7 -0
  304. package/tenants/home/content/home.html +58 -0
  305. package/tenants/home/content/home.json +11 -0
  306. package/tenants/home/public/js/storefront.js +210 -0
  307. package/tenants/home/public/logo-wordmark.png +0 -0
  308. package/tenants/home/public/pages/storefront.css +212 -0
  309. package/tenants/home/theme.json +6 -0
  310. package/tsconfig.base.json +20 -0
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `tot dev` — the developer's on-save local preview loop (the magic).
4
+ *
5
+ * Boots the storefront's `astro dev` (which already gives Vite HMR / live-reload
6
+ * on save) and points the browser at the developer's tenant via the DNS-free
7
+ * path-prefix route (`/<appDomain>/`). Edit a file under `content/<tenant>/`
8
+ * (home.html, pages, chrome) or the tenant theme → save → the browser reloads.
9
+ * Private, on-save, NO publish: product data renders from local fixtures; nothing
10
+ * is written to KV or shared. ("Publish" — to test/live — stays a separate,
11
+ * deliberate, compliance-gated step.)
12
+ *
13
+ * TWO MODES
14
+ * 1. MONOREPO (default): `node scripts/tot-dev.mjs <tenant>` — runs astro dev
15
+ * over the in-tree colocated `tenants/<tenant>/` layout (content/, public/,
16
+ * theme.json).
17
+ * 2. STANDALONE: `node scripts/tot-dev.mjs --workspace <checkout-path>` — the
18
+ * developer has cloned their tenant's Gitea repo (a FLAT, tenant-centric
19
+ * layout: `content/`, `public/`, `theme.json`, `.tot/config.json`). This mode
20
+ * reads the checkout's `.tot/config.json` `mappings` and grafts each workspace
21
+ * prefix into the storefront paths the app globs, then boots the same astro dev.
22
+ * Edits in the CHECKOUT hot-reload via Vite. On exit the graft (and any
23
+ * backed-up in-tree tenant content) is restored — the tree is left clean.
24
+ * Phase-0 bridge: the platform still comes from THIS storefront checkout; the
25
+ * containerized "platform baked in, tenant clone mounted" version is deferred.
26
+ *
27
+ * WHY COPY+WATCH, NOT SYMLINK: a symlink from the in-tree path to the checkout
28
+ * resolves (in Vite) to the checkout's REAL path, which is OUTSIDE the app root,
29
+ * so Vite's `server.fs.allow` denies the `?raw`/glob import ("Denied ID …"). So
30
+ * we COPY the checkout's files into the in-tree paths (inside the app root, which
31
+ * Vite trusts) and run a recursive `fs.watch` on the checkout that re-syncs each
32
+ * changed file back into the in-tree copy on save — which is what fires Vite HMR.
33
+ *
34
+ * Two things learned the hard way (see the two-liners below):
35
+ * - Tenant is selected by the URL path-prefix (`/<appDomain>/`), NOT by
36
+ * DEFAULT_TENANT_ID — the @astrojs/cloudflare dev runtime (workerd) reads env
37
+ * from .dev.vars/bindings, so a spawned process.env var never reaches it, and
38
+ * a bare `/` falls back to the first static tenant (pilot).
39
+ * - `--host` is passed so the server binds all interfaces (0.0.0.0 + ::).
40
+ * Without it astro/vite may bind IPv6 `::1` only, and browsers that resolve
41
+ * `localhost` to IPv4 `127.0.0.1` get "unable to connect".
42
+ *
43
+ * Usage:
44
+ * node scripts/tot-dev.mjs <tenant> [--port 4321]
45
+ * node scripts/tot-dev.mjs sample-store.example
46
+ * node scripts/tot-dev.mjs --workspace ~/tot/site [--port 4321] (standalone)
47
+ * (from a workspace dir with .tot/config.json) node <repo>/scripts/tot-dev.mjs
48
+ *
49
+ * Ctrl-C stops the server. Exit 1 = can't resolve tenant/domain/workspace; 2 = bad usage.
50
+ */
51
+ import { spawn, spawnSync } from "node:child_process";
52
+ import {
53
+ readFileSync,
54
+ existsSync,
55
+ lstatSync,
56
+ mkdirSync,
57
+ renameSync,
58
+ rmSync,
59
+ cpSync,
60
+ } from "node:fs";
61
+ import { dirname, resolve, join } from "node:path";
62
+ import { fileURLToPath } from "node:url";
63
+ import { resolveDevSourceRoot } from "./dev/tenant-source-root.mjs";
64
+ import { firstFreePort } from "./dev/port-check.mjs";
65
+ import { resync, startWatch } from "./dev/checkout-watch.mjs";
66
+
67
+ const here = dirname(fileURLToPath(import.meta.url));
68
+ const repoRoot = resolve(here, ".."); // scripts/ -> repo root
69
+
70
+ const argv = process.argv.slice(2);
71
+ const flag = (name, def) => {
72
+ const i = argv.indexOf(`--${name}`);
73
+ return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : def;
74
+ };
75
+
76
+ // `--version` / `-v` / `version` → print the runner's PUBLISHED version and exit.
77
+ // Reads the emitted root package.json (build-runner.mjs stamps RUNNER_VERSION in
78
+ // PUBLISH mode). This is the version the 2026-07-14 cache-staleness bug hid — the
79
+ // runner kept being served from a stale public-0.1.0 dir with no way to see it —
80
+ // so making it directly checkable (npm bin `storefront-runner`) is part of the fix.
81
+ if (argv[0] === "--version" || argv[0] === "-v" || argv[0] === "version") {
82
+ let v = "0.0.0";
83
+ try {
84
+ v = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")).version || v;
85
+ } catch {
86
+ /* fall back to 0.0.0 */
87
+ }
88
+ console.log(v);
89
+ process.exit(0);
90
+ }
91
+
92
+ // --- workspace (standalone) mode? --------------------------------------------
93
+ // `--workspace <path>` points at a developer's cloned tenant repo (flat layout +
94
+ // .tot/config.json). Absent → classic monorepo mode.
95
+ const workspacePath = flag("workspace", null);
96
+
97
+ // --- resolve the tenant + its appDomain --------------------------------------
98
+ // In monorepo mode the workspace config (if any) is read from CWD, as before.
99
+ // In standalone mode it comes from the checkout's .tot/config.json.
100
+ const cfgPath = workspacePath
101
+ ? join(resolve(workspacePath), ".tot", "config.json")
102
+ : join(process.cwd(), ".tot", "config.json");
103
+ let cfg = null;
104
+ if (existsSync(cfgPath)) {
105
+ try {
106
+ cfg = JSON.parse(readFileSync(cfgPath, "utf8"));
107
+ } catch {
108
+ /* ignore */
109
+ }
110
+ }
111
+
112
+ if (workspacePath && !cfg) {
113
+ console.error(
114
+ `✗ standalone mode: no readable .tot/config.json under ${resolve(workspacePath)}\n` +
115
+ ` Is this a materialized tenant checkout (content/, public/, theme.json, .tot/config.json)?`,
116
+ );
117
+ process.exit(1);
118
+ }
119
+
120
+ let tenant = argv[0] && !argv[0].startsWith("--") ? argv[0] : null;
121
+ tenant = tenant ?? cfg?.tenant ?? null;
122
+
123
+ if (!tenant) {
124
+ console.error(
125
+ "usage: node scripts/tot-dev.mjs <tenant> [--port 4321]\n" +
126
+ " node scripts/tot-dev.mjs --workspace <checkout-path> [--port 4321]\n" +
127
+ " (or run from a workspace dir containing .tot/config.json)",
128
+ );
129
+ process.exit(2);
130
+ }
131
+
132
+ /** The path-prefix segment that selects this tenant: its registered appDomain. */
133
+ function resolveDomain(id) {
134
+ // A workspace marker's `scope` carries the appDomain — but only trust it when
135
+ // it looks like a hostname (has a dot), since the path-prefix route only
136
+ // matches a domain that strictly claims the tenant (see tenantRouting.ts).
137
+ if (cfg?.scope && cfg.scope.includes(".")) return cfg.scope;
138
+ try {
139
+ const src = readFileSync(
140
+ resolve(repoRoot, "apps/storefront/src/config/tenants.ts"),
141
+ "utf8",
142
+ );
143
+ // Each tenant block declares tenant_id then appDomain; pair them up.
144
+ const re = /tenant_id:\s*"([^"]+)"[\s\S]*?appDomain:\s*"([^"]+)"/g;
145
+ for (let m; (m = re.exec(src)); ) if (m[1] === id) return m[2];
146
+ } catch {
147
+ /* ignore */
148
+ }
149
+ return null;
150
+ }
151
+
152
+ const domain = resolveDomain(tenant);
153
+ if (!domain) {
154
+ console.error(
155
+ `✗ couldn't resolve a domain for "${tenant}" — is it registered in ` +
156
+ `apps/storefront/src/config/tenants.ts (or set as a hostname in .tot/config.json scope)?`,
157
+ );
158
+ process.exit(1);
159
+ }
160
+
161
+ // --- standalone: graft the checkout into the storefront's globbed paths -------
162
+ // COPY the checkout's files into the in-tree paths (so Vite's fs.allow trusts them)
163
+ // + fs.watch the checkout to re-sync each save into the in-tree copy (fires HMR).
164
+ // Records let us undo everything on exit (leave the tree exactly as found).
165
+ /** @type {{ target: string, backup: string|null }[]} */
166
+ const grafts = [];
167
+ /** @type {import("node:fs").FSWatcher[]} */
168
+ const watchers = [];
169
+ let cleanedUp = false;
170
+
171
+ function isSymlink(p) {
172
+ try {
173
+ return lstatSync(p).isSymbolicLink();
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Move `from` → `to`, with a cross-device fallback. In the container the baked-in
181
+ * `tenants/<id>/` lives in a read-only overlay layer, and overlayfs can't rename a
182
+ * directory across the layer boundary — `renameSync` throws EXDEV. Fall back to a
183
+ * recursive copy + remove so the graft's move-aside/restore still works there. On
184
+ * macOS (same-fs) the fast rename is taken.
185
+ */
186
+ function movePath(from, to) {
187
+ try {
188
+ renameSync(from, to);
189
+ } catch (e) {
190
+ if (e?.code !== "EXDEV") throw e;
191
+ cpSync(from, to, { recursive: true });
192
+ rmSync(from, { recursive: true, force: true });
193
+ }
194
+ }
195
+
196
+ function cleanupGrafts() {
197
+ if (cleanedUp) return;
198
+ cleanedUp = true;
199
+ for (const w of watchers) {
200
+ try {
201
+ w.close();
202
+ } catch {
203
+ /* best effort */
204
+ }
205
+ }
206
+ // Undo in reverse: remove our copied-in files, then restore any backed-up content.
207
+ for (const { target, backup } of grafts.reverse()) {
208
+ try {
209
+ if (existsSync(target) || isSymlink(target)) rmSync(target, { recursive: true, force: true });
210
+ } catch {
211
+ /* best effort */
212
+ }
213
+ if (backup) {
214
+ try {
215
+ movePath(backup, target);
216
+ } catch {
217
+ /* best effort */
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ // resync() + startWatch() moved to scripts/dev/checkout-watch.mjs (testable +
224
+ // ships in the runner via copyMjsFlat). startWatch now polls by default for
225
+ // reliable save detection — native fs.watch dropped editor atomic-saves on macOS.
226
+
227
+ if (workspacePath) {
228
+ const wsRoot = resolve(workspacePath);
229
+ const mappings = Array.isArray(cfg.mappings) ? cfg.mappings : [];
230
+ if (mappings.length === 0) {
231
+ console.error(`✗ .tot/config.json has no "mappings" — cannot graft the checkout.`);
232
+ process.exit(1);
233
+ }
234
+
235
+ console.log(`\n tot dev — standalone (${tenant})`);
236
+ console.log(` checkout: ${wsRoot}`);
237
+
238
+ for (const map of mappings) {
239
+ // `workspace` is the checkout-relative prefix (e.g. "content/", "theme.json").
240
+ // COLOCATION: a tenant IS the repo-root dir `tenants/<id>/`, and the checkout
241
+ // (a `git subtree split --prefix=tenants/<id>` of that dir) has the SAME layout
242
+ // at its root — so the graft is an identity map: content/ -> tenants/<id>/content/,
243
+ // public/ -> tenants/<id>/public/, theme.json -> tenants/<id>/theme.json. We map
244
+ // structurally (ignoring any legacy `repo` field in the checkout's .tot/config.json).
245
+ const wsRel = String(map.workspace ?? "").replace(/\/+$/, "");
246
+ if (!wsRel) continue;
247
+ const repoRel = join("tenants", tenant, wsRel);
248
+
249
+ const source = resolve(wsRoot, wsRel); // real files in the checkout
250
+ const target = resolve(repoRoot, repoRel); // where the app globs them
251
+ const isDir = map.kind !== "file";
252
+
253
+ if (!existsSync(source)) {
254
+ console.warn(` ⚠ checkout has no ${wsRel} — skipping (${map.kind ?? "?"})`);
255
+ continue;
256
+ }
257
+
258
+ // Ensure the destination's parent exists (e.g. content/ or themes/tenants/).
259
+ mkdirSync(dirname(target), { recursive: true });
260
+
261
+ // If real in-tree content already occupies the destination, move it aside so
262
+ // we can restore it verbatim on exit (never destroy tracked tenant content).
263
+ let backup = null;
264
+ if (existsSync(target) || isSymlink(target)) {
265
+ backup = `${target}.__totdev_bak_${process.pid}`;
266
+ movePath(target, backup);
267
+ }
268
+
269
+ // COPY the checkout's files into the in-tree path (inside Vite's trusted root).
270
+ cpSync(source, target, { recursive: true });
271
+ grafts.push({ target, backup });
272
+
273
+ // WATCH the checkout for saves → re-sync into the in-tree copy → Vite HMR.
274
+ // `startWatch` polls by default (reliable — native fs.watch dropped editor
275
+ // atomic-saves on macOS); TOT_DEV_NATIVE_WATCH=1 opts into event-driven watch.
276
+ try {
277
+ const w = startWatch(source, isDir, (rel) => resync(source, target, rel));
278
+ watchers.push(w);
279
+ } catch (e) {
280
+ console.warn(` ⚠ could not watch ${wsRel} (edits won't hot-reload): ${e?.message}`);
281
+ }
282
+
283
+ console.log(` ↪ ${repoRel} ⇐ ${wsRel}${backup ? " (in-tree content backed up)" : ""}`);
284
+ }
285
+
286
+ if (grafts.length === 0) {
287
+ console.error(`✗ nothing to graft — the checkout had none of the mapped prefixes.`);
288
+ cleanupGrafts();
289
+ process.exit(1);
290
+ }
291
+ } else if (!existsSync(resolve(repoRoot, "tenants", tenant, "content"))) {
292
+ console.warn(`⚠ no tenants/${tenant}/content/ found — the tenant may not be registered yet.`);
293
+ }
294
+
295
+ // Resolve the port BEFORE printing a URL or booting astro. Vite's `strictPort`
296
+ // defaults off, so a taken port makes astro SILENTLY drift to the next free one —
297
+ // which then mismatches the URL we (and the CLI's liveness poll) advertise, and a
298
+ // stale server on the original port produced a false "You're live" (2026-07-14).
299
+ // Instead we pick the first free port ourselves and PRINT it, so a busy default
300
+ // port "just works" (better UX than failing). See port-check.mjs.
301
+ const requestedPort = Number(flag("port", "4321"));
302
+ const resolvedPort = await firstFreePort(requestedPort);
303
+ const port = String(resolvedPort);
304
+ if (resolvedPort !== requestedPort) {
305
+ console.log(` ⚠ port ${requestedPort} was busy — starting on ${resolvedPort} instead.`);
306
+ }
307
+
308
+ // Land the developer on their store ROOT (plain save→reload HMR against their own
309
+ // site) — not the /dev cockpit, which is a hosted-only surface excluded from the
310
+ // local runner build (see build-runner.mjs DROP + stripCockpitPlugins).
311
+ const url = `http://localhost:${port}/${domain}/`;
312
+
313
+ console.log(`\n tot dev — ${tenant}`);
314
+ console.log(` → ${url}`);
315
+ if (workspacePath) {
316
+ console.log(` Edit files in your CHECKOUT (content/*.html, theme.json, public/…) + save → the browser reloads.`);
317
+ } else {
318
+ console.log(` Edit tenants/${tenant}/content/*.html (or the theme) + save → the browser reloads.`);
319
+ }
320
+ console.log(` Private local preview — nothing is published. Server binds all interfaces (LAN-visible). Ctrl-C to stop.\n`);
321
+
322
+ // --- mirror colocated tenant public/ assets into the storefront public/ dir ----
323
+ // astro dev is exec'd directly below (bypassing the `predev` npm lifecycle), so
324
+ // run the copy here: tenants/<id>/public/** -> apps/storefront/public/tenants/<id>/.
325
+ // Must run AFTER any standalone graft (so a grafted checkout's public/ is mirrored).
326
+ {
327
+ const r = spawnSync(
328
+ "node",
329
+ [resolve(repoRoot, "scripts/build/copy-tenant-assets.mjs")],
330
+ { cwd: repoRoot, stdio: "inherit" },
331
+ );
332
+ if (r.status !== 0) {
333
+ console.warn(" ⚠ copy-tenant-assets failed — static tenant assets may 404.");
334
+ }
335
+ }
336
+
337
+ // --- boot astro dev (Vite HMR); --host so IPv4 `localhost`/127.0.0.1 both work -
338
+ // In standalone mode we MUST keep astro in the FOREGROUND, attached to this
339
+ // process: Astro 7 auto-detects an agent-run terminal and forks the dev server to
340
+ // a detached daemon (`astro dev stop/status`), which would make THIS process exit
341
+ // immediately — firing our cleanup and tearing down the graft symlinks while the
342
+ // daemon keeps serving the (restored) in-tree content. Setting ASTRO_DEV_BACKGROUND
343
+ // tells Astro "you are already the managed child → run foreground" (see astro's
344
+ // cli/dev/{index,background}.js), so our SIGINT/exit lifecycle and symlink cleanup
345
+ // stay correct. Monorepo mode is left untouched (current behavior).
346
+ // TOT_DEV_TENANT tells the inline-AI edit loop (WS5 G1 — astro.config.mjs's
347
+ // inlineAiEditPlugin, scripts/dev/ai-edit.mjs) which tenant's content/theme to
348
+ // read+write: one `tot dev <tenant>` run always serves exactly one tenant, so
349
+ // there's no per-request domain lookup to do.
350
+ // TOT_DEV_SOURCE_ROOT tells the /dev file tools (C3 tree/viewer, C4 diff, C5
351
+ // writer — scripts/dev/tenant-source-root.mjs) WHERE the developer's editable
352
+ // source lives. STANDALONE: the developer's REAL checkout (wsRoot) — the tools
353
+ // MUST read+write it, NOT the in-tree graft, or browser edits are silently lost
354
+ // on the next resync. MONOREPO: the in-tree tenants/<id>/ dir.
355
+ const devSourceRoot = resolveDevSourceRoot(workspacePath, repoRoot, tenant);
356
+ const spawnEnv = workspacePath
357
+ ? { ...process.env, ASTRO_DEV_BACKGROUND: "1", TOT_DEV_TENANT: tenant, TOT_DEV_SOURCE_ROOT: devSourceRoot }
358
+ : { ...process.env, TOT_DEV_TENANT: tenant, TOT_DEV_SOURCE_ROOT: devSourceRoot };
359
+ const child = spawn(
360
+ "pnpm",
361
+ ["--filter", "@tot/storefront-runner", "exec", "astro", "dev", "--port", port, "--host"],
362
+ { cwd: repoRoot, stdio: "inherit", env: spawnEnv },
363
+ );
364
+
365
+ // Clean up grafts on every exit path so we never leave symlinks in the tree.
366
+ function shutdown(signal) {
367
+ child.kill(signal);
368
+ }
369
+ child.on("exit", (code) => {
370
+ cleanupGrafts();
371
+ process.exit(code ?? 0);
372
+ });
373
+ process.on("SIGINT", () => shutdown("SIGINT"));
374
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
375
+ // Belt-and-suspenders: if the parent dies without the child exit firing.
376
+ process.on("exit", cleanupGrafts);
@@ -0,0 +1,14 @@
1
+ # tenants/
2
+
3
+ This directory is the **runtime graft target** for the local dev loop — it ships
4
+ **empty** on purpose.
5
+
6
+ The storefront runner is a moat-free LOCAL dev harness. It bundles **no** tenant
7
+ content. When you run `tot dev`, your OWN tenant checkout (content, theme, public
8
+ assets) is grafted into `tenants/<your-id>/` here, and the renderer's
9
+ checkout-glob resolver discovers and serves it at `/<your-domain>/`. On exit the
10
+ graft is removed and this directory is left empty again.
11
+
12
+ Do not commit real merchant content here — the runner's build-time clean-guard
13
+ (`scripts/build/check-runner-clean.mjs`) fails the build if anything but this
14
+ README appears under `tenants/`.
@@ -0,0 +1,7 @@
1
+ {
2
+ "nav": [],
3
+ "footer": {
4
+ "columns": [],
5
+ "social": []
6
+ }
7
+ }
@@ -0,0 +1,58 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Storefront runner — local dev</title>
7
+ <meta name="description" content="You're running the Token of Trust storefront runner locally.">
8
+ <meta name="robots" content="noindex,nofollow">
9
+ <style>
10
+ :root { color-scheme: light dark; }
11
+ * { box-sizing: border-box; margin: 0; }
12
+ body {
13
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
14
+ line-height: 1.6; color: #16243d; background: #f5f7fa;
15
+ min-height: 100vh; display: grid; place-items: center; padding: 2rem;
16
+ }
17
+ .card {
18
+ max-width: 640px; width: 100%; background: #fff; border: 1px solid #e3e8ef;
19
+ border-radius: 16px; padding: 2.5rem; box-shadow: 0 6px 24px rgba(15,33,56,.08);
20
+ }
21
+ .badge {
22
+ display: inline-block; font-size: .78rem; font-weight: 600; letter-spacing: .04em;
23
+ text-transform: uppercase; color: #1b3b64; background: #eaeef4;
24
+ padding: .3rem .7rem; border-radius: 999px; margin-bottom: 1.1rem;
25
+ }
26
+ h1 { font-size: 1.7rem; line-height: 1.2; margin-bottom: .6rem; color: #0f2138; }
27
+ p { color: #5b6a82; margin-bottom: 1rem; }
28
+ code {
29
+ font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: .92em;
30
+ background: #f5f7fa; border: 1px solid #e3e8ef; border-radius: 6px; padding: .1rem .4rem;
31
+ }
32
+ ul { margin: 0 0 1rem 1.2rem; color: #5b6a82; }
33
+ li { margin-bottom: .4rem; }
34
+ .muted { font-size: .88rem; color: #8a95a8; margin-bottom: 0; }
35
+ @media (prefers-color-scheme: dark) {
36
+ body { color: #e6ebf3; background: #0f1622; }
37
+ .card { background: #16202e; border-color: #26313f; box-shadow: none; }
38
+ h1 { color: #f2f5f9; }
39
+ p, ul { color: #9aa6b8; }
40
+ .badge { color: #cdd8e6; background: #22303f; }
41
+ code { background: #0f1622; border-color: #26313f; }
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <main class="card">
47
+ <span class="badge">Local dev</span>
48
+ <h1>You're running the Token of Trust storefront runner locally.</h1>
49
+ <p>This is the default home page. To make it yours, edit
50
+ <code>tenants/home/content/home.html</code> and save — this page reloads automatically.</p>
51
+ <ul>
52
+ <li>Your real store renders here once you check one out with the Token of Trust CLI.</li>
53
+ <li>Visit <code>/sample-store.example/</code> to see the bundled sample store, including the 21+ age-gate and nicotine-warning compliance affordances.</li>
54
+ </ul>
55
+ <p class="muted">Serving from the storefront runner. No account or network required.</p>
56
+ </main>
57
+ </body>
58
+ </html>
@@ -0,0 +1,11 @@
1
+ {
2
+ "seoTitle": "Storefront runner — local dev",
3
+ "seoDescription": "You're running the Token of Trust storefront runner locally.",
4
+ "blocks": [
5
+ {
6
+ "component": "hero",
7
+ "headline": "Running locally",
8
+ "subhead": "You're running the Token of Trust storefront runner. Edit tenants/home/content/home.html and save to change this page."
9
+ }
10
+ ]
11
+ }