@tokenoftrust/storefront-runner 2.0.55 → 2.0.56

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.
@@ -128,7 +128,56 @@
128
128
  function showMsg(kind, text) {
129
129
  const el = $("msg");
130
130
  el.className = "msg " + kind;
131
- el.textContent = text;
131
+ el.replaceChildren();
132
+ if (typeof text === "string") {
133
+ el.textContent = text;
134
+ return;
135
+ }
136
+ if (text && text.nodeType) el.appendChild(text);
137
+ }
138
+
139
+ function showProvisionPrompt(message) {
140
+ const wrap = document.createElement("div");
141
+ const line = document.createElement("p");
142
+ line.style.margin = "0 0 .6rem";
143
+ line.textContent = message || "This store's repo isn't provisioned yet.";
144
+ const btn = document.createElement("button");
145
+ btn.type = "button";
146
+ btn.id = "provisionBtn";
147
+ btn.className = "secondary";
148
+ btn.textContent = "Provision now?";
149
+ btn.title = "Create the store repo on this environment, then retry the invite";
150
+ btn.addEventListener("click", () => provisionStore(btn));
151
+ wrap.appendChild(line);
152
+ wrap.appendChild(btn);
153
+ showMsg("err", wrap);
154
+ }
155
+
156
+ async function provisionStore(btn) {
157
+ if (btn) btn.disabled = true;
158
+ showMsg("", "Provisioning store repo…");
159
+ try {
160
+ const res = await fetch("/api/grants/provision-store", {
161
+ method: "POST",
162
+ headers: { "content-type": "application/json", accept: "application/json" },
163
+ body: JSON.stringify({ tenant, confirm: true }),
164
+ });
165
+ const body = await res.json().catch(() => ({}));
166
+ if (res.ok) {
167
+ const created = body.created ? "created" : "already present";
168
+ showMsg(
169
+ "ok",
170
+ "Store repo " + created + " for " + (body.tenant || tenant) +
171
+ ". Click Send invite again.",
172
+ );
173
+ } else {
174
+ showMsg("err", body.error || ("Provision failed (HTTP " + res.status + ")."));
175
+ }
176
+ } catch (e) {
177
+ showMsg("err", "Provision failed: " + (e && e.message ? e.message : e));
178
+ } finally {
179
+ if (btn) btn.disabled = false;
180
+ }
132
181
  }
133
182
 
134
183
  async function invite() {
@@ -161,6 +210,11 @@
161
210
  $("email").value = "";
162
211
  if (noteEl) noteEl.value = "";
163
212
  loadMembers();
213
+ } else if (
214
+ res.status === 409 &&
215
+ (body.code === "store-not-provisioned" || body.reason === "storeNotProvisioned")
216
+ ) {
217
+ showProvisionPrompt(body.error);
164
218
  } else {
165
219
  showMsg("err", body.error || ("Invite failed (HTTP " + res.status + ")."));
166
220
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * TENANT_CACHE projection for staff/operator store provision.
3
+ *
4
+ * Lives under src/config so pages/lib may call it without crossing the
5
+ * tenancy-resolver-fence (only config/middleware may import kvTenantResolver).
6
+ */
7
+ import type { TenantConfig } from "@tot/public-runtime";
8
+ import { putTenant } from "@/config/kvTenantResolver";
9
+
10
+ export async function projectProvisionedTenant(
11
+ kv: KVNamespace,
12
+ config: TenantConfig,
13
+ repoMeta: { owner: string; repo: string; cloneUrl: string | null },
14
+ ): Promise<void> {
15
+ await putTenant(kv, config);
16
+ await kv.put(
17
+ `repo:${config.tenant_id}`,
18
+ JSON.stringify({
19
+ owner: repoMeta.owner,
20
+ repo: repoMeta.repo,
21
+ cloneUrl: repoMeta.cloneUrl,
22
+ }),
23
+ );
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.0.55",
3
+ "version": "2.0.56",
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",
@@ -479,6 +479,14 @@ export const ACTION_CATALOG = {
479
479
  argsAllow: ["resource", "capability"], renderedAllow: [],
480
480
  desc: "A ToT-staff operator entered a vendor tenant (impersonation boundary).",
481
481
  },
482
+ "dashboard.store_provision": {
483
+ // grants/provision-store.ts — "Provision now?" from the Team invite dead-end when
484
+ // the store repo is not yet provisioned. Same near-side admission as team-invite
485
+ // (membership → merchant/dev, staff selection → admin). Args are counts/ids only.
486
+ actors: ["admin", "merchant", "dev"], source: "ui", sinks: LIFECYCLE,
487
+ argsAllow: ["resource"], renderedAllow: ["created", "repo"],
488
+ desc: "A store's forge repo was provisioned from the Team console.",
489
+ },
482
490
  "cache.purged": {
483
491
  // cache-purge.ts — bumps a tenant's edge page-cache version (the ETL load step calls
484
492
  // it per tenant post-migration). Bearer-secret service call (no session) → emitted