@tokenoftrust/storefront-runner 2.0.2 → 2.0.4
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.
- package/apps/storefront/.astro/content.d.ts +159 -0
- package/apps/storefront/.astro/integrations/_astrojs_cloudflare/cloudflare.d.ts +1 -0
- package/apps/storefront/.astro/types.d.ts +3 -0
- package/apps/storefront/node_modules/.bin/acorn +43 -0
- package/apps/storefront/node_modules/.bin/astro +43 -0
- package/apps/storefront/node_modules/.bin/astro-check +43 -0
- package/apps/storefront/node_modules/.bin/browserslist +43 -0
- package/apps/storefront/node_modules/.bin/cf-wrangler +43 -0
- package/apps/storefront/node_modules/.bin/depcruise +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-baseline +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-fmt +43 -0
- package/apps/storefront/node_modules/.bin/depcruise-wrap-stream-in-html +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruise +43 -0
- package/apps/storefront/node_modules/.bin/dependency-cruiser +43 -0
- package/apps/storefront/node_modules/.bin/drizzle-kit +43 -0
- package/apps/storefront/node_modules/.bin/esbuild +43 -0
- package/apps/storefront/node_modules/.bin/jiti +43 -0
- package/apps/storefront/node_modules/.bin/playwright +43 -0
- package/apps/storefront/node_modules/.bin/playwright-core +43 -0
- package/apps/storefront/node_modules/.bin/prettier +43 -0
- package/apps/storefront/node_modules/.bin/rollup +43 -0
- package/apps/storefront/node_modules/.bin/tsc +43 -0
- package/apps/storefront/node_modules/.bin/tsserver +43 -0
- package/apps/storefront/node_modules/.bin/tsx +43 -0
- package/apps/storefront/node_modules/.bin/vite +43 -0
- package/apps/storefront/node_modules/.bin/vitest +43 -0
- package/apps/storefront/node_modules/.bin/workerd +43 -0
- package/apps/storefront/node_modules/.bin/wrangler +43 -0
- package/apps/storefront/node_modules/.bin/wrangler2 +43 -0
- package/apps/storefront/node_modules/.bin/yaml +43 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js +58 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_client__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js +741 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_n_@preact_signals.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js +2549 -0
- package/apps/storefront/node_modules/.vite/deps/@astrojs_preact_server__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/_metadata.json +150 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js +434 -0
- package/apps/storefront/node_modules/.vite/deps/astro-BIHuWFxm.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/apps/storefront/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/apps/storefront/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js +1535 -0
- package/apps/storefront/node_modules/.vite/deps/audit-DKDpcdEI.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js +48 -0
- package/apps/storefront/node_modules/.vite/deps/highlight-B9UoX1wa.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js +42 -0
- package/apps/storefront/node_modules/.vite/deps/icons-BWi2wI9n.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/package.json +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js +310 -0
- package/apps/storefront/node_modules/.vite/deps/preact.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js +420 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_compat_jsx-runtime.js +4 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js +236 -0
- package/apps/storefront/node_modules/.vite/deps/preact_debug.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js +12 -0
- package/apps/storefront/node_modules/.vite/deps/preact_devtools.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js +192 -0
- package/apps/storefront/node_modules/.vite/deps/preact_hooks.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-dev-runtime.js +3 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js +92 -0
- package/apps/storefront/node_modules/.vite/deps/preact_jsx-runtime.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js +59 -0
- package/apps/storefront/node_modules/.vite/deps/preact_test-utils.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/rolldown-runtime-BPOCksWG.js +24 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js +189 -0
- package/apps/storefront/node_modules/.vite/deps/settings-BVDV0Roj.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js +13 -0
- package/apps/storefront/node_modules/.vite/deps/static-html-DveYqwMo.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js +458 -0
- package/apps/storefront/node_modules/.vite/deps/toolbar-B3VIHCXg.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js +1016 -0
- package/apps/storefront/node_modules/.vite/deps/ui-library-BK6ricUd.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js +32 -0
- package/apps/storefront/node_modules/.vite/deps/window-BvxDenKN.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js +113 -0
- package/apps/storefront/node_modules/.vite/deps/xray-YFiRUU0Z.js.map +1 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/_metadata.json +8 -0
- package/apps/storefront/node_modules/.vite/deps_ssr/package.json +3 -0
- package/apps/storefront/src/layouts/Layout.astro +1 -1
- package/apps/storefront/src/lib/apps/registryAccess.ts +45 -0
- package/apps/storefront/src/lib/auth/adminEntry.ts +4 -4
- package/apps/storefront/src/lib/shared/capability.ts +68 -0
- package/apps/storefront/src/lib/shared/developerInvites.ts +1250 -0
- package/apps/storefront/src/lib/shared/invitePreflight.ts +123 -0
- package/apps/storefront/src/lib/webhooks/deliveryEngine.ts +38 -4
- package/apps/storefront/src/lib/webhooks/emit.ts +1 -1
- package/apps/storefront/src/lib/webhooks/getDispatcher.ts +1 -1
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +1 -1
- package/apps/storefront/src/pages/index.astro +1 -1
- package/apps/storefront/src/pages/products/[handle].astro +1 -1
- package/package.json +1 -1
- package/packages/public-runtime/node_modules/.bin/tsc +43 -0
- package/packages/public-runtime/node_modules/.bin/tsserver +43 -0
- package/packages/public-runtime/node_modules/.bin/vitest +43 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/lib/tenant-dirs.mjs +80 -0
- package/scripts/tenant/lint-embeds.mjs +180 -0
- package/scripts/tenant/materialize-tenant-content.mjs +533 -0
- package/scripts/tenant/validate.mjs +837 -0
- package/apps/storefront/scripts/build-v84-clean-clone-standalone.mjs +0 -68
- package/apps/storefront/scripts/deny-v84-clean-clone-standalone-capabilities.mjs +0 -111
- package/apps/storefront/scripts/deny-v84-dogfood-capabilities.mjs +0 -2
- package/apps/storefront/scripts/deny-v84-lifecycle-capabilities.mjs +0 -247
- package/apps/storefront/scripts/dogfood-v84-nonportable-routes.ts +0 -73
- package/apps/storefront/scripts/fixed-v84-native-install-producer.ts +0 -79
- package/apps/storefront/scripts/generated/v84-clean-clone-replay.bundle.mjs +0 -10982
- package/apps/storefront/scripts/import-v84-through-ws2.ts +0 -38
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling-dependency.ts +0 -2
- package/apps/storefront/scripts/initialize-v84-lifecycle-tooling.ts +0 -4
- package/apps/storefront/scripts/install-v84-native-through-ws1.ts +0 -185
- package/apps/storefront/scripts/prove-clone-isolation.ts +0 -506
- package/apps/storefront/scripts/replay-v84-clean-clone-pass.ts +0 -148
- package/apps/storefront/scripts/replay-v84-clean-clone-standalone.ts +0 -35
- package/apps/storefront/scripts/replay-v84-clean-clone.ts +0 -214
- package/apps/storefront/scripts/run-fixed-v84-native-install-producer.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-isolation-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-clone-lifecycle-proof.mjs +0 -18
- package/apps/storefront/scripts/run-v84-import-through-ws2-proof.mjs +0 -17
- package/apps/storefront/scripts/run-v84-nonportable-route-dogfood.mjs +0 -15
- package/apps/storefront/scripts/sanitize-v84-proof-environment.mjs +0 -21
- package/apps/storefront/scripts/v84CleanCloneGraphPin.ts +0 -42
- package/apps/storefront/scripts/v84CleanCloneSourceGraph.ts +0 -145
- package/apps/storefront/scripts/validate-v84-clone-lifecycle.ts +0 -18
- package/apps/storefront/src/components/admin/AdminAIWorkflowsTab.astro +0 -531
- package/apps/storefront/src/components/admin/AdminBlogDetailRail.astro +0 -878
- package/apps/storefront/src/components/admin/AdminBlogTab.astro +0 -1435
- package/apps/storefront/src/components/admin/AdminCheckoutSettingsPanel.astro +0 -226
- package/apps/storefront/src/components/admin/AdminCustomersTab.astro +0 -593
- package/apps/storefront/src/components/admin/AdminFulfillmentTab.astro +0 -465
- package/apps/storefront/src/components/admin/AdminIntegrationsTab.astro +0 -105
- package/apps/storefront/src/components/admin/AdminProductsTab.astro +0 -901
- package/apps/storefront/src/components/admin/AdminPublishTab.astro +0 -2983
- package/apps/storefront/src/components/admin/AdminReportingTab.astro +0 -435
- package/apps/storefront/src/components/admin/AdminSettingsTab.astro +0 -782
- package/apps/storefront/src/components/admin/AdminSubscriptionsTab.astro +0 -611
- package/apps/storefront/src/components/admin/orders/OrdersWorkspace.astro +0 -1223
- package/apps/storefront/src/lib/apps/adminService.ts +0 -116
- package/apps/storefront/src/lib/apps/adminSession.ts +0 -214
- package/apps/storefront/src/lib/apps/apiAuth.ts +0 -91
- package/apps/storefront/src/lib/apps/apiRoute.ts +0 -35
- package/apps/storefront/src/lib/apps/catalogMapper.ts +0 -39
- package/apps/storefront/src/lib/apps/consentConfigStore.ts +0 -228
- package/apps/storefront/src/lib/apps/gtmMigrationInstall.ts +0 -447
- package/apps/storefront/src/lib/apps/gtmMigrationReport.ts +0 -367
- package/apps/storefront/src/lib/apps/gtmMigrationTab.ts +0 -315
- package/apps/storefront/src/lib/apps/healthAggregate.ts +0 -66
- package/apps/storefront/src/lib/apps/integrationLifecycle.ts +0 -614
- package/apps/storefront/src/lib/apps/integrationLifecycleController.ts +0 -125
- package/apps/storefront/src/lib/apps/integrationReadModel.ts +0 -563
- package/apps/storefront/src/lib/apps/integrationsTab.ts +0 -547
- package/apps/storefront/src/lib/apps/integrationsView.ts +0 -106
- package/apps/storefront/src/lib/apps/managedAnalyticsService.ts +0 -599
- package/apps/storefront/src/lib/apps/tenantIntegrationMaterialization.ts +0 -170
- package/apps/storefront/src/lib/apps/tokenIssuer.ts +0 -121
- package/apps/storefront/src/lib/apps/tokenVerifier.ts +0 -148
- package/apps/storefront/src/lib/apps/v84CleanCloneReplayContext.ts +0 -16
- package/apps/storefront/src/lib/apps/v84CloneLifecycleCacheEvidence.ts +0 -105
- package/apps/storefront/src/lib/apps/v84CloneLifecycleEvidence.ts +0 -177
- package/apps/storefront/src/lib/apps/v84CloneLifecycleMaterialization.ts +0 -251
- package/apps/storefront/src/lib/apps/v84CloneLifecycleSemantics.ts +0 -241
- package/apps/storefront/src/lib/apps/v84CloneMigrationEvidence.ts +0 -211
- package/apps/storefront/src/lib/apps/v84CloneNativeInstallEvidence.ts +0 -409
- package/apps/storefront/src/lib/apps/v84NonportableCodeCoaching.ts +0 -368
- package/apps/storefront/src/lib/apps/v84NonportableRouteContract.ts +0 -188
- package/apps/storefront/src/lib/apps/v84NonportableRouteDogfood.ts +0 -291
- package/apps/storefront/src/lib/apps/ws1ManagedAnalyticsClient.ts +0 -95
- package/apps/storefront/src/lib/content-edit/applyDiff.ts +0 -91
- package/apps/storefront/src/lib/content-edit/client.ts +0 -286
- package/apps/storefront/src/lib/content-edit/index.ts +0 -14
- package/apps/storefront/src/lib/content-edit/route.ts +0 -25
- package/apps/storefront/src/lib/content-edit/types.ts +0 -19
- package/apps/storefront/src/lib/dev/cockpitStore.ts +0 -57
- package/apps/storefront/src/lib/publish/adminPublishTab.ts +0 -4625
- package/apps/storefront/src/lib/publish/apex-readiness.ts +0 -360
- package/apps/storefront/src/lib/publish/buildStream.ts +0 -269
- package/apps/storefront/src/lib/publish/collapsePersistence.ts +0 -72
- package/apps/storefront/src/lib/publish/dispatchHealth.ts +0 -267
- package/apps/storefront/src/lib/publish/domainState.ts +0 -360
- package/apps/storefront/src/lib/publish/lifecycleState.ts +0 -369
- package/apps/storefront/src/lib/publish/liveReleaseSummary.ts +0 -29
- package/apps/storefront/src/lib/publish/mergeDoctorOnFailure.ts +0 -78
- package/apps/storefront/src/lib/publish/mergeHealthBanner.ts +0 -62
- package/apps/storefront/src/lib/publish/mergeRowFindings.ts +0 -124
- package/apps/storefront/src/lib/publish/mergeSupportEscalation.ts +0 -75
- package/apps/storefront/src/lib/publish/panelPolling.ts +0 -78
- package/apps/storefront/src/lib/publish/publish-guard.ts +0 -88
- package/apps/storefront/src/lib/publish/publishFormat.ts +0 -165
- package/apps/storefront/src/lib/publish/publishHero.ts +0 -177
- package/apps/storefront/src/lib/publish/reviewRelease/changeState.ts +0 -66
- package/apps/storefront/src/lib/publish/reviewRelease/copy.ts +0 -141
- package/apps/storefront/src/lib/publish/reviewRelease/index.ts +0 -41
- package/apps/storefront/src/lib/publish/reviewRelease/permissions.ts +0 -47
- package/apps/storefront/src/lib/publish/reviewRelease/releaseState.ts +0 -42
- package/apps/storefront/src/lib/publish/reviewRelease/types.ts +0 -59
- package/apps/storefront/src/lib/publish/reviewRelease/viewModel.ts +0 -440
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/actions.ts +0 -161
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/controller.ts +0 -325
- package/apps/storefront/src/lib/publish/reviewReleaseWorkspace/render.ts +0 -241
- package/apps/storefront/src/lib/publish/shipWorkspace.ts +0 -439
- package/apps/storefront/src/lib/publish/sseRoute.ts +0 -119
- package/apps/storefront/src/lib/publish/stageBundleUpload.ts +0 -215
- package/apps/storefront/src/lib/publish/streamLogConsole.ts +0 -142
- package/apps/storefront/src/lib/publish/versionNumber.ts +0 -52
- package/apps/storefront/src/pages/admin/ops-timeline.astro +0 -420
- package/apps/storefront/src/pages/admin/review-release.astro +0 -265
- package/apps/storefront/src/pages/admin/select.astro +0 -96
- package/apps/storefront/src/pages/admin.astro +0 -570
- package/apps/storefront/src/pages/api/apps/admin/credentials/rotate.ts +0 -86
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration-install.ts +0 -174
- package/apps/storefront/src/pages/api/apps/admin/gtm-migration.ts +0 -96
- package/apps/storefront/src/pages/api/apps/admin/health.ts +0 -44
- package/apps/storefront/src/pages/api/apps/admin/install.ts +0 -125
- package/apps/storefront/src/pages/api/apps/admin/integration-lifecycle.ts +0 -118
- package/apps/storefront/src/pages/api/apps/admin/integrations.ts +0 -89
- package/apps/storefront/src/pages/api/apps/admin/list.ts +0 -26
- package/apps/storefront/src/pages/api/apps/admin/platform-adapters.ts +0 -85
- package/apps/storefront/src/pages/api/apps/admin/resume.ts +0 -79
- package/apps/storefront/src/pages/api/apps/admin/suspend.ts +0 -80
- package/apps/storefront/src/pages/api/apps/admin/uninstall.ts +0 -98
- package/apps/storefront/src/pages/api/apps/admin/update.ts +0 -126
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries/[deliveryId]/replay.ts +0 -70
- package/apps/storefront/src/pages/api/apps/admin/webhooks/deliveries.ts +0 -51
- package/apps/storefront/src/pages/api/apps/internal/order-forward.ts +0 -255
- package/apps/storefront/src/pages/api/apps/oauth/token.ts +0 -87
- package/apps/storefront/src/pages/api/apps/v1/attribution.ts +0 -162
- package/apps/storefront/src/pages/api/apps/v1/catalog/products/[handle].ts +0 -39
- package/apps/storefront/src/pages/api/apps/v1/catalog/products.ts +0 -49
- package/apps/storefront/src/pages/api/apps/v1/health.ts +0 -32
- package/apps/storefront/src/pages/api/apps/v1/inventory.ts +0 -59
- package/apps/storefront/src/pages/api/apps/v1/orders/[id].ts +0 -48
- package/apps/storefront/src/pages/api/apps/v1/orders.ts +0 -73
- package/apps/storefront/src/pages/api/apps/v1/reports.ts +0 -21
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries/[deliveryId]/replay.ts +0 -74
- package/apps/storefront/src/pages/api/apps/v1/webhooks/deliveries.ts +0 -47
- package/apps/storefront/src/pages/api/cache-purge.ts +0 -55
- package/apps/storefront/src/pages/api/compliance/tamper.ts +0 -88
- package/apps/storefront/src/pages/api/content-edit/ai-edit.ts +0 -164
- package/apps/storefront/src/pages/api/dashboard/enter-vendor.ts +0 -154
- package/apps/storefront/src/pages/api/dashboard/vendor-search.ts +0 -120
- package/apps/storefront/src/pages/api/integrations/v1/[operation].ts +0 -108
- package/apps/storefront/src/pages/api/publish/stage-bundle.ts +0 -70
- package/apps/storefront/src/pages/api/test/dev-session.ts +0 -133
- package/apps/storefront/src/pages/cockpit.astro +0 -1548
- package/apps/storefront/src/pages/dashboard/[appDomain]/apps/index.astro +0 -119
- package/apps/storefront/src/pages/dashboard/[appDomain]/index.astro +0 -105
- package/apps/storefront/src/pages/dashboard/[appDomain]/team.astro +0 -177
- package/apps/storefront/src/pages/dashboard/index.astro +0 -160
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invite preflight — block a developer invite when the target store's repo isn't
|
|
3
|
+
* provisioned on THIS environment, so a broken connection surfaces to the INVITER
|
|
4
|
+
* at invite time instead of dead-ending later in the invitee's terminal on
|
|
5
|
+
* "Repo <owner>-main is not provisioned".
|
|
6
|
+
*
|
|
7
|
+
* The check is the MCP's credential-free resource-registry read — the SAME read
|
|
8
|
+
* `tenant_checkout` gates on — reached the SAME way the app-binding writer +
|
|
9
|
+
* cli-signin-code seam reach the MCP:
|
|
10
|
+
* GET {origin(MCP_APP_BINDING_URL)}/internal/preview-status?tenant=<appDomain>&app=storefront&tag=main
|
|
11
|
+
* header x-mcp-service-secret: <MCP_SERVICE_SHARED_SECRET>
|
|
12
|
+
* → 200 { owner, app, repo, provisioned, previewReleased } | 400 | 502
|
|
13
|
+
* Contract source of truth: tot-mcp src/server/preview-status-routes.ts. We gate on
|
|
14
|
+
* `provisioned` — the exact precondition `tenant_checkout` needs. We do NOT gate on
|
|
15
|
+
* `previewReleased`: it is (provisioned && preview-approved), which is FALSE for every
|
|
16
|
+
* store that hasn't shipped a preview yet — gating on it would block invites for
|
|
17
|
+
* provisioned-but-unreleased stores whose checkout would actually succeed.
|
|
18
|
+
*
|
|
19
|
+
* FAIL-OPEN: we BLOCK only on a DEFINITIVE not-ready answer (a clean 200 with
|
|
20
|
+
* previewReleased:false). An unconfigured seam (no URL/secret), a non-200, a
|
|
21
|
+
* transport/parse fault, or a timeout is INCONCLUSIVE → allow (log a warning),
|
|
22
|
+
* mirroring recordDeveloperAppBinding's "skipped when not-configured" stance — we
|
|
23
|
+
* never break invites just because the MCP is briefly unreachable.
|
|
24
|
+
*/
|
|
25
|
+
import { readEnv } from "@/lib/env";
|
|
26
|
+
|
|
27
|
+
/** How long we'll wait on the preview-status read before failing open. */
|
|
28
|
+
const PREFLIGHT_TIMEOUT_MS = 4000;
|
|
29
|
+
|
|
30
|
+
/** GET transport seam (injectable for tests) — mirrors HttpTransport but read-only. */
|
|
31
|
+
export interface PreflightTransport {
|
|
32
|
+
get(
|
|
33
|
+
url: string,
|
|
34
|
+
headers: Record<string, string>,
|
|
35
|
+
): Promise<{ status: number; json(): Promise<unknown> }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The real transport: a timeout-bounded GET. */
|
|
39
|
+
export class FetchPreflightTransport implements PreflightTransport {
|
|
40
|
+
async get(url: string, headers: Record<string, string>) {
|
|
41
|
+
const ctrl = new AbortController();
|
|
42
|
+
const timer = setTimeout(() => ctrl.abort(), PREFLIGHT_TIMEOUT_MS);
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(url, { method: "GET", headers, signal: ctrl.signal });
|
|
45
|
+
return { status: res.status, json: () => res.json() };
|
|
46
|
+
} finally {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** BLOCK carries the inviter-facing message; ALLOW carries a reason (for logging). */
|
|
53
|
+
export type PreflightDecision =
|
|
54
|
+
| { block: true; reason: "not-provisioned"; message: string }
|
|
55
|
+
| { block: false; reason: string };
|
|
56
|
+
|
|
57
|
+
/** The inviter-facing block message, naming the store. */
|
|
58
|
+
function blockMessage(tenant: string): string {
|
|
59
|
+
return (
|
|
60
|
+
`${tenant} isn't set up on this environment yet — its store repo isn't ` +
|
|
61
|
+
`provisioned. Finish provisioning that store before inviting a developer to it.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Preflight `tenant` (the target appDomain). Returns a BLOCK only on a definitive
|
|
67
|
+
* not-provisioned answer; every inconclusive condition allows (fail-open). Never
|
|
68
|
+
* throws.
|
|
69
|
+
*/
|
|
70
|
+
export async function checkStorePreviewReady(
|
|
71
|
+
tenant: string,
|
|
72
|
+
deps: { transport?: PreflightTransport } = {},
|
|
73
|
+
): Promise<PreflightDecision> {
|
|
74
|
+
const bindingUrl = await readEnv("MCP_APP_BINDING_URL");
|
|
75
|
+
const secret = await readEnv("MCP_SERVICE_SHARED_SECRET");
|
|
76
|
+
if (!bindingUrl || !secret) {
|
|
77
|
+
// Seam not configured (the MCP route isn't even mounted without the secret) —
|
|
78
|
+
// inconclusive, so allow rather than break every invite.
|
|
79
|
+
return { block: false, reason: "not-configured" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let endpoint: string;
|
|
83
|
+
try {
|
|
84
|
+
const origin = new URL(bindingUrl).origin;
|
|
85
|
+
const q = new URLSearchParams({ tenant: tenant.toLowerCase(), app: "storefront", tag: "main" });
|
|
86
|
+
// Canonical, namespace-neutral path (the read is generic, not dev-role-scoped).
|
|
87
|
+
endpoint = `${origin}/internal/preview-status?${q.toString()}`;
|
|
88
|
+
} catch {
|
|
89
|
+
return { block: false, reason: "bad-mcp-url" };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const transport = deps.transport ?? new FetchPreflightTransport();
|
|
93
|
+
let res: { status: number; json(): Promise<unknown> };
|
|
94
|
+
try {
|
|
95
|
+
res = await transport.get(endpoint, { "x-mcp-service-secret": secret });
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
return {
|
|
98
|
+
block: false,
|
|
99
|
+
reason: "preflight request failed: " + (cause instanceof Error ? cause.message : String(cause)),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Only a clean 200 is a conclusive answer; anything else is inconclusive → allow.
|
|
104
|
+
if (res.status < 200 || res.status >= 300) {
|
|
105
|
+
return { block: false, reason: `preflight returned status ${res.status}` };
|
|
106
|
+
}
|
|
107
|
+
let payload: { provisioned?: unknown; previewReleased?: unknown };
|
|
108
|
+
try {
|
|
109
|
+
payload = (await res.json()) as typeof payload;
|
|
110
|
+
} catch {
|
|
111
|
+
return { block: false, reason: "unparseable preflight response" };
|
|
112
|
+
}
|
|
113
|
+
// Gate on `provisioned` — the ONE precondition the invitee's first `tot start`
|
|
114
|
+
// checkout needs (a missing repo is the austin→sample-store failure). Treat only
|
|
115
|
+
// an explicit `false` as a definitive block; a missing/non-boolean field is
|
|
116
|
+
// inconclusive → allow. NOT `previewReleased` (see the file header): that now means
|
|
117
|
+
// provisioned && preview-approved, which is false for every not-yet-shipped store and
|
|
118
|
+
// would block invites whose checkout actually succeeds.
|
|
119
|
+
if (payload.provisioned === false) {
|
|
120
|
+
return { block: true, reason: "not-provisioned", message: blockMessage(tenant) };
|
|
121
|
+
}
|
|
122
|
+
return { block: false, reason: "provisioned" };
|
|
123
|
+
}
|
|
@@ -24,8 +24,6 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { WebhookDeliveryStore, type WebhookDelivery, type WebhookEvent, type WebhookTopic } from "./deliveryStore.js";
|
|
26
26
|
import type { AppInstall } from "../apps/registryService.js";
|
|
27
|
-
import { loadWebhookSigningConfig, type WebhookSigningConfig } from "./webhookSigningKey.js";
|
|
28
|
-
import { signWebhookRequest, type SignWebhookRequestResult } from "./signing.js";
|
|
29
27
|
import { buildCloudEventEnvelope } from "./events.js";
|
|
30
28
|
import { computeBackoffDelaySeconds, MAX_DELIVERY_ATTEMPTS, type AttemptOutcome } from "./dispatcher.js";
|
|
31
29
|
import { validateWebhookEndpoint } from "./endpointGuard.js";
|
|
@@ -33,6 +31,28 @@ import { validateWebhookEndpoint } from "./endpointGuard.js";
|
|
|
33
31
|
/** Bounded per-attempt timeout — a hung receiver must not hold a dispatcher slot open indefinitely. */
|
|
34
32
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
35
33
|
|
|
34
|
+
// Both signing.js and webhookSigningKey.js ultimately re-export from
|
|
35
|
+
// @tokenoftrust/private-apps-devkit, a workspace-only package never vendored
|
|
36
|
+
// into the public storefront-runner — a static import of either (type-only
|
|
37
|
+
// included: `tsc` resolves a module for `import type` too) would be a
|
|
38
|
+
// dangling import in that build. The VALUES are loaded via dynamic import at
|
|
39
|
+
// their call sites below (see attemptDelivery/deliverSigned); these two
|
|
40
|
+
// shapes are duplicated here, type-only, so this module's own types don't
|
|
41
|
+
// need those files to exist in the runner. Keep in sync with
|
|
42
|
+
// webhookSigningKey.ts's WebhookSigningConfig and
|
|
43
|
+
// private-apps-devkit/src/signing.ts's SignWebhookRequestResult.
|
|
44
|
+
interface WebhookSigningConfig {
|
|
45
|
+
key: CryptoKey;
|
|
46
|
+
kid: string;
|
|
47
|
+
}
|
|
48
|
+
interface SignWebhookRequestResult {
|
|
49
|
+
headers: {
|
|
50
|
+
"content-digest": string;
|
|
51
|
+
"signature-input": string;
|
|
52
|
+
signature: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
/** The minimal registry seam the engine needs: resolving the target install by id. Satisfied by `AppRegistryService`. */
|
|
37
57
|
export interface InstallResolver {
|
|
38
58
|
get(installId: string): Promise<AppInstall | null>;
|
|
@@ -83,7 +103,17 @@ export async function attemptDelivery(deliveryId: string, deps: AttemptDeliveryD
|
|
|
83
103
|
const now = deps.now ?? (() => new Date());
|
|
84
104
|
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
85
105
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
86
|
-
const loadSigningConfig =
|
|
106
|
+
const loadSigningConfig =
|
|
107
|
+
deps.loadSigningConfig ??
|
|
108
|
+
(async () => {
|
|
109
|
+
// Absent from the public storefront-runner (dropped; see WebhookSigningConfig above).
|
|
110
|
+
// ts-ignore, not ts-expect-error: this module DOES exist in the full repo's own
|
|
111
|
+
// typecheck (only missing from the runner-extracted tree), so there's no error to
|
|
112
|
+
// "expect" here in that context — ts-expect-error would itself error as unused.
|
|
113
|
+
// @ts-ignore
|
|
114
|
+
const mod = await import("./webhookSigningKey.js");
|
|
115
|
+
return mod.loadWebhookSigningConfig() as Promise<WebhookSigningConfig>;
|
|
116
|
+
});
|
|
87
117
|
|
|
88
118
|
const delivery = await store.getDelivery(deliveryId);
|
|
89
119
|
if (!delivery) {
|
|
@@ -172,7 +202,11 @@ async function deliverSigned(
|
|
|
172
202
|
now: ctx.now,
|
|
173
203
|
});
|
|
174
204
|
const body = JSON.stringify(envelope);
|
|
175
|
-
|
|
205
|
+
// Absent from the public storefront-runner (dropped; see SignWebhookRequestResult above);
|
|
206
|
+
// ts-ignore not ts-expect-error, same reasoning as loadSigningConfig above.
|
|
207
|
+
// @ts-ignore
|
|
208
|
+
const { signWebhookRequest } = await import("./signing.js");
|
|
209
|
+
const { headers }: SignWebhookRequestResult = await signWebhookRequest({
|
|
176
210
|
method: "POST",
|
|
177
211
|
url: install.webhookEndpoint!,
|
|
178
212
|
body,
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
import { WebhookDeliveryStore, type WebhookTopic } from "./deliveryStore.js";
|
|
49
49
|
import { TOPIC_REQUIRED_SCOPE } from "./events.js";
|
|
50
50
|
import { getDispatcher } from "./getDispatcher.js";
|
|
51
|
-
import { getRegistryService } from "../apps/
|
|
51
|
+
import { getRegistryService } from "../apps/registryAccess.js";
|
|
52
52
|
import type { AppInstall } from "../apps/registryService.js";
|
|
53
53
|
import { fromD1 } from "../d1/catalog.js";
|
|
54
54
|
import { readD1 } from "../env.js";
|
|
@@ -21,7 +21,7 @@ import { CloudflareQueueDispatcher, type CloudflareQueueSender } from "./cloudfl
|
|
|
21
21
|
import { AppRegistryService } from "../apps/registryService.js";
|
|
22
22
|
import { D1CredentialStore } from "../apps/credentials.js";
|
|
23
23
|
import { DEFAULT_APP_GATEWAY_KID, APP_GATEWAY_KID_ENV } from "../apps/gatewayKeys.js";
|
|
24
|
-
import { json } from "../apps/
|
|
24
|
+
import { json } from "../apps/registryAccess.js";
|
|
25
25
|
import { fromD1 } from "../d1/catalog.js";
|
|
26
26
|
import { readD1, readEnv, readQueue } from "../env.js";
|
|
27
27
|
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "@/lib/auth/identityToken";
|
|
26
26
|
import { createSession, DEFAULT_SESSION_TTL_SECONDS } from "@/lib/auth/session";
|
|
27
27
|
import { normalizeEmailHint } from "@/lib/privacy/emailHint";
|
|
28
|
-
import { acceptDeveloperInvite } from "@/lib/
|
|
28
|
+
import { acceptDeveloperInvite } from "@/lib/shared/developerInvites";
|
|
29
29
|
|
|
30
30
|
export const prerender = false;
|
|
31
31
|
|
|
@@ -17,7 +17,7 @@ import TierCards from "@/components/membership/TierCards.astro";
|
|
|
17
17
|
import { withBase } from "@/lib/basePath";
|
|
18
18
|
import { rawHtmlPolicy, rawHtmlResponse } from "@/lib/rawMarketingHtml";
|
|
19
19
|
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
20
|
-
import { getRegistryService } from "@/lib/apps/
|
|
20
|
+
import { getRegistryService } from "@/lib/apps/registryAccess.js";
|
|
21
21
|
import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
|
|
22
22
|
import type { CatalogCollection, CatalogProduct } from "@tot/public-runtime";
|
|
23
23
|
import {
|
|
@@ -42,7 +42,7 @@ import { productLd, breadcrumbLd, reviewLd } from "@/lib/jsonld";
|
|
|
42
42
|
import { isUsableImageUrl } from "@/lib/placeholder";
|
|
43
43
|
import { withBase } from "@/lib/basePath";
|
|
44
44
|
import AppWidgetFrame from "@/components/apps/AppWidgetFrame.astro";
|
|
45
|
-
import { getRegistryService } from "@/lib/apps/
|
|
45
|
+
import { getRegistryService } from "@/lib/apps/registryAccess.js";
|
|
46
46
|
import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
|
|
47
47
|
|
|
48
48
|
const { handle } = Astro.params;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
basedir_win="$basedir"
|
|
4
|
+
exe=""
|
|
5
|
+
msys=""
|
|
6
|
+
|
|
7
|
+
case `uname -a` in
|
|
8
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
9
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
10
|
+
basedir_win=`cygpath -w "$basedir"`
|
|
11
|
+
fi
|
|
12
|
+
exe=".exe"
|
|
13
|
+
msys="true"
|
|
14
|
+
;;
|
|
15
|
+
*WSL2*)
|
|
16
|
+
if command -v wslpath > /dev/null 2>&1; then
|
|
17
|
+
basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"
|
|
18
|
+
if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then
|
|
19
|
+
basedir_win="$basedir"
|
|
20
|
+
else
|
|
21
|
+
exe=".exe"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
if [ -z "$NODE_PATH" ]; then
|
|
28
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules"
|
|
29
|
+
else
|
|
30
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
31
|
+
fi
|
|
32
|
+
if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
|
|
33
|
+
exec "$basedir/node.exe" "$basedir_win/../typescript/bin/tsc" "$@"
|
|
34
|
+
elif [ -x "$basedir/node" ]; then
|
|
35
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
36
|
+
elif command -v node >/dev/null 2>&1; then
|
|
37
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
38
|
+
elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
|
|
39
|
+
exec node.exe "$basedir_win/../typescript/bin/tsc" "$@"
|
|
40
|
+
else
|
|
41
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
42
|
+
fi
|
|
43
|
+
# cmd-shim-target=/home/runner/work/storefront/storefront/storefront-runner-dist/packages/public-runtime/node_modules/typescript/bin/tsc
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
basedir_win="$basedir"
|
|
4
|
+
exe=""
|
|
5
|
+
msys=""
|
|
6
|
+
|
|
7
|
+
case `uname -a` in
|
|
8
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
9
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
10
|
+
basedir_win=`cygpath -w "$basedir"`
|
|
11
|
+
fi
|
|
12
|
+
exe=".exe"
|
|
13
|
+
msys="true"
|
|
14
|
+
;;
|
|
15
|
+
*WSL2*)
|
|
16
|
+
if command -v wslpath > /dev/null 2>&1; then
|
|
17
|
+
basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"
|
|
18
|
+
if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then
|
|
19
|
+
basedir_win="$basedir"
|
|
20
|
+
else
|
|
21
|
+
exe=".exe"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
if [ -z "$NODE_PATH" ]; then
|
|
28
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules"
|
|
29
|
+
else
|
|
30
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
31
|
+
fi
|
|
32
|
+
if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
|
|
33
|
+
exec "$basedir/node.exe" "$basedir_win/../typescript/bin/tsserver" "$@"
|
|
34
|
+
elif [ -x "$basedir/node" ]; then
|
|
35
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
36
|
+
elif command -v node >/dev/null 2>&1; then
|
|
37
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
38
|
+
elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
|
|
39
|
+
exec node.exe "$basedir_win/../typescript/bin/tsserver" "$@"
|
|
40
|
+
else
|
|
41
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
42
|
+
fi
|
|
43
|
+
# cmd-shim-target=/home/runner/work/storefront/storefront/storefront-runner-dist/packages/public-runtime/node_modules/typescript/bin/tsserver
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
basedir_win="$basedir"
|
|
4
|
+
exe=""
|
|
5
|
+
msys=""
|
|
6
|
+
|
|
7
|
+
case `uname -a` in
|
|
8
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
9
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
10
|
+
basedir_win=`cygpath -w "$basedir"`
|
|
11
|
+
fi
|
|
12
|
+
exe=".exe"
|
|
13
|
+
msys="true"
|
|
14
|
+
;;
|
|
15
|
+
*WSL2*)
|
|
16
|
+
if command -v wslpath > /dev/null 2>&1; then
|
|
17
|
+
basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"
|
|
18
|
+
if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then
|
|
19
|
+
basedir_win="$basedir"
|
|
20
|
+
else
|
|
21
|
+
exe=".exe"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
26
|
+
|
|
27
|
+
if [ -z "$NODE_PATH" ]; then
|
|
28
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/vitest@2.1.8_@types+node@26.4.1_happy-dom@20.13.1_lightningcss@1.33.0/node_modules/vitest/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/vitest@2.1.8_@types+node@26.4.1_happy-dom@20.13.1_lightningcss@1.33.0/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules"
|
|
29
|
+
else
|
|
30
|
+
export NODE_PATH="/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/vitest@2.1.8_@types+node@26.4.1_happy-dom@20.13.1_lightningcss@1.33.0/node_modules/vitest/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/vitest@2.1.8_@types+node@26.4.1_happy-dom@20.13.1_lightningcss@1.33.0/node_modules:/home/runner/work/storefront/storefront/storefront-runner-dist/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
31
|
+
fi
|
|
32
|
+
if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
|
|
33
|
+
exec "$basedir/node.exe" "$basedir_win/../vitest/vitest.mjs" "$@"
|
|
34
|
+
elif [ -x "$basedir/node" ]; then
|
|
35
|
+
exec "$basedir/node" "$basedir/../vitest/vitest.mjs" "$@"
|
|
36
|
+
elif command -v node >/dev/null 2>&1; then
|
|
37
|
+
exec node "$basedir/../vitest/vitest.mjs" "$@"
|
|
38
|
+
elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
|
|
39
|
+
exec node.exe "$basedir_win/../vitest/vitest.mjs" "$@"
|
|
40
|
+
else
|
|
41
|
+
exec node "$basedir/../vitest/vitest.mjs" "$@"
|
|
42
|
+
fi
|
|
43
|
+
# cmd-shim-target=/home/runner/work/storefront/storefront/storefront-runner-dist/packages/public-runtime/node_modules/vitest/vitest.mjs
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a tenant's colocated directory lives — the NODE-side copy.
|
|
3
|
+
*
|
|
4
|
+
* A tenant IS a directory. Real tenants sit directly under `tenants/<id>/`;
|
|
5
|
+
* PLATFORM TEST tenants (every id ending `.e2e.test`) sit one level deeper,
|
|
6
|
+
* under `tenants/e2e/<id>/`.
|
|
7
|
+
*
|
|
8
|
+
* `@tot/public-runtime` owns this contract for the Worker/TS side
|
|
9
|
+
* (`packages/public-runtime/src/tenant.ts`), but it is TypeScript source with no
|
|
10
|
+
* build output, so the `.mjs` build/dev/tenant scripts cannot import it. This is
|
|
11
|
+
* that same total mapping, byte-for-byte in behavior, and
|
|
12
|
+
* `packages/public-runtime/tests/tenant-dirs-parity.test.ts` fails the build if
|
|
13
|
+
* the two ever disagree on any id.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The directory segment platform test tenants are nested under. */
|
|
17
|
+
export const E2E_TENANT_DIR = "e2e";
|
|
18
|
+
|
|
19
|
+
/** True for a PLATFORM TEST tenant id — the `<scenario>.e2e.test` class. */
|
|
20
|
+
export function isE2ETenantId(tenantId) {
|
|
21
|
+
return /\.e2e\.test$/.test(tenantId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Repo-relative, POSIX-separated directory for a tenant id. */
|
|
25
|
+
export function tenantDirRelative(tenantId) {
|
|
26
|
+
return isE2ETenantId(tenantId)
|
|
27
|
+
? `tenants/${E2E_TENANT_DIR}/${tenantId}`
|
|
28
|
+
: `tenants/${tenantId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The `/tenants/…/<id>/` substring identifying one tenant inside a path. */
|
|
32
|
+
export function tenantDirSegment(tenantId) {
|
|
33
|
+
return `/${tenantDirRelative(tenantId)}/`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The tenant id a `…/tenants/[e2e/]<id>/…` path encodes, or null. */
|
|
37
|
+
export function tenantIdFromPath(path) {
|
|
38
|
+
return /\/tenants\/(?:e2e\/)?([^/]+)\//.exec(path)?.[1] ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Narrow a set of tenant ids to what a generalized listing should show —
|
|
43
|
+
* platform test tenants are dropped unless explicitly requested.
|
|
44
|
+
*/
|
|
45
|
+
export function listedTenantIds(ids, opts = {}) {
|
|
46
|
+
return opts.includeTestTenants ? [...ids] : ids.filter((id) => !isE2ETenantId(id));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Path segments (relative to the repo root) for a tenant's own directory —
|
|
51
|
+
* ready for `join(repoRoot, ...tenantDirSegments(id))`.
|
|
52
|
+
*/
|
|
53
|
+
export function tenantDirSegments(tenantId) {
|
|
54
|
+
return tenantDirRelative(tenantId).split("/");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every tenant id with a directory under `tenants/`, real and test, from one
|
|
59
|
+
* `readdir`-shaped view of the tree. `readdirSync` is passed in so the walk
|
|
60
|
+
* stays testable without a filesystem.
|
|
61
|
+
*
|
|
62
|
+
* @param tenantsDir absolute path to the repo's `tenants/` directory
|
|
63
|
+
* @param readdir `(dir) => string[]` listing a directory's entry names
|
|
64
|
+
* @param isDir `(path) => boolean`
|
|
65
|
+
* @param join path join
|
|
66
|
+
*/
|
|
67
|
+
export function listTenantDirIds(tenantsDir, readdir, isDir, join) {
|
|
68
|
+
const ids = [];
|
|
69
|
+
for (const entry of readdir(tenantsDir)) {
|
|
70
|
+
if (!isDir(join(tenantsDir, entry))) continue;
|
|
71
|
+
if (entry === E2E_TENANT_DIR) {
|
|
72
|
+
for (const nested of readdir(join(tenantsDir, entry))) {
|
|
73
|
+
if (isDir(join(tenantsDir, entry, nested))) ids.push(nested);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
ids.push(entry);
|
|
78
|
+
}
|
|
79
|
+
return ids;
|
|
80
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-loop lint for third-party embeds (shift-left for the CSP allowlist).
|
|
3
|
+
*
|
|
4
|
+
* A strict CSP silently blocks any external <script>/<iframe> a tenant page
|
|
5
|
+
* embeds unless the origin is allowlisted (see packages/public-runtime/src/csp.ts
|
|
6
|
+
* — EMBED_PROVIDERS + embedCspAdditions). A blocked embed produces no build
|
|
7
|
+
* error and no page error — just a blank spot — so historically the only way it
|
|
8
|
+
* surfaced was a human noticing and escalating to the platform team.
|
|
9
|
+
*
|
|
10
|
+
* This lint closes that gap at author/preview time: it scans a tenant's HTML for
|
|
11
|
+
* external embed origins and tells the developer, right where they're working:
|
|
12
|
+
* - KNOWN provider present → make sure the tenant opts the slug into `embeds`
|
|
13
|
+
* (if we know the opt-in, hard-fail when it's missing).
|
|
14
|
+
* - UNKNOWN external origin → it will be blocked; a platform catalog entry is
|
|
15
|
+
* needed (EMBED_PROVIDERS), not a raw-origin config edit.
|
|
16
|
+
*
|
|
17
|
+
* Source of truth for the catalog is EMBED_PROVIDERS in TS; KNOWN_EMBEDS below
|
|
18
|
+
* mirrors it for dependency-free `node` execution and a vitest parity test
|
|
19
|
+
* (lint-embeds.test.mjs) fails if the two drift.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* node scripts/tenant/lint-embeds.mjs --dir tenants/<id>/content [--declared pipedrive,calendly]
|
|
23
|
+
* node scripts/tenant/lint-embeds.mjs --dir <tenant-repo>/content
|
|
24
|
+
* Exit non-zero when a blocking finding exists (undeclared known provider given
|
|
25
|
+
* a known opt-in, or an unknown external origin) so `tot preview` / CI can gate.
|
|
26
|
+
*/
|
|
27
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
28
|
+
import { join, extname } from "node:path";
|
|
29
|
+
import { EMBED_PROVIDERS } from "../../packages/public-runtime/src/embed-catalog.mjs";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The embed catalog, flattened for the lint from the shared source of truth
|
|
33
|
+
* (packages/public-runtime/src/embed-catalog.mjs — imported directly, no mirror).
|
|
34
|
+
* `markers` = substrings meaning the embed is on a page; `origins` = the union
|
|
35
|
+
* of the provider's exact CSP hosts.
|
|
36
|
+
*/
|
|
37
|
+
export const KNOWN_EMBEDS = Object.entries(EMBED_PROVIDERS).map(([slug, p]) => ({
|
|
38
|
+
slug,
|
|
39
|
+
label: p.label,
|
|
40
|
+
markers: p.markers,
|
|
41
|
+
origins: [...new Set([...(p.scriptSrc ?? []), ...(p.connectSrc ?? []), ...(p.frameSrc ?? [])])],
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
/** Hosts the platform always allows (self is implicit); embeds to these are fine. */
|
|
45
|
+
const ALWAYS_ALLOWED_HOSTS = new Set([
|
|
46
|
+
"static.cloudflareinsights.com",
|
|
47
|
+
"cloudflareinsights.com",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Only <script src> and <iframe src> load executable/frameable content the CSP
|
|
51
|
+
// gates (script-src / frame-src). We deliberately DON'T scan generic URLs
|
|
52
|
+
// (canonical links, JSON-LD @context, <a href>, og:url) — those aren't embeds
|
|
53
|
+
// and would be noisy false positives. Provider-specific embeds that mount via a
|
|
54
|
+
// data-attribute (e.g. Pipedrive's data-pd-webforms) are caught by their catalog
|
|
55
|
+
// marker instead, so they're covered without a generic URL sweep.
|
|
56
|
+
const SRC_RE = /<(?:script|iframe)\b[^>]*\bsrc\s*=\s*["']([^"']+)["']/gi;
|
|
57
|
+
|
|
58
|
+
/** Origin (host) of an absolute URL, or null for relative/self URLs. */
|
|
59
|
+
function originOf(u) {
|
|
60
|
+
try {
|
|
61
|
+
return new URL(u).origin;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Classify one HTML document's embeds.
|
|
69
|
+
* @returns {{ knownSlugs: string[], externalOrigins: string[], unknownOrigins: string[] }}
|
|
70
|
+
*/
|
|
71
|
+
export function scanHtmlForEmbeds(html) {
|
|
72
|
+
const knownSlugs = new Set();
|
|
73
|
+
for (const { slug, markers } of KNOWN_EMBEDS) {
|
|
74
|
+
if (markers.some((m) => html.includes(m))) knownSlugs.add(slug);
|
|
75
|
+
}
|
|
76
|
+
const external = new Set();
|
|
77
|
+
SRC_RE.lastIndex = 0;
|
|
78
|
+
let m;
|
|
79
|
+
while ((m = SRC_RE.exec(html))) {
|
|
80
|
+
const origin = originOf(m[1]);
|
|
81
|
+
if (!origin) continue;
|
|
82
|
+
const host = new URL(origin).hostname;
|
|
83
|
+
if (ALWAYS_ALLOWED_HOSTS.has(host)) continue;
|
|
84
|
+
external.add(origin);
|
|
85
|
+
}
|
|
86
|
+
// An external origin is "unknown" if no catalog provider claims it.
|
|
87
|
+
const catalogOrigins = new Set(KNOWN_EMBEDS.flatMap((p) => p.origins));
|
|
88
|
+
const unknown = [...external].filter((o) => !catalogOrigins.has(o));
|
|
89
|
+
return {
|
|
90
|
+
knownSlugs: [...knownSlugs],
|
|
91
|
+
externalOrigins: [...external],
|
|
92
|
+
unknownOrigins: unknown,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function walkHtml(dir) {
|
|
97
|
+
/** @type {string[]} */
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const entry of readdirSync(dir)) {
|
|
100
|
+
const p = join(dir, entry);
|
|
101
|
+
const st = statSync(p);
|
|
102
|
+
if (st.isDirectory()) out.push(...walkHtml(p));
|
|
103
|
+
else if (extname(p) === ".html") out.push(p);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Lint every HTML file under `dir`.
|
|
110
|
+
* @param {{ dir: string, declared?: string[] | null }} opts
|
|
111
|
+
* `declared` = the tenant's opted-in slugs if known (platform CI); when null,
|
|
112
|
+
* findings for known providers are advisory (we can't prove the opt-in here).
|
|
113
|
+
* @returns {{ findings: Array<{level:'error'|'warn'|'info', file:string, message:string}>, blocking: boolean }}
|
|
114
|
+
*/
|
|
115
|
+
export function lintTenantEmbeds({ dir, declared = null }) {
|
|
116
|
+
/** @type {Array<{level:'error'|'warn'|'info', file:string, message:string}>} */
|
|
117
|
+
const findings = [];
|
|
118
|
+
const declaredSet = declared ? new Set(declared) : null;
|
|
119
|
+
for (const file of walkHtml(dir)) {
|
|
120
|
+
const html = readFileSync(file, "utf8");
|
|
121
|
+
const { knownSlugs, unknownOrigins } = scanHtmlForEmbeds(html);
|
|
122
|
+
for (const slug of knownSlugs) {
|
|
123
|
+
const label = KNOWN_EMBEDS.find((p) => p.slug === slug)?.label ?? slug;
|
|
124
|
+
if (declaredSet && !declaredSet.has(slug)) {
|
|
125
|
+
findings.push({
|
|
126
|
+
level: "error",
|
|
127
|
+
file,
|
|
128
|
+
message: `embeds ${label} but the tenant has not opted "${slug}" into its embeds allowlist — the CSP will block it. Add "${slug}" to the tenant's embeds config.`,
|
|
129
|
+
});
|
|
130
|
+
} else if (!declaredSet) {
|
|
131
|
+
findings.push({
|
|
132
|
+
level: "info",
|
|
133
|
+
file,
|
|
134
|
+
message: `embeds ${label}: ensure "${slug}" is in the tenant's embeds allowlist, or the CSP will block it.`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const origin of unknownOrigins) {
|
|
139
|
+
findings.push({
|
|
140
|
+
level: "error",
|
|
141
|
+
file,
|
|
142
|
+
message: `embeds ${origin}, which is not a known embed provider — the CSP will block it. A platform catalog entry (EMBED_PROVIDERS) is required, or remove the embed.`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { findings, blocking: findings.some((f) => f.level === "error") };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function parseArgs(argv) {
|
|
150
|
+
const args = { dir: null, declared: null };
|
|
151
|
+
for (let i = 0; i < argv.length; i++) {
|
|
152
|
+
if (argv[i] === "--dir") args.dir = argv[++i];
|
|
153
|
+
else if (argv[i] === "--declared") args.declared = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
154
|
+
}
|
|
155
|
+
return args;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function main() {
|
|
159
|
+
const { dir, declared } = parseArgs(process.argv.slice(2));
|
|
160
|
+
if (!dir) {
|
|
161
|
+
console.error("usage: node scripts/tenant/lint-embeds.mjs --dir <content-dir> [--declared slug,slug]");
|
|
162
|
+
process.exit(2);
|
|
163
|
+
}
|
|
164
|
+
const { findings, blocking } = lintTenantEmbeds({ dir, declared });
|
|
165
|
+
if (findings.length === 0) {
|
|
166
|
+
console.log("embed lint: no external embeds found (or all allowlisted).");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const f of findings) {
|
|
170
|
+
const tag = f.level === "error" ? "BLOCKED" : f.level === "warn" ? "WARN" : "note";
|
|
171
|
+
console.log(`[embed-lint:${tag}] ${f.file}\n ${f.message}`);
|
|
172
|
+
}
|
|
173
|
+
if (blocking) {
|
|
174
|
+
console.error(`\nembed lint: ${findings.filter((f) => f.level === "error").length} embed(s) will be blocked by CSP — see above.`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Only run main when invoked directly, not when imported by the parity test.
|
|
180
|
+
if (import.meta.url === `file://${process.argv[1]}`) main();
|