@tokenoftrust/storefront-runner 2.2.111 → 2.2.113
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.
|
@@ -220,8 +220,8 @@
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function showMsg(kind, text) {
|
|
224
|
-
const el = $("msg");
|
|
223
|
+
function showMsg(kind, text, targetId) {
|
|
224
|
+
const el = $(targetId || "msg");
|
|
225
225
|
el.className = "msg " + kind;
|
|
226
226
|
el.replaceChildren();
|
|
227
227
|
if (typeof text === "string") {
|
|
@@ -242,15 +242,21 @@
|
|
|
242
242
|
btn.className = "secondary";
|
|
243
243
|
btn.textContent = "Provision now?";
|
|
244
244
|
btn.title = "Create the store repo on this environment, then retry the invite";
|
|
245
|
-
btn.addEventListener("click", () => provisionStore(btn));
|
|
245
|
+
btn.addEventListener("click", () => provisionStore(btn, { successHint: "Click Send invite again." }));
|
|
246
246
|
wrap.appendChild(line);
|
|
247
247
|
wrap.appendChild(btn);
|
|
248
248
|
showMsg("err", wrap);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
// Shared by the invite dead-end's one-shot "Provision now?" button AND the
|
|
252
|
+
// persistent "Repair store connection" button below — same idempotent
|
|
253
|
+
// POST /api/grants/provision-store, which re-asserts the repo AND its webhooks
|
|
254
|
+
// on every call, even when the repo is already provisioned.
|
|
255
|
+
async function provisionStore(btn, opts) {
|
|
256
|
+
opts = opts || {};
|
|
257
|
+
const targetId = opts.targetId || "msg";
|
|
252
258
|
if (btn) btn.disabled = true;
|
|
253
|
-
showMsg("", "Provisioning store repo…");
|
|
259
|
+
showMsg("", opts.pendingMessage || "Provisioning store repo…", targetId);
|
|
254
260
|
try {
|
|
255
261
|
const res = await fetch("/api/grants/provision-store", {
|
|
256
262
|
method: "POST",
|
|
@@ -262,14 +268,15 @@
|
|
|
262
268
|
const created = body.created ? "created" : "already present";
|
|
263
269
|
showMsg(
|
|
264
270
|
"ok",
|
|
265
|
-
"Store repo " + created + " for " + (body.tenant || tenant) +
|
|
266
|
-
|
|
271
|
+
"Store repo " + created + " for " + (body.tenant || tenant) + "." +
|
|
272
|
+
(opts.successHint ? " " + opts.successHint : ""),
|
|
273
|
+
targetId,
|
|
267
274
|
);
|
|
268
275
|
} else {
|
|
269
|
-
showMsg("err", body.error || ("Provision failed (HTTP " + res.status + ")."));
|
|
276
|
+
showMsg("err", body.error || ("Provision failed (HTTP " + res.status + ")."), targetId);
|
|
270
277
|
}
|
|
271
278
|
} catch (e) {
|
|
272
|
-
showMsg("err", "Provision failed: " + (e && e.message ? e.message : e));
|
|
279
|
+
showMsg("err", "Provision failed: " + (e && e.message ? e.message : e), targetId);
|
|
273
280
|
} finally {
|
|
274
281
|
if (btn) btn.disabled = false;
|
|
275
282
|
}
|
|
@@ -330,5 +337,20 @@
|
|
|
330
337
|
inviteBtn.addEventListener("click", invite);
|
|
331
338
|
emailInput.addEventListener("keydown", (e) => { if (e.key === "Enter") invite(); });
|
|
332
339
|
}
|
|
340
|
+
|
|
341
|
+
// Persistent repair affordance: the invite dead-end's "Provision now?" button only
|
|
342
|
+
// ever appears as a 409 reaction, so once a repo is `provisioned: true` that path
|
|
343
|
+
// can never recur — leaving no self-service way to re-run the same idempotent
|
|
344
|
+
// repair (e.g. when the repo's registered webhook has drifted). This button is
|
|
345
|
+
// always available, independent of invite state.
|
|
346
|
+
const repairBtn = $("repairBtn");
|
|
347
|
+
if (repairBtn) {
|
|
348
|
+
repairBtn.addEventListener("click", () => provisionStore(repairBtn, {
|
|
349
|
+
targetId: "repairMsg",
|
|
350
|
+
pendingMessage: "Repairing store connection…",
|
|
351
|
+
successHint: "Webhooks re-synced.",
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
|
|
333
355
|
loadMembers();
|
|
334
356
|
})();
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
CSP_REPORT_GROUP,
|
|
43
43
|
CSP_REPORT_PATH,
|
|
44
44
|
readPublishedArtifact,
|
|
45
|
+
readVersionArtifact,
|
|
45
46
|
themeArtifactPath,
|
|
46
47
|
resolveTenantFeatures,
|
|
47
48
|
resolveCapabilities,
|
|
@@ -63,6 +64,7 @@ import {
|
|
|
63
64
|
resolveNativeGa4SandboxUrl,
|
|
64
65
|
} from "@tot/public-runtime";
|
|
65
66
|
import { readBakedManagedInstalls } from "@/lib/integration/bakedManagedInstalls";
|
|
67
|
+
import { readWithImagePreviewRewriteOverlay } from "@tot/public-runtime/image-preview-rewrite-overlay";
|
|
66
68
|
import type {
|
|
67
69
|
CapabilityConfig,
|
|
68
70
|
ConsentSnapshot,
|
|
@@ -451,15 +453,13 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
451
453
|
? `${proto}://${host}${basePath}`
|
|
452
454
|
: `https://${tenant.canonicalDomain}`;
|
|
453
455
|
|
|
454
|
-
// Apply
|
|
455
|
-
//
|
|
456
|
-
// is published the build-time tenant theme is used unchanged.
|
|
456
|
+
// Apply the theme selected for this render. A candidate route reads its exact
|
|
457
|
+
// version; an ordinary route resolves the environment's published channel.
|
|
457
458
|
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
// pointer
|
|
461
|
-
//
|
|
462
|
-
// pointer fall back to the legacy per-path selection — no behavior change.
|
|
459
|
+
// readPublishedArtifact maps env to a publish channel (live->live,
|
|
460
|
+
// test->preview) and resolves it through the immutable site version. A
|
|
461
|
+
// candidate must bypass that pointer so a reviewer sees the candidate's own
|
|
462
|
+
// theme, capabilities, and embed opt-in with its content.
|
|
463
463
|
// Request-scoped read memo: every content read resolves the same pointer and
|
|
464
464
|
// version manifest, and a catalog render reads one artifact per catalog file.
|
|
465
465
|
const kvReads = new Map<string, Promise<string | null>>();
|
|
@@ -473,6 +473,15 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
473
473
|
return read;
|
|
474
474
|
}
|
|
475
475
|
: async () => null;
|
|
476
|
+
const readRuntimeArtifact = (path: string) => candidatePin
|
|
477
|
+
? readWithImagePreviewRewriteOverlay(
|
|
478
|
+
kvGet,
|
|
479
|
+
tenant.tenant_id,
|
|
480
|
+
candidatePin.versionId,
|
|
481
|
+
path,
|
|
482
|
+
() => readVersionArtifact(kvGet, tenant.tenant_id, candidatePin.versionId, path),
|
|
483
|
+
)
|
|
484
|
+
: readPublishedArtifact(kvGet, tenant.tenant_id, customization.env, path);
|
|
476
485
|
|
|
477
486
|
// Self-heal a DANGLING channel pointer: the pointer for this env's channel may
|
|
478
487
|
// name a version that was never built (or whose manifest was evicted).
|
|
@@ -480,7 +489,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
480
489
|
// the surface; here we do the once-per-request detection to ENQUEUE a rebuild of
|
|
481
490
|
// the dangling version. Fail-open: a KV hiccup never blocks the render (the reads
|
|
482
491
|
// below still run).
|
|
483
|
-
if (kv) {
|
|
492
|
+
if (kv && !candidatePin) {
|
|
484
493
|
const channel = channelFromEnv(customization.env);
|
|
485
494
|
await resolveServableChannelVersion(kvGet, tenant.tenant_id, channel, ({ versionId }) => {
|
|
486
495
|
recordActivity({
|
|
@@ -508,12 +517,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
508
517
|
draftClaims && draftClaims.tenantId === tenant.tenant_id ? draftClaims : null;
|
|
509
518
|
|
|
510
519
|
let theme = mergeTheme(referenceTheme, tenant.theme_tokens);
|
|
511
|
-
const publishedTheme = await
|
|
512
|
-
kvGet,
|
|
513
|
-
tenant.tenant_id,
|
|
514
|
-
customization.env,
|
|
515
|
-
themeArtifactPath(tenant.tenant_id),
|
|
516
|
-
);
|
|
520
|
+
const publishedTheme = await readRuntimeArtifact(themeArtifactPath(tenant.tenant_id));
|
|
517
521
|
if (publishedTheme) {
|
|
518
522
|
try {
|
|
519
523
|
theme = mergeTheme(theme, JSON.parse(publishedTheme) as DeepPartial<ThemeTokens>);
|
|
@@ -539,12 +543,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
539
543
|
// commit (localhost=working tree, preview=PR head, live=promoted). So a
|
|
540
544
|
// capability change is PR-gated and separable per environment.
|
|
541
545
|
let publishedCaps: Partial<CapabilityConfig> | null = null;
|
|
542
|
-
const publishedCapsRaw = await
|
|
543
|
-
kvGet,
|
|
544
|
-
tenant.tenant_id,
|
|
545
|
-
customization.env,
|
|
546
|
-
capabilitiesArtifactPath(tenant.tenant_id),
|
|
547
|
-
);
|
|
546
|
+
const publishedCapsRaw = await readRuntimeArtifact(capabilitiesArtifactPath(tenant.tenant_id));
|
|
548
547
|
if (publishedCapsRaw) {
|
|
549
548
|
try {
|
|
550
549
|
publishedCaps = JSON.parse(publishedCapsRaw) as Partial<CapabilityConfig>;
|
|
@@ -560,12 +559,7 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
|
|
|
560
559
|
// tenant enables a catalogued embed by committing embeds.json — no platform
|
|
561
560
|
// change — but can never introduce a non-catalog origin.
|
|
562
561
|
let publishedEmbeds: string[] | null = null;
|
|
563
|
-
const publishedEmbedsRaw = await
|
|
564
|
-
kvGet,
|
|
565
|
-
tenant.tenant_id,
|
|
566
|
-
customization.env,
|
|
567
|
-
embedsArtifactPath(tenant.tenant_id),
|
|
568
|
-
);
|
|
562
|
+
const publishedEmbedsRaw = await readRuntimeArtifact(embedsArtifactPath(tenant.tenant_id));
|
|
569
563
|
if (publishedEmbedsRaw) {
|
|
570
564
|
try {
|
|
571
565
|
const parsed = JSON.parse(publishedEmbedsRaw);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.113",
|
|
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",
|