@tokenoftrust/storefront-runner 1.3.2 → 1.3.4-rc.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 (79) hide show
  1. package/apps/storefront/astro.config.mjs +59 -0
  2. package/apps/storefront/env.d.ts +24 -8
  3. package/apps/storefront/package.json +1 -1
  4. package/apps/storefront/src/components/admin/{AdminFoxyCartSettingsPanel.astro → AdminCheckoutSettingsPanel.astro} +50 -51
  5. package/apps/storefront/src/components/admin/AdminSettingsTab.astro +2 -2
  6. package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +613 -0
  7. package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +3 -3
  8. package/apps/storefront/src/components/chrome/SiteHeader.astro +3 -4
  9. package/apps/storefront/src/components/commerce/{FoxySidecartStyles.astro → CartDrawerStyles.astro} +13 -14
  10. package/apps/storefront/src/components/commerce/CheckoutLoader.astro +21 -0
  11. package/apps/storefront/src/components/islands/VariantSelector.tsx +101 -20
  12. package/apps/storefront/src/config/adminTargetTenant.ts +2 -2
  13. package/apps/storefront/src/config/devTenantSeed.ts +65 -16
  14. package/apps/storefront/src/config/resolver.ts +48 -1
  15. package/apps/storefront/src/layouts/Layout.astro +14 -12
  16. package/apps/storefront/src/lib/admin/README.md +2 -2
  17. package/apps/storefront/src/lib/admin/checkoutStyleInput.ts +5 -5
  18. package/apps/storefront/src/lib/admin/orderClient.ts +20 -20
  19. package/apps/storefront/src/lib/admin/services/mock/{foxyCartSettings.ts → checkoutSettings.ts} +21 -22
  20. package/apps/storefront/src/lib/admin/services/mock/index.ts +3 -2
  21. package/apps/storefront/src/lib/admin/services/mock/settings.ts +1 -1
  22. package/apps/storefront/src/lib/admin/services/mock/subscriptions.ts +398 -0
  23. package/apps/storefront/src/lib/admin/services/orders.ts +21 -21
  24. package/apps/storefront/src/lib/admin/viewmodels/subscriptions.ts +106 -0
  25. package/apps/storefront/src/lib/auth/loginGate.ts +14 -3
  26. package/apps/storefront/src/lib/{foxyCommerce.ts → checkoutCommerce.ts} +20 -18
  27. package/apps/storefront/src/lib/commerce/{foxyCartSettings.ts → checkoutSettings.ts} +65 -66
  28. package/apps/storefront/src/lib/dev/activityIngestToken.ts +9 -0
  29. package/apps/storefront/src/lib/dev/activityStore.ts +12 -0
  30. package/apps/storefront/src/lib/dev/cliSignInCode.ts +23 -4
  31. package/apps/storefront/src/lib/dev/runtimeStore.ts +193 -22
  32. package/apps/storefront/src/lib/edgeCache.ts +7 -6
  33. package/apps/storefront/src/lib/health.ts +53 -0
  34. package/apps/storefront/src/lib/privacy/emailHint.ts +33 -0
  35. package/apps/storefront/src/lib/rawMarketingHtml.ts +1 -0
  36. package/apps/storefront/src/lib/sections.ts +19 -23
  37. package/apps/storefront/src/lib/securityHeaders.ts +1 -1
  38. package/apps/storefront/src/lib/storyblok/content-model.ts +3 -2
  39. package/apps/storefront/src/middleware/index.ts +29 -12
  40. package/apps/storefront/src/pages/[...slug].astro +5 -6
  41. package/apps/storefront/src/pages/admin.astro +10 -3
  42. package/apps/storefront/src/pages/api/admin/orders/[id].ts +4 -4
  43. package/apps/storefront/src/pages/api/admin/orders.ts +5 -5
  44. package/apps/storefront/src/pages/api/auth/magic-exchange.ts +14 -7
  45. package/apps/storefront/src/pages/api/checkout-style.css.ts +5 -5
  46. package/apps/storefront/src/pages/auth/login.astro +4 -1
  47. package/apps/storefront/src/pages/auth/magic.astro +263 -43
  48. package/apps/storefront/src/pages/capabilities.astro +1 -1
  49. package/apps/storefront/src/pages/cockpit.astro +2365 -0
  50. package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +19 -0
  51. package/apps/storefront/src/pages/index.astro +23 -29
  52. package/apps/storefront/src/pages/products/[handle].astro +50 -20
  53. package/package.json +6 -2
  54. package/packages/public-runtime/src/capabilities.ts +97 -9
  55. package/packages/public-runtime/src/{foxy-orders.ts → checkout-orders.ts} +28 -27
  56. package/packages/public-runtime/src/checkout-style-publish.ts +25 -25
  57. package/packages/public-runtime/src/checkout-style.ts +13 -13
  58. package/packages/public-runtime/src/{foxy.ts → checkout.ts} +111 -34
  59. package/packages/public-runtime/src/csp.ts +11 -11
  60. package/packages/public-runtime/src/customization-scripts.ts +28 -0
  61. package/packages/public-runtime/src/extension-contract.ts +73 -0
  62. package/packages/public-runtime/src/index.ts +4 -2
  63. package/packages/public-runtime/src/product.ts +57 -0
  64. package/packages/public-runtime/src/raw-html-policy.ts +35 -0
  65. package/packages/public-runtime/src/tenant.ts +14 -11
  66. package/pnpm-workspace.yaml +1 -0
  67. package/scripts/dev/checkout-watch.mjs +9 -0
  68. package/scripts/dev/file-browser.mjs +5 -1
  69. package/scripts/dev/transient-files.mjs +34 -0
  70. package/scripts/tot-dev.mjs +16 -3
  71. package/apps/storefront/src/components/commerce/FoxyLoader.astro +0 -23
  72. package/apps/storefront/src/pages/api/dev/activity.ts +0 -124
  73. package/apps/storefront/src/pages/api/dev/cli-signin-code.ts +0 -73
  74. package/apps/storefront/src/pages/api/dev/diff.ts +0 -68
  75. package/apps/storefront/src/pages/api/dev/file.ts +0 -57
  76. package/apps/storefront/src/pages/api/dev/locate.ts +0 -125
  77. package/apps/storefront/src/pages/api/dev/publish.ts +0 -128
  78. package/apps/storefront/src/pages/api/dev/tree.ts +0 -70
  79. package/apps/storefront/src/pages/api/dev/write.ts +0 -116
@@ -1,128 +0,0 @@
1
- /**
2
- * POST /api/dev/publish — Phase 3 (cp-publish) of the hosted /dev cockpit
3
- * (workstream tot-lime-cloud-dev-cockpit). Publishes the developer's current
4
- * draft content for one file into the tenant's TEST channel — the moment the
5
- * private draft overlay (cp-write) becomes something OTHER viewers can see
6
- * (still not live).
7
- *
8
- * SCOPE (see decision cp-publish-scope-correction): the card's literal plan
9
- * ("wire to api/preview/promote.ts") doesn't fit this surface — that route
10
- * needs a server-to-server bearer secret + a CDP-driven compliance
11
- * GateReport, and operates on the Gitea-fed preview/live channel, not the
12
- * dev-draft: overlay. Instead this uses the lower-level primitive the
13
- * reconcile pipeline itself is built on — `publishToTest`
14
- * (private-controlplane/customization-versioning.ts) over `KVVersionStore` —
15
- * writing straight into the SAME published-artifact store
16
- * `readPublishedArtifact`/`PublishedContentProvider` read. Content-addressed
17
- * + idempotent by construction (re-publishing identical bytes is a no-op).
18
- *
19
- * Deliberately test-only: promoting test -> live is the real external-publish
20
- * action and isn't reachable from this surface (see decision + the dispatched
21
- * cp-publish-go-live follow-up). "Publish" here closes write -> preview ->
22
- * diff -> publish-to-test, honestly, without faking or bypassing the
23
- * existing compliance gate.
24
- */
25
- import type { APIContext } from "astro";
26
- import { json } from "@/lib/grants/route.js";
27
- import { readKv } from "@/lib/env";
28
- import {
29
- publishToTest,
30
- CustomizationError,
31
- KVVersionStore,
32
- type CustomizationManifest,
33
- } from "@tot/private-controlplane";
34
- import { contentArtifactPath, themeArtifactPath } from "@tot/public-runtime";
35
- import {
36
- resolveCockpitContext,
37
- isCockpitContext,
38
- isWritablePath,
39
- pageSlugForPath,
40
- resolveDraftState,
41
- } from "@/lib/dev/hostedCockpit";
42
-
43
- export const prerender = false;
44
-
45
- // The developer's editable raw-HTML/theme surface, in ARTIFACT-path shape
46
- // (tenants/<id>/...) — what publishToTest's isEditable check and the
47
- // published-artifact store both key on. Not the tenant's real
48
- // declarative-customization manifest (out of scope here): this is
49
- // defense-in-depth on top of the isWritablePath gate already enforced below.
50
- const PUBLISH_MANIFEST: CustomizationManifest = {
51
- version: 1,
52
- classes: {
53
- "platform-owned": [],
54
- "extension-point": [],
55
- "tenant-owned": ["tenants/<id>/content/home.html", "tenants/<id>/content/pages-html/**", "tenants/<id>/theme.json"],
56
- },
57
- immutableCompliance: [],
58
- };
59
-
60
- /**
61
- * Map this route's short content path (content/home.html, theme.json, ...)
62
- * to the artifact path the published-artifact store actually keys on
63
- * (contentArtifactPath/themeArtifactPath — the SAME mapping
64
- * PublishedContentProvider's getHomeHtml/getPageHtml use internally via
65
- * `publishedRaw`). Only ever called after isWritablePath(path) passed, so
66
- * every branch here has a defined result.
67
- */
68
- function artifactPathFor(tenantId: string, path: string): string {
69
- if (path === "theme.json") return themeArtifactPath(tenantId);
70
- if (path === "content/home.html") return contentArtifactPath(tenantId, "home.html");
71
- return contentArtifactPath(tenantId, `pages-html/${pageSlugForPath(path)}.html`);
72
- }
73
-
74
- interface PublishBody {
75
- path?: unknown;
76
- }
77
-
78
- export async function POST(context: APIContext): Promise<Response> {
79
- const ctx = await resolveCockpitContext(context);
80
- if (!isCockpitContext(ctx)) return ctx;
81
-
82
- let body: PublishBody;
83
- try {
84
- body = (await context.request.json()) as PublishBody;
85
- } catch {
86
- return json({ ok: false, status: "bad_content", message: "invalid JSON body" }, 400);
87
- }
88
-
89
- const path = typeof body.path === "string" ? body.path : "";
90
- if (!path) return json({ ok: false, status: "no_path", message: "path is required" }, 400);
91
- if (!isWritablePath(path)) {
92
- return json({ ok: false, status: "not_writable", message: `not in the write allowlist: ${path}` }, 403);
93
- }
94
-
95
- const { draftContent } = await resolveDraftState(context, ctx, path);
96
- if (draftContent === null) {
97
- return json(
98
- { ok: false, status: "no_draft", message: "You have no unpublished edit for this file — nothing to publish." },
99
- 409,
100
- );
101
- }
102
-
103
- const kv = await readKv("TENANT_CACHE");
104
- if (!kv) {
105
- // No KV bound (e.g. bare local build) — accept but report unpersisted,
106
- // matching cp-write's degraded-mode contract rather than a broken call.
107
- return json({ ok: true, status: "ok", path, applied: false });
108
- }
109
-
110
- const store = new KVVersionStore(kv);
111
- const artifactPath = artifactPathFor(ctx.tenant.tenant_id, path);
112
- try {
113
- const result = await publishToTest(store, PUBLISH_MANIFEST, ctx.tenant.tenant_id, artifactPath, draftContent);
114
- return json({
115
- ok: true,
116
- status: "ok",
117
- path,
118
- applied: true,
119
- versionId: result.artifact.versionId,
120
- channel: "test",
121
- });
122
- } catch (err) {
123
- if (err instanceof CustomizationError) {
124
- return json({ ok: false, status: err.code, message: err.message }, 409);
125
- }
126
- throw err;
127
- }
128
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * GET /api/dev/tree — Phase 1 (cp-tree) of the hosted /dev cockpit
3
- * (workstream tot-lime-cloud-dev-cockpit, decision
4
- * architecture-environment-aware-cockpit). Lists the signed-in developer's OWN
5
- * tenant content so the hosted cockpit's Files tab renders the same tree shape
6
- * the local loop's `/__tot/tree` does (see scripts/dev/file-browser.mjs
7
- * buildTree/handleTree — this route mirrors its {ok,tree} response contract,
8
- * consumed unchanged by dev.astro's filesScript via the cp-env apiBase resolver).
9
- *
10
- * Unlike the local tree (an arbitrary on-disk directory walk), the hosted
11
- * Worker has no filesystem — the tree here is SYNTHESIZED from exactly the
12
- * raw-HTML surface the content provider chain understands + the write
13
- * allowlist (scripts/dev/safe-path.mjs WRITE_ALLOWLIST): content/home.html,
14
- * content/pages-html/<slug>.html, and theme.json. A tenant on structured
15
- * (Storyblok/JSON) content simply has an emptier tree — nothing to browse/edit
16
- * on this raw-HTML surface yet.
17
- */
18
- import type { APIContext } from "astro";
19
- import { json } from "@/lib/grants/route.js";
20
- import { readPublishedArtifact, themeArtifactPath } from "@tot/public-runtime";
21
- import { resolveCockpitContext, isCockpitContext } from "@/lib/dev/hostedCockpit";
22
-
23
- export const prerender = false;
24
-
25
- type TreeNode =
26
- | { name: string; path: string; type: "file"; language: string | null }
27
- | { name: string; path: string; type: "dir"; children: TreeNode[] };
28
-
29
- export async function GET(context: APIContext): Promise<Response> {
30
- const ctx = await resolveCockpitContext(context);
31
- if (!isCockpitContext(ctx)) return ctx;
32
- const { tenant, provider, kvGet } = ctx;
33
-
34
- const [homeHtml, pageSlugs, themeRaw] = await Promise.all([
35
- provider.getHomeHtml(),
36
- provider.listPageHtmlSlugs(),
37
- readPublishedArtifact(kvGet, tenant.tenant_id, "live", themeArtifactPath(tenant.tenant_id)),
38
- ]);
39
-
40
- const contentChildren: TreeNode[] = [];
41
- if (homeHtml !== null) {
42
- contentChildren.push({ name: "home.html", path: "content/home.html", type: "file", language: "html" });
43
- }
44
- if (pageSlugs.length > 0) {
45
- contentChildren.push({
46
- name: "pages-html",
47
- path: "content/pages-html",
48
- type: "dir",
49
- children: pageSlugs
50
- .slice()
51
- .sort((a, b) => a.localeCompare(b))
52
- .map((slug) => ({
53
- name: `${slug}.html`,
54
- path: `content/pages-html/${slug}.html`,
55
- type: "file" as const,
56
- language: "html",
57
- })),
58
- });
59
- }
60
-
61
- const tree: TreeNode[] = [];
62
- if (contentChildren.length > 0) {
63
- tree.push({ name: "content", path: "content", type: "dir", children: contentChildren });
64
- }
65
- if (themeRaw !== null) {
66
- tree.push({ name: "theme.json", path: "theme.json", type: "file", language: "json" });
67
- }
68
-
69
- return json({ ok: true, tenantId: tenant.tenant_id, tree });
70
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * POST /api/dev/write — Phase 2 (cp-write) of the hosted /dev cockpit
3
- * (workstream tot-lime-cloud-dev-cockpit, decision
4
- * architecture-environment-aware-cockpit). The heart of cloud editing: a
5
- * direct text edit (not an AI prompt) written into the developer's PRIVATE
6
- * draft overlay, reusing /api/content-edit/ai-edit.ts's exact materialization
7
- * mechanism (dev-draft: KV keys + signed tot_dev_draft cookie the middleware
8
- * overlay reads) — so the /dev live preview reflects the edit and no other
9
- * viewer/channel is touched. Same request/response contract as the local
10
- * loop's `/__tot/write` (scripts/dev/file-writer.mjs handleWrite), including
11
- * the C6 baseVersion 409 conflict guard — but "version" here is
12
- * contentVersion() (a content hash), not a filesystem mtime, since the Worker
13
- * has none; cp-file hands back the same hash so the two agree.
14
- *
15
- * Deviates from file-writer.mjs in one place: a theme.json write must parse as
16
- * JSON before it lands (mirrors ai-edit.ts's guard) — this draft overlay feeds
17
- * the SAME live preview theme.json ai-edit writes, and a malformed draft would
18
- * degrade that preview for the developer, unlike a plain on-disk write.
19
- *
20
- * When KV or the signing secret is unavailable (e.g. no CUSTOMIZATION_PREVIEW_SECRET
21
- * configured for this env), the edit is accepted but not persisted —
22
- * `applied:false`, exactly ai-edit's degraded-mode contract — never a broken call.
23
- */
24
- import type { APIContext } from "astro";
25
- import { json } from "@/lib/grants/route.js";
26
- import { DEV_DRAFT_TTL_SECONDS, devDraftKey } from "@/lib/dev/devDraft";
27
- import {
28
- resolveCockpitContext,
29
- isCockpitContext,
30
- isWritablePath,
31
- readPublishedPath,
32
- resolveDraftState,
33
- contentVersion,
34
- setDraftCookie,
35
- } from "@/lib/dev/hostedCockpit";
36
-
37
- export const prerender = false;
38
-
39
- const MAX_WRITE_BYTES = 2 * 1024 * 1024;
40
-
41
- interface WriteBody {
42
- path?: unknown;
43
- content?: unknown;
44
- baseVersion?: unknown;
45
- }
46
-
47
- export async function POST(context: APIContext): Promise<Response> {
48
- const ctx = await resolveCockpitContext(context);
49
- if (!isCockpitContext(ctx)) return ctx;
50
-
51
- let body: WriteBody;
52
- try {
53
- body = (await context.request.json()) as WriteBody;
54
- } catch {
55
- return json({ ok: false, status: "bad_content", message: "invalid JSON body" }, 400);
56
- }
57
-
58
- const path = typeof body.path === "string" ? body.path : "";
59
- if (!path) return json({ ok: false, status: "no_path", message: "path is required" }, 400);
60
- if (!isWritablePath(path)) {
61
- return json({ ok: false, status: "not_writable", message: `not in the write allowlist: ${path}` }, 403);
62
- }
63
-
64
- const content = body.content;
65
- if (typeof content !== "string") {
66
- return json({ ok: false, status: "bad_content", message: "content must be a string" }, 400);
67
- }
68
- if (new TextEncoder().encode(content).length > MAX_WRITE_BYTES) {
69
- return json({ ok: false, status: "too_large", message: "content exceeds the write size limit" }, 413);
70
- }
71
- if (path === "theme.json") {
72
- try {
73
- JSON.parse(content);
74
- } catch {
75
- return json({ ok: false, status: "bad_content", message: "theme.json must be valid JSON" }, 400);
76
- }
77
- }
78
-
79
- // Stale-write guard (C6): compare the caller's baseVersion against the
80
- // developer's CURRENT value for this path (their draft if one exists, else
81
- // published) — omit baseVersion to force-save, exactly the local contract.
82
- const { draftId, draftContent } = await resolveDraftState(context, ctx, path);
83
- const currentContent = draftContent !== null ? draftContent : await readPublishedPath(ctx, path);
84
- const currentVersion = contentVersion(currentContent);
85
- const baseVersion = typeof body.baseVersion === "number" ? body.baseVersion : undefined;
86
- if (baseVersion !== undefined && currentVersion !== baseVersion) {
87
- return json(
88
- {
89
- ok: false,
90
- status: "conflict",
91
- current: currentVersion,
92
- message:
93
- currentVersion === null
94
- ? `${path} was deleted since you opened it`
95
- : `${path} changed since you opened it`,
96
- },
97
- 409,
98
- );
99
- }
100
-
101
- const bytes = new TextEncoder().encode(content).length;
102
- const mtimeMs = contentVersion(content);
103
-
104
- if (!ctx.kvPut || !ctx.draftSecret) {
105
- // Nothing to persist against (e.g. bare local build with no KV/secret) —
106
- // report success without pretending the draft landed, like ai-edit.
107
- return json({ ok: true, status: "ok", path, bytes, mtimeMs, applied: false });
108
- }
109
-
110
- await ctx.kvPut(devDraftKey(ctx.tenant.tenant_id, draftId, path), content, {
111
- expirationTtl: DEV_DRAFT_TTL_SECONDS,
112
- });
113
- await setDraftCookie(context, ctx, draftId);
114
-
115
- return json({ ok: true, status: "ok", path, bytes, mtimeMs, applied: true });
116
- }