@tokenoftrust/storefront-runner 2.0.46 → 2.0.47

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.
@@ -15,7 +15,6 @@ import { readFile } from "node:fs/promises";
15
15
  // normalized to match Vite's ctx.file (Vite always reports posix-style paths).
16
16
  const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
17
17
  const TENANTS_DIR = join(REPO_ROOT, "tenants").replace(/\\/g, "/");
18
- const PUBLIC_TENANTS_DEST = fileURLToPath(new URL("../public/tenants", import.meta.url));
19
18
 
20
19
  /** Per-file finding signatures, so an unchanged warning is not reprinted on every
21
20
  * keystroke-save. Lives for the dev server's lifetime. */
@@ -58,12 +57,13 @@ async function loadTsModule(server, url) {
58
57
  * @param {string} file
59
58
  * @returns {{ id: string, rest: string } | null}
60
59
  */
61
- export function classifyTenantFile(file) {
60
+ export function classifyTenantFile(file, tenantsDir = TENANTS_DIR) {
62
61
  const f = String(file).replace(/\\/g, "/");
63
- if (!f.startsWith(TENANTS_DIR + "/")) return null;
62
+ const normalizedTenantsDir = String(tenantsDir).replace(/\\/g, "/");
63
+ if (!f.startsWith(normalizedTenantsDir + "/")) return null;
64
64
  // "<id>/<...>" for a real tenant, "e2e/<id>/<...>" for a platform test tenant —
65
65
  // consume the nesting segment so `id` is always the tenant's own id.
66
- let rel = f.slice(TENANTS_DIR.length + 1);
66
+ let rel = f.slice(normalizedTenantsDir.length + 1);
67
67
  if (rel.startsWith(E2E_TENANT_DIR + "/")) rel = rel.slice(E2E_TENANT_DIR.length + 1);
68
68
  const slash = rel.indexOf("/");
69
69
  if (slash < 0) return null;
@@ -102,23 +102,31 @@ export function stripThemeMeta(parsed) {
102
102
  * save mirroring into public/tenants/<id>/.
103
103
  * @param {string} id
104
104
  */
105
- export function isVersionedTenant(id) {
105
+ export function isVersionedTenant(id, root = REPO_ROOT) {
106
106
  const envList = (process.env.TENANT_ASSETS_VERSIONED ?? "")
107
107
  .split(",")
108
108
  .map((s) => s.trim())
109
109
  .filter(Boolean);
110
110
  if (envList.includes(id)) return true;
111
- return existsSync(join(REPO_ROOT, ...tenantDirSegments(id), ".tot", "assets-versioned"));
111
+ return existsSync(join(root, ...tenantDirSegments(id), ".tot", "assets-versioned"));
112
112
  }
113
113
 
114
114
  /**
115
115
  * Re-mirror one changed/added public asset into public/tenants/<id>/ (skip R2-versioned).
116
116
  * @param {string} id @param {string} rest @param {string} sourceFile
117
117
  */
118
- async function mirrorPublicAsset(id, rest, sourceFile) {
119
- if (isVersionedTenant(id)) return;
118
+ async function mirrorPublicAsset(
119
+ id,
120
+ rest,
121
+ sourceFile,
122
+ {
123
+ root = REPO_ROOT,
124
+ publicTenantsDest = join(root, "apps", "storefront", "public", "tenants"),
125
+ } = {},
126
+ ) {
127
+ if (isVersionedTenant(id, root)) return;
120
128
  const { cp, mkdir } = await import("node:fs/promises");
121
- const target = join(PUBLIC_TENANTS_DEST, id, rest.slice("public/".length));
129
+ const target = join(publicTenantsDest, id, rest.slice("public/".length));
122
130
  try {
123
131
  await mkdir(dirname(target), { recursive: true });
124
132
  await cp(sourceFile, target);
@@ -131,10 +139,17 @@ async function mirrorPublicAsset(id, rest, sourceFile) {
131
139
  * Remove the stale mirrored copy under public/tenants/<id>/ when a public asset is deleted.
132
140
  * @param {string} id @param {string} rest
133
141
  */
134
- async function unmirrorPublicAsset(id, rest) {
135
- if (isVersionedTenant(id)) return;
142
+ async function unmirrorPublicAsset(
143
+ id,
144
+ rest,
145
+ {
146
+ root = REPO_ROOT,
147
+ publicTenantsDest = join(root, "apps", "storefront", "public", "tenants"),
148
+ } = {},
149
+ ) {
150
+ if (isVersionedTenant(id, root)) return;
136
151
  const { rm } = await import("node:fs/promises");
137
- const target = join(PUBLIC_TENANTS_DEST, id, rest.slice("public/".length));
152
+ const target = join(publicTenantsDest, id, rest.slice("public/".length));
138
153
  try {
139
154
  await rm(target, { force: true });
140
155
  } catch {
@@ -194,7 +209,11 @@ export function seedKnown(root, known) {
194
209
  // the worker teardown, same as the change path). Public assets are NOT in any
195
210
  // graph, so Vite ignores their add/unlink entirely — we mirror (add) / un-mirror
196
211
  // (unlink) and reload ourselves. loopState.lastEvent then reflects add/unlink.
197
- export function tenantHotReload() {
212
+ export function tenantHotReload({
213
+ root = REPO_ROOT,
214
+ publicTenantsDest = join(root, "apps", "storefront", "public", "tenants"),
215
+ } = {}) {
216
+ const tenantsDir = join(root, "tenants").replace(/\\/g, "/");
198
217
  // Typed `any`: `vite` isn't a direct dependency of this package (it's pulled in
199
218
  // transitively via astro), so `import('vite').Plugin` can't be resolved here and
200
219
  // importing from "vite" would even fail at runtime. `any` keeps the hook params
@@ -205,7 +224,7 @@ export function tenantHotReload() {
205
224
  apply: "serve",
206
225
  /** @param {any} server */
207
226
  configureServer(server) {
208
- server.watcher.add(TENANTS_DIR);
227
+ server.watcher.add(tenantsDir);
209
228
 
210
229
  // Tell the hosted /dev panel the store is UP the moment the dev server is
211
230
  // listening — completes Step 1 ("Get running") before any edit. Best-effort,
@@ -218,12 +237,12 @@ export function tenantHotReload() {
218
237
  // really added/removed so the guard stays accurate for the session.
219
238
  /** @type {Set<string>} */
220
239
  const known = new Set();
221
- seedKnown(TENANTS_DIR, known);
240
+ seedKnown(tenantsDir, known);
222
241
 
223
242
  /** @param {"add"|"unlink"} event @param {string} rawPath */
224
243
  const onAddUnlink = async (event, rawPath) => {
225
244
  const file = String(rawPath).replace(/\\/g, "/");
226
- const c = classifyTenantFile(file);
245
+ const c = classifyTenantFile(file, tenantsDir);
227
246
  if (!c) return;
228
247
  const { id, rest } = c;
229
248
 
@@ -244,8 +263,11 @@ export function tenantHotReload() {
244
263
  // Public assets: Vite doesn't react (not in the module graph). Maintain the
245
264
  // mirror ourselves, then reload (server.ws.send is timestamped by the Monitor).
246
265
  if (rest.startsWith("public/")) {
247
- if (event === "add") await mirrorPublicAsset(id, rest, file);
248
- else await unmirrorPublicAsset(id, rest);
266
+ if (event === "add") {
267
+ await mirrorPublicAsset(id, rest, file, { root, publicTenantsDest });
268
+ } else {
269
+ await unmirrorPublicAsset(id, rest, { root, publicTenantsDest });
270
+ }
249
271
  server.ws.send({ type: "full-reload" });
250
272
  return;
251
273
  }
@@ -259,7 +281,7 @@ export function tenantHotReload() {
259
281
  },
260
282
  /** @param {any} ctx */
261
283
  async handleHotUpdate(ctx) {
262
- const c = classifyTenantFile(ctx.file);
284
+ const c = classifyTenantFile(ctx.file, tenantsDir);
263
285
  if (!c) return undefined;
264
286
  const { id, rest } = c;
265
287
 
@@ -276,7 +298,7 @@ export function tenantHotReload() {
276
298
  // terminal a beat later. Putting this in the reload path would tax every
277
299
  // save to report something that is almost always empty.
278
300
  void validateSavedFile({
279
- tenantDir: join(REPO_ROOT, tenantDirRelative(id)),
301
+ tenantDir: join(root, tenantDirRelative(id)),
280
302
  tenantId: id,
281
303
  rest,
282
304
  memo: savedFileFindings,
@@ -301,7 +323,10 @@ export function tenantHotReload() {
301
323
  // Unlike content HTML this reloads no worker, so there's no teardown race to
302
324
  // fire ahead of. Skip R2-versioned tenants, like copy-tenant-assets.mjs.
303
325
  if (rest.startsWith("public/")) {
304
- await mirrorPublicAsset(id, rest, ctx.file.replace(/\\/g, "/"));
326
+ await mirrorPublicAsset(id, rest, ctx.file.replace(/\\/g, "/"), {
327
+ root,
328
+ publicTenantsDest,
329
+ });
305
330
  ctx.server.ws.send({ type: "full-reload" });
306
331
  return [];
307
332
  }
@@ -19,7 +19,7 @@ import {
19
19
  * STATIC publish (materialize-tenant.mjs) sets this to the single tenant it renders,
20
20
  * so a sibling tenant can never block it. Unset ⇒ framework-only Worker build.
21
21
  */
22
- export function materializeGuard() {
22
+ export function materializeGuard({ root } = {}) {
23
23
  return {
24
24
  name: "materialize-guard",
25
25
  hooks: {
@@ -40,7 +40,7 @@ export function materializeGuard() {
40
40
  // set (not "all tenants", which `null` would mean), so a sibling
41
41
  // tenant that is present-but-stale can never block this build.
42
42
  const frameworkOnlyTenants = [];
43
- const { problems } = checkPinned(undefined, { expectRef, allowUnpinned, onlyTenants: frameworkOnlyTenants });
43
+ const { problems } = checkPinned(root, { expectRef, allowUnpinned, onlyTenants: frameworkOnlyTenants });
44
44
  const absent = problems.filter((p) => p.reason === "absent");
45
45
  const real = problems.filter((p) => p.reason !== "absent");
46
46
  if (absent.length) {
@@ -49,11 +49,11 @@ export function materializeGuard() {
49
49
  else console.log(msg);
50
50
  }
51
51
  if (real.length) {
52
- assertPinnedMaterialized({ expectRef, allowUnpinned, onlyTenants: frameworkOnlyTenants });
52
+ assertPinnedMaterialized({ root, expectRef, allowUnpinned, onlyTenants: frameworkOnlyTenants });
53
53
  }
54
54
  return;
55
55
  }
56
- assertPinnedMaterialized({ expectRef, allowUnpinned, onlyTenants });
56
+ assertPinnedMaterialized({ root, expectRef, allowUnpinned, onlyTenants });
57
57
  },
58
58
  },
59
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.0.46",
3
+ "version": "2.0.47",
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",