@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
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ship operations workspace — the PURE composer behind the admin
|
|
3
|
-
* Publish tab's operations surface that sits ALONGSIDE the go-live core
|
|
4
|
-
* (AdminPublishTab.astro's change queue + accept/reject + dispatch status).
|
|
5
|
-
*
|
|
6
|
-
* This module composes the operations workspace ALONGSIDE the Publish-tab core
|
|
7
|
-
* (the change queue from GET /api/changes, Go-live/Reject, the live-version +
|
|
8
|
-
* dispatch-status panel, and the owner-only Domain section) — the three read/act
|
|
9
|
-
* surfaces woven into the same tab:
|
|
10
|
-
*
|
|
11
|
-
* 1. DIFF VIEW — for one pending candidate, the artifact-level diff of what
|
|
12
|
-
* changed (its rendered immutable version vs the current live version),
|
|
13
|
-
* tied to the exact tenant Gitea head SHA, with the immutable
|
|
14
|
-
* `/<tenant>/preview/rev/<sha>/` link and the evidence AUTHORITY
|
|
15
|
-
* (authoritative / stale / missing) — never a diff a reviewer would mistake
|
|
16
|
-
* for the head when a last-good version actually renders.
|
|
17
|
-
* 2. VERSIONS — the tenant's published-version / promotion history
|
|
18
|
-
* (`listPromotions`, promotion_status), each row carrying the tenant
|
|
19
|
-
* Gitea SHA, its promotion time, a
|
|
20
|
-
* `/rev/<sha>/` link, and — marking the current live one.
|
|
21
|
-
* 3. ROLLBACK — pick a prior version and roll back THROUGH the one orchestrator
|
|
22
|
-
* (`/api/changes/rollback` → `rollbackCandidateAtomic` /
|
|
23
|
-
* `staticRollbackOrchestrator`), rendering only the HONEST terminal state
|
|
24
|
-
* (`rolled_back` when VERIFIED; `publish_pending` in flight; `publish_failed`
|
|
25
|
-
* / `blocked` fail-closed) — never a false "rolled back".
|
|
26
|
-
*
|
|
27
|
-
* COMPOSES, never rebuilds:
|
|
28
|
-
* - the immutable link is `reviewDashboardRevisionHref` verbatim;
|
|
29
|
-
* - the authority is `candidateEvidenceAuthority` /
|
|
30
|
-
* `classifyCandidateForRender` verbatim;
|
|
31
|
-
* - the versions come from the SAME `KVVersionStore.listPromotions` the
|
|
32
|
-
* "one step back" rollback resolution reads;
|
|
33
|
-
* - the rollback ACTION is the existing `/api/changes/rollback` seam — this
|
|
34
|
-
* module only INTERPRETS its honest response, it never re-decides a ship.
|
|
35
|
-
*
|
|
36
|
-
* PURE (no I/O): the endpoints inject already-read KV data, so every honest-state
|
|
37
|
-
* decision is unit-tested without a Worker/KV — mirroring the reviewDashboard.ts /
|
|
38
|
-
* candidatePreviewState.ts split.
|
|
39
|
-
*
|
|
40
|
-
* `rollbackMode` names the path a rollback is honestly driven by: a `control-plane`
|
|
41
|
-
* (hosted) tenant and a `pointer` (static-publish) tenant BOTH roll back one-click
|
|
42
|
-
* via the instant KV `live` pointer flip — the Worker serves whatever bundle the
|
|
43
|
-
* pointer's digest names (from R2, for a static tenant), so restoring the pointer IS
|
|
44
|
-
* the rollback. The `publish_pending`/`publish_failed`/`blocked` honesty model from
|
|
45
|
-
* `interpretRollbackResponse` below applies identically in both modes — only the
|
|
46
|
-
* digest column (surfaced for a static tenant) differs.
|
|
47
|
-
*/
|
|
48
|
-
import { reviewDashboardRevisionHref } from "@/lib/preview/reviewDashboard";
|
|
49
|
-
import { assignVersionNumbers } from "@/lib/publish/versionNumber";
|
|
50
|
-
import {
|
|
51
|
-
candidateEvidenceAuthority,
|
|
52
|
-
classifyCandidateForRender,
|
|
53
|
-
defaultBlockedReason,
|
|
54
|
-
type CandidateEvidenceAuthority,
|
|
55
|
-
} from "@/lib/preview/candidatePreviewState";
|
|
56
|
-
import type { ReviewEnvironment, PromotionEvent } from "@tot/public-runtime";
|
|
57
|
-
|
|
58
|
-
// ── Versions / promotion history ───────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
/** The plain-language "what shipped in this version" model (same shape the staged
|
|
61
|
-
* release summary uses — a name + a lead sentence + one highlight per included change). */
|
|
62
|
-
export interface VersionReleaseSummary {
|
|
63
|
-
/** The human-readable release name (see `deriveReleaseName`), or null. */
|
|
64
|
-
name: string | null;
|
|
65
|
-
lead: string;
|
|
66
|
-
highlights: { title: string; detail: string }[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** One change that shipped in a published version — its PR summary, for the
|
|
70
|
-
* version's openable "what shipped" list. Fields degrade to null/`PR #N` when
|
|
71
|
-
* candidate_summary didn't cover the PR (best-effort enrichment). */
|
|
72
|
-
export interface WorkspaceVersionChange {
|
|
73
|
-
changeId: string;
|
|
74
|
-
prNumber: number | null;
|
|
75
|
-
headSha: string;
|
|
76
|
-
title: string | null;
|
|
77
|
-
description: string | null;
|
|
78
|
-
fileCount: number | null;
|
|
79
|
-
linesAdded: number | null;
|
|
80
|
-
linesRemoved: number | null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Per-version release enrichment, keyed by versionId (== the promoted aggregate sha). */
|
|
84
|
-
export interface VersionReleaseInfo {
|
|
85
|
-
releaseSummary: VersionReleaseSummary | null;
|
|
86
|
-
changes: WorkspaceVersionChange[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** One published version in the tenant's promotion history (promotion_status). */
|
|
90
|
-
export interface WorkspaceVersion {
|
|
91
|
-
/** The tenant Gitea SHA the version serves — the content identity. */
|
|
92
|
-
versionId: string;
|
|
93
|
-
/**
|
|
94
|
-
* The merchant-facing monotonic version number ("v42") — derived from first
|
|
95
|
-
* promotion order (see `versionNumber.ts`), shared with the "Live version"
|
|
96
|
-
* metric. The human label; the SHA is demoted to a technical disclosure.
|
|
97
|
-
*/
|
|
98
|
-
number: number | null;
|
|
99
|
-
/** How the version was published (a Gitea commit, or a content hash). */
|
|
100
|
-
source: "commit" | "hash" | null;
|
|
101
|
-
/** ISO time the version was PROMOTED onto `live` (the promotion event). */
|
|
102
|
-
promotedAt: string;
|
|
103
|
-
/** ISO time the version was first published (from its site-version manifest). */
|
|
104
|
-
createdAt: string | null;
|
|
105
|
-
/** Artifact count in the version manifest, or `null` when the manifest is absent. */
|
|
106
|
-
artifactCount: number | null;
|
|
107
|
-
/**
|
|
108
|
-
* The content-addressed `treeDigest`, when the console can honestly know it —
|
|
109
|
-
* i.e. ONLY the current live version of a static tenant (read back from
|
|
110
|
-
* `live/manifest.json`). `null` for every other row: a prior static version's
|
|
111
|
-
* digest lives only in the S3 archive the Worker can't enumerate. Never fabricated.
|
|
112
|
-
*/
|
|
113
|
-
digest: string | null;
|
|
114
|
-
/** The immutable `/<tenant>/preview/rev/<sha>/` deep-link, or `null` without a SHA. */
|
|
115
|
-
revHref: string | null;
|
|
116
|
-
/** The version currently serving live (the one you never roll back TO). */
|
|
117
|
-
isCurrent: boolean;
|
|
118
|
-
/**
|
|
119
|
-
* The plain-language "what shipped in this version" summary, composed from the
|
|
120
|
-
* release receipt's included PRs + their candidate_summary. `null` when no
|
|
121
|
-
* receipt links this version to a PR set, or none carried summary prose.
|
|
122
|
-
*/
|
|
123
|
-
releaseSummary: VersionReleaseSummary | null;
|
|
124
|
-
/**
|
|
125
|
-
* The PRs that shipped in this version (from its release receipt), each with its
|
|
126
|
-
* human summary — the version's openable "what shipped" list. Empty when no
|
|
127
|
-
* receipt links this version to a PR set.
|
|
128
|
-
*/
|
|
129
|
-
changes: WorkspaceVersionChange[];
|
|
130
|
-
/** How many changes shipped in this version, or `null` when unknown (no receipt). */
|
|
131
|
-
changeCount: number | null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* How a rollback is honestly driven for this tenant. `control-plane` (hosted) and
|
|
136
|
-
* `pointer` (static-publish) both roll back one-click via the instant KV `live`
|
|
137
|
-
* pointer flip; they render identical instant "Roll back" copy, differing only in
|
|
138
|
-
* whether the static digest column is shown.
|
|
139
|
-
*/
|
|
140
|
-
export type RollbackMode = "control-plane" | "pointer";
|
|
141
|
-
|
|
142
|
-
/** The versions-panel view model. */
|
|
143
|
-
export interface VersionsView {
|
|
144
|
-
/** The current live version + (static) the digest `live/` actually serves. */
|
|
145
|
-
current: { versionId: string | null; number: number | null; digest: string | null };
|
|
146
|
-
/** Promotion history, newest first, current marked. */
|
|
147
|
-
versions: WorkspaceVersion[];
|
|
148
|
-
/** Whether the tenant publishes its public site to an external static target. */
|
|
149
|
-
staticPublish: boolean;
|
|
150
|
-
/**
|
|
151
|
-
* `control-plane` (hosted) and `pointer` (static-publish) both roll back
|
|
152
|
-
* one-click via `targetVersionId` (instant KV pointer flip) and render identical
|
|
153
|
-
* instant "Roll back" copy.
|
|
154
|
-
*/
|
|
155
|
-
rollbackMode: RollbackMode;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** A single promoted version enriched with the manifest facts the panel shows. */
|
|
159
|
-
export interface VersionManifestFacts {
|
|
160
|
-
source: "commit" | "hash" | null;
|
|
161
|
-
createdAt: string | null;
|
|
162
|
-
artifactCount: number | null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface BuildVersionsViewInput {
|
|
166
|
-
tenantId: string;
|
|
167
|
-
/** Live-promotion history, newest first (`KVVersionStore.listPromotions`). */
|
|
168
|
-
promotions: PromotionEvent[];
|
|
169
|
-
/** The current live version id (the live-channel pointer), or `null`. */
|
|
170
|
-
currentVersionId: string | null;
|
|
171
|
-
/** The digest `live/` serves — static tenants only; `null` otherwise. */
|
|
172
|
-
currentDigest: string | null;
|
|
173
|
-
/** Per-version manifest facts, keyed by versionId (absent ⇒ unknown facts). */
|
|
174
|
-
manifests: Map<string, VersionManifestFacts>;
|
|
175
|
-
/**
|
|
176
|
-
* Per-version release enrichment, keyed by versionId (== the promoted aggregate
|
|
177
|
-
* sha on the release receipt). Absent ⇒ a version with no linked PR set (an
|
|
178
|
-
* honest empty release summary + no changes list — never fabricated).
|
|
179
|
-
*/
|
|
180
|
-
releases?: Map<string, VersionReleaseInfo>;
|
|
181
|
-
staticPublish: boolean;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Assemble the versions panel from the promotion history + per-version manifest
|
|
186
|
-
* facts + the current-live pointer. Pure. The current live row is marked; the
|
|
187
|
-
* digest is populated ONLY for the current live of a static tenant (never faked
|
|
188
|
-
* for priors). `revHref` is the immutable link.
|
|
189
|
-
*/
|
|
190
|
-
export function buildVersionsView(input: BuildVersionsViewInput): VersionsView {
|
|
191
|
-
const {
|
|
192
|
-
tenantId,
|
|
193
|
-
promotions,
|
|
194
|
-
currentVersionId,
|
|
195
|
-
currentDigest,
|
|
196
|
-
manifests,
|
|
197
|
-
releases,
|
|
198
|
-
staticPublish,
|
|
199
|
-
} = input;
|
|
200
|
-
|
|
201
|
-
// The merchant-facing "v42" counter, derived from first-promotion order — the
|
|
202
|
-
// SAME derivation `resolveLiveVersion` feeds the "Live version" metric, so the
|
|
203
|
-
// panel and the metric always agree on a version's number.
|
|
204
|
-
const numbers = assignVersionNumbers(promotions);
|
|
205
|
-
|
|
206
|
-
const versions: WorkspaceVersion[] = promotions.map((p) => {
|
|
207
|
-
const facts = manifests.get(p.versionId);
|
|
208
|
-
const release = releases?.get(p.versionId);
|
|
209
|
-
const isCurrent = currentVersionId != null && p.versionId === currentVersionId;
|
|
210
|
-
return {
|
|
211
|
-
versionId: p.versionId,
|
|
212
|
-
number: numbers.get(p.versionId) ?? null,
|
|
213
|
-
source: facts?.source ?? null,
|
|
214
|
-
promotedAt: p.at,
|
|
215
|
-
createdAt: facts?.createdAt ?? null,
|
|
216
|
-
artifactCount: facts?.artifactCount ?? null,
|
|
217
|
-
// Honest: a digest is known only for the current live of a static tenant.
|
|
218
|
-
digest: isCurrent && staticPublish ? currentDigest : null,
|
|
219
|
-
revHref: reviewDashboardRevisionHref(tenantId, p.versionId),
|
|
220
|
-
isCurrent,
|
|
221
|
-
releaseSummary: release?.releaseSummary ?? null,
|
|
222
|
-
changes: release?.changes ?? [],
|
|
223
|
-
changeCount: release ? release.changes.length : null,
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
current: {
|
|
229
|
-
versionId: currentVersionId,
|
|
230
|
-
number: currentVersionId != null ? numbers.get(currentVersionId) ?? null : null,
|
|
231
|
-
digest: staticPublish ? currentDigest : null,
|
|
232
|
-
},
|
|
233
|
-
versions,
|
|
234
|
-
staticPublish,
|
|
235
|
-
rollbackMode: staticPublish ? "pointer" : "control-plane",
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ── Diff view ──────────────────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
export type DiffChangeKind = "added" | "removed" | "changed";
|
|
242
|
-
|
|
243
|
-
/** One artifact path that differs between the candidate and the current live version. */
|
|
244
|
-
export interface DiffEntry {
|
|
245
|
-
path: string;
|
|
246
|
-
change: DiffChangeKind;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** The diff-view model for one pending candidate. */
|
|
250
|
-
export interface CandidateDiffView {
|
|
251
|
-
changeId: string;
|
|
252
|
-
/** The candidate's exact head SHA — the reconcile TARGET (tenant Gitea sha). */
|
|
253
|
-
headSha: string | null;
|
|
254
|
-
/** The immutable version actually rendered + diffed — the reconcile ACTUAL. */
|
|
255
|
-
versionId: string | null;
|
|
256
|
-
/** The immutable link to the exact reviewed SHA, or `null` without a head SHA. */
|
|
257
|
-
revHref: string | null;
|
|
258
|
-
/** Evidence authority: authoritative (tied to head) / stale (last-good) / missing. */
|
|
259
|
-
authority: CandidateEvidenceAuthority;
|
|
260
|
-
/** The artifact-level diff (candidate rendered version vs current live), sorted by path. */
|
|
261
|
-
files: DiffEntry[];
|
|
262
|
-
/** True when the candidate has a renderable version to diff (the `ready` classification). */
|
|
263
|
-
renderable: boolean;
|
|
264
|
-
/** When NOT renderable, the honest reason (names why) — never a fake empty diff. */
|
|
265
|
-
blockedReason: string | null;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export interface BuildCandidateDiffInput {
|
|
269
|
-
tenantId: string;
|
|
270
|
-
env: ReviewEnvironment;
|
|
271
|
-
/** The candidate rendered version's artifacts (path → contentHash), or `null`. */
|
|
272
|
-
candidateArtifacts: Record<string, string> | null;
|
|
273
|
-
/** The current live version's artifacts (path → contentHash), or `null` (nothing live yet). */
|
|
274
|
-
liveArtifacts: Record<string, string> | null;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Diff a candidate's rendered version against the current live version, at the
|
|
279
|
-
* artifact level (each path carries a content hash in its site-version manifest).
|
|
280
|
-
* Composes `classifyCandidateForRender` (only a `ready` candidate has a
|
|
281
|
-
* version to diff — a blocked/queued one yields an HONEST `blockedReason`, never a
|
|
282
|
-
* fabricated empty diff) and `candidateEvidenceAuthority` (so a diff of a last-good
|
|
283
|
-
* version that LAGS the head is flagged `stale`, not passed off as the head).
|
|
284
|
-
* Pure.
|
|
285
|
-
*/
|
|
286
|
-
export function buildCandidateDiff(input: BuildCandidateDiffInput): CandidateDiffView {
|
|
287
|
-
const { tenantId, env, candidateArtifacts, liveArtifacts } = input;
|
|
288
|
-
const classification = classifyCandidateForRender(env);
|
|
289
|
-
const authority = candidateEvidenceAuthority(env);
|
|
290
|
-
const revHref = reviewDashboardRevisionHref(tenantId, env.headSha);
|
|
291
|
-
|
|
292
|
-
if (classification.kind !== "ready") {
|
|
293
|
-
const reason =
|
|
294
|
-
classification.kind === "blocked"
|
|
295
|
-
? classification.reason
|
|
296
|
-
: defaultBlockedReason(env.status);
|
|
297
|
-
return {
|
|
298
|
-
changeId: env.changeId,
|
|
299
|
-
headSha: env.headSha,
|
|
300
|
-
versionId: null,
|
|
301
|
-
revHref,
|
|
302
|
-
authority,
|
|
303
|
-
files: [],
|
|
304
|
-
renderable: false,
|
|
305
|
-
blockedReason: reason,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const files = diffArtifacts(candidateArtifacts ?? {}, liveArtifacts ?? {});
|
|
310
|
-
return {
|
|
311
|
-
changeId: env.changeId,
|
|
312
|
-
headSha: env.headSha,
|
|
313
|
-
versionId: classification.versionId,
|
|
314
|
-
revHref,
|
|
315
|
-
authority,
|
|
316
|
-
files,
|
|
317
|
-
renderable: true,
|
|
318
|
-
blockedReason: null,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/** Artifact-level diff: paths added / removed / content-changed, sorted by path. */
|
|
323
|
-
export function diffArtifacts(
|
|
324
|
-
candidate: Record<string, string>,
|
|
325
|
-
live: Record<string, string>,
|
|
326
|
-
): DiffEntry[] {
|
|
327
|
-
const entries: DiffEntry[] = [];
|
|
328
|
-
for (const [path, hash] of Object.entries(candidate)) {
|
|
329
|
-
if (!(path in live)) entries.push({ path, change: "added" });
|
|
330
|
-
else if (live[path] !== hash) entries.push({ path, change: "changed" });
|
|
331
|
-
}
|
|
332
|
-
for (const path of Object.keys(live)) {
|
|
333
|
-
if (!(path in candidate)) entries.push({ path, change: "removed" });
|
|
334
|
-
}
|
|
335
|
-
return entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ── Rollback response interpretation ─────────────────────────────────────────
|
|
339
|
-
|
|
340
|
-
/** The normalized honest terminal state of a rollback, for the surface to render. */
|
|
341
|
-
export type RollbackState =
|
|
342
|
-
| "rolled_back" // VERIFIED live on the prior version — the ONLY "rolled back" claim
|
|
343
|
-
| "publish_pending" // restored + dispatched, not yet verified — NOT "rolled back"
|
|
344
|
-
| "publish_failed" // fail-closed: unwired / dispatch failed / verification failed
|
|
345
|
-
| "blocked" // a gate refused (capability / no prior / race) — nothing moved
|
|
346
|
-
| "error"; // transport / unexpected shape
|
|
347
|
-
|
|
348
|
-
export interface RollbackOutcome {
|
|
349
|
-
state: RollbackState;
|
|
350
|
-
/** True ONLY for `rolled_back` (the target is verified to serve the prior version). */
|
|
351
|
-
rolledBack: boolean;
|
|
352
|
-
/** The version restored TO, when known. */
|
|
353
|
-
toVersionId: string | null;
|
|
354
|
-
/** A human-legible message for the surface. */
|
|
355
|
-
message: string;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Interpret the `/api/changes/rollback` response into ONE honest terminal state,
|
|
360
|
-
* for BOTH planes without the surface having to know which it hit:
|
|
361
|
-
* - a HOSTED (control-plane) rollback returns `{ rolledBack: true, toVersionId }`
|
|
362
|
-
* on success (the pointer flip IS the rollback) or `{ error, reason }` on refusal;
|
|
363
|
-
* - a STATIC rollback returns the orchestrator's honest `{ state, ... }`.
|
|
364
|
-
* The rule the surface depends on: `rolledBack` is true ONLY for a verified
|
|
365
|
-
* `rolled_back` — a `publish_pending` is NEVER rendered as rolled back.
|
|
366
|
-
* Pure.
|
|
367
|
-
*/
|
|
368
|
-
export function interpretRollbackResponse(
|
|
369
|
-
httpOk: boolean,
|
|
370
|
-
body: {
|
|
371
|
-
state?: unknown;
|
|
372
|
-
rolledBack?: unknown;
|
|
373
|
-
toVersionId?: unknown;
|
|
374
|
-
message?: unknown;
|
|
375
|
-
error?: unknown;
|
|
376
|
-
reason?: unknown;
|
|
377
|
-
},
|
|
378
|
-
): RollbackOutcome {
|
|
379
|
-
const toVersionId = typeof body.toVersionId === "string" ? body.toVersionId : null;
|
|
380
|
-
|
|
381
|
-
// Static plane: the orchestrator's explicit honest state wins.
|
|
382
|
-
if (typeof body.state === "string") {
|
|
383
|
-
const state = normalizeRollbackState(body.state);
|
|
384
|
-
const message =
|
|
385
|
-
typeof body.message === "string" && body.message
|
|
386
|
-
? body.message
|
|
387
|
-
: typeof body.error === "string" && body.error
|
|
388
|
-
? body.error
|
|
389
|
-
: rollbackStateFallbackMessage(state);
|
|
390
|
-
return { state, rolledBack: state === "rolled_back", toVersionId, message };
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Control-plane: a bare success flag, or a typed refusal.
|
|
394
|
-
if (httpOk && body.rolledBack === true) {
|
|
395
|
-
return {
|
|
396
|
-
state: "rolled_back",
|
|
397
|
-
rolledBack: true,
|
|
398
|
-
toVersionId,
|
|
399
|
-
message: toVersionId ? `Rolled back live to ${toVersionId}.` : "Rolled back live.",
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const reason = typeof body.reason === "string" ? body.reason : null;
|
|
404
|
-
const message =
|
|
405
|
-
typeof body.error === "string" && body.error
|
|
406
|
-
? body.error + (reason ? ` (${reason})` : "")
|
|
407
|
-
: "Rollback failed.";
|
|
408
|
-
// A control-plane refusal is a gate refusal (capability / no prior / conflict) —
|
|
409
|
-
// nothing moved; treat as blocked (never a false rolled-back).
|
|
410
|
-
return { state: "blocked", rolledBack: false, toVersionId, message };
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function normalizeRollbackState(raw: string): RollbackState {
|
|
414
|
-
switch (raw) {
|
|
415
|
-
case "rolled_back":
|
|
416
|
-
case "publish_pending":
|
|
417
|
-
case "publish_failed":
|
|
418
|
-
case "blocked":
|
|
419
|
-
return raw;
|
|
420
|
-
default:
|
|
421
|
-
return "error";
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function rollbackStateFallbackMessage(state: RollbackState): string {
|
|
426
|
-
switch (state) {
|
|
427
|
-
case "rolled_back":
|
|
428
|
-
return "Rolled back live.";
|
|
429
|
-
case "publish_pending":
|
|
430
|
-
return "Restored the pointer and dispatched the re-publish — not yet verified live. Refresh to re-check.";
|
|
431
|
-
case "publish_failed":
|
|
432
|
-
return "Rollback failed — the publish path is unwired or verification failed. Nothing false went live.";
|
|
433
|
-
case "blocked":
|
|
434
|
-
return "Rollback was refused by a gate — nothing changed.";
|
|
435
|
-
case "error":
|
|
436
|
-
default:
|
|
437
|
-
return "Rollback returned an unexpected response.";
|
|
438
|
-
}
|
|
439
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The shared "buffer-or-stream on `Accept`" plumbing for a long-running admin
|
|
3
|
-
* route. ONE emit-driven work function serves BOTH transports:
|
|
4
|
-
*
|
|
5
|
-
* - a non-streaming caller (the CLI, any `Accept: application/json` client) gets
|
|
6
|
-
* the buffered terminal JSON body + status the work resolved — `emit` is a no-op;
|
|
7
|
-
* - a streaming caller (`Accept: text/event-stream`, the admin Publish tab) gets
|
|
8
|
-
* each `emit(event, data)` as an SSE frame AS IT HAPPENS, then a terminal
|
|
9
|
-
* `result` event carrying the SAME payload the JSON body would have, then close.
|
|
10
|
-
*
|
|
11
|
-
* The work runs ONCE either way; the terminal payload + status are IDENTICAL. Only
|
|
12
|
-
* the delivery differs. This is the generalized form of the pattern
|
|
13
|
-
* `POST /api/preview/build` first shipped inline — extracted here so every
|
|
14
|
-
* long-running route (build / integrate / rebuild / ship) streams the same way
|
|
15
|
-
* without re-implementing the ReadableStream/encoder/close dance.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* A buffered JSON Response — the non-streaming terminal body. Inlined (not imported
|
|
20
|
-
* from the change/grants controlplane) so this generic transport helper stays in the
|
|
21
|
-
* `lib/publish` render layer, respecting the renderer-no-controlplane boundary.
|
|
22
|
-
*/
|
|
23
|
-
function jsonResponse(body: unknown, status = 200): Response {
|
|
24
|
-
return new Response(JSON.stringify(body), {
|
|
25
|
-
status,
|
|
26
|
-
headers: { "content-type": "application/json", "cache-control": "no-store" },
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Emit one SSE event. A no-op in buffered (JSON) mode. */
|
|
31
|
-
export type SseEmit = (event: string, data: Record<string, unknown>) => void;
|
|
32
|
-
|
|
33
|
-
/** The terminal outcome of the work: the HTTP status + the JSON/`result` payload. */
|
|
34
|
-
export interface StreamWorkResult {
|
|
35
|
-
status: number;
|
|
36
|
-
payload: Record<string, unknown>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** True when the caller asked for an SSE stream (`Accept: text/event-stream`). */
|
|
40
|
-
export function wantsEventStream(request: Request): boolean {
|
|
41
|
-
return (request.headers.get("accept") || "").includes("text/event-stream");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Run `work` and deliver its terminal payload as either a buffered JSON Response
|
|
46
|
-
* (default) or a streamed SSE Response (when the caller sent
|
|
47
|
-
* `Accept: text/event-stream`). In stream mode each `emit` becomes an
|
|
48
|
-
* `event: <name>\ndata: <json>\n\n` frame; the terminal payload is sent as a
|
|
49
|
-
* `result` event and the stream closes. `onError` maps an UNEXPECTED throw (the
|
|
50
|
-
* work is expected to be fail-safe and return its own error payload) to a terminal
|
|
51
|
-
* result — in JSON mode it becomes the response body+status, in SSE mode the
|
|
52
|
-
* `result` event.
|
|
53
|
-
*/
|
|
54
|
-
export async function respondBufferedOrStreamed(
|
|
55
|
-
request: Request,
|
|
56
|
-
work: (emit: SseEmit) => Promise<StreamWorkResult>,
|
|
57
|
-
opts?: { onError?: (error: unknown) => StreamWorkResult },
|
|
58
|
-
): Promise<Response> {
|
|
59
|
-
const onError =
|
|
60
|
-
opts?.onError ??
|
|
61
|
-
((error: unknown): StreamWorkResult => ({
|
|
62
|
-
status: 500,
|
|
63
|
-
payload: {
|
|
64
|
-
ok: false,
|
|
65
|
-
outcome: "failed",
|
|
66
|
-
errors: [`operation failed: ${error instanceof Error ? error.message : String(error)}`],
|
|
67
|
-
},
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
// Buffered JSON mode — the CLI / any non-streaming caller. `emit` is a no-op; the
|
|
71
|
-
// work's own status + payload are returned verbatim (same contract as before).
|
|
72
|
-
if (!wantsEventStream(request)) {
|
|
73
|
-
try {
|
|
74
|
-
const { status, payload } = await work(() => {});
|
|
75
|
-
return jsonResponse(payload, status);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
const { status, payload } = onError(error);
|
|
78
|
-
return jsonResponse(payload, status);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// SSE mode — stream each phase as it happens, then a terminal `result` event
|
|
83
|
-
// carrying the SAME payload the JSON body would have, then close. Workers stream a
|
|
84
|
-
// ReadableStream body natively — no infra.
|
|
85
|
-
const encoder = new TextEncoder();
|
|
86
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
87
|
-
start(controller) {
|
|
88
|
-
const send: SseEmit = (event, data) => {
|
|
89
|
-
try {
|
|
90
|
-
controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
|
|
91
|
-
} catch {
|
|
92
|
-
/* controller already closed (client disconnected) — nothing to do */
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
void (async () => {
|
|
96
|
-
try {
|
|
97
|
-
const { payload } = await work(send);
|
|
98
|
-
send("result", payload);
|
|
99
|
-
} catch (error) {
|
|
100
|
-
send("result", onError(error).payload);
|
|
101
|
-
} finally {
|
|
102
|
-
try {
|
|
103
|
-
controller.close();
|
|
104
|
-
} catch {
|
|
105
|
-
/* already closed */
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
})();
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
return new Response(stream, {
|
|
112
|
-
status: 200,
|
|
113
|
-
headers: {
|
|
114
|
-
"content-type": "text/event-stream; charset=utf-8",
|
|
115
|
-
"cache-control": "no-cache, no-transform",
|
|
116
|
-
"x-accel-buffering": "no",
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
}
|