@tokenoftrust/storefront-runner 2.2.85 → 2.2.87
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/agency-kit/tools/lib/redirect-declaration.mjs +627 -0
- package/apps/storefront/src/config/declaredTenantConfig.ts +94 -0
- package/apps/storefront/src/middleware/renderChain.ts +14 -3
- package/package.json +1 -1
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/declared-tenant-config.d.mts +22 -0
- package/packages/public-runtime/src/declared-tenant-config.mjs +150 -0
- package/packages/public-runtime/src/publication-redirects.d.mts +23 -4
- package/packages/public-runtime/src/publication-redirects.mjs +4 -378
- package/packages/public-runtime/src/static-bundle.ts +59 -11
- package/packages/public-runtime/tests/fixtures/redirect-match-cases.json +34 -0
- package/scripts/dev/port-lock.mjs +109 -0
- package/scripts/dev/standalone-graft-plan.mjs +35 -0
- package/scripts/tot-dev.mjs +49 -20
package/scripts/tot-dev.mjs
CHANGED
|
@@ -63,8 +63,10 @@ import { dirname, resolve, join } from "node:path";
|
|
|
63
63
|
import { fileURLToPath } from "node:url";
|
|
64
64
|
import { resolveDevSourceRoot } from "./dev/tenant-source-root.mjs";
|
|
65
65
|
import { tenantDirRelative, tenantDirSegments } from "./lib/tenant-dirs.mjs";
|
|
66
|
-
import { firstFreePort } from "./dev/port-check.mjs";
|
|
66
|
+
import { firstFreePort, isPortFree } from "./dev/port-check.mjs";
|
|
67
|
+
import { busyPortMessage, classifyBusyPort, lockPort, unlockPort } from "./dev/port-lock.mjs";
|
|
67
68
|
import { resync, startWatch } from "./dev/checkout-watch.mjs";
|
|
69
|
+
import { standaloneGraftPlan } from "./dev/standalone-graft-plan.mjs";
|
|
68
70
|
import { findMissingCtaIds } from "./tenant/validate.mjs";
|
|
69
71
|
|
|
70
72
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -273,25 +275,18 @@ if (workspacePath) {
|
|
|
273
275
|
console.log(`\n tot dev — standalone (${tenant})`);
|
|
274
276
|
console.log(` checkout: ${wsRoot}`);
|
|
275
277
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// structurally (ignoring any legacy `repo` field in the checkout's .tot/config.json).
|
|
283
|
-
const wsRel = String(map.workspace ?? "").replace(/\/+$/, "");
|
|
284
|
-
if (!wsRel) continue;
|
|
278
|
+
/**
|
|
279
|
+
* Graft one checkout path (`wsRel`, checkout-relative) into the in-tree
|
|
280
|
+
* `tenants/<tenant>/` layout the app globs, with the same move-aside/restore +
|
|
281
|
+
* watch-and-resync machinery every mapping gets. Returns true if grafted.
|
|
282
|
+
*/
|
|
283
|
+
function graftOne(wsRel, isDir) {
|
|
285
284
|
const repoRel = join(tenantDirRelative(tenant), wsRel);
|
|
286
285
|
|
|
287
286
|
const source = resolve(wsRoot, wsRel); // real files in the checkout
|
|
288
287
|
const target = resolve(repoRoot, repoRel); // where the app globs them
|
|
289
|
-
const isDir = map.kind !== "file";
|
|
290
288
|
|
|
291
|
-
if (!existsSync(source))
|
|
292
|
-
console.warn(` ⚠ checkout has no ${wsRel} — skipping (${map.kind ?? "?"})`);
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
289
|
+
if (!existsSync(source)) return false;
|
|
295
290
|
|
|
296
291
|
// Ensure the destination's parent exists (e.g. content/ or themes/tenants/).
|
|
297
292
|
mkdirSync(dirname(target), { recursive: true });
|
|
@@ -322,9 +317,31 @@ if (workspacePath) {
|
|
|
322
317
|
}
|
|
323
318
|
|
|
324
319
|
console.log(` ↪ ${repoRel} ⇐ ${wsRel}${backup ? " (in-tree content backed up)" : ""}`);
|
|
320
|
+
return true;
|
|
325
321
|
}
|
|
326
322
|
|
|
327
|
-
|
|
323
|
+
// `workspace` is the checkout-relative prefix (e.g. "content/", "theme.json").
|
|
324
|
+
// COLOCATION: a tenant IS the repo-root dir `tenants/<id>/`, and the checkout
|
|
325
|
+
// (a `git subtree split --prefix=tenants/<id>` of that dir) has the SAME layout
|
|
326
|
+
// at its root — so the graft is an identity map: content/ -> tenants/<id>/content/,
|
|
327
|
+
// public/ -> tenants/<id>/public/, theme.json -> tenants/<id>/theme.json. We map
|
|
328
|
+
// structurally (ignoring any legacy `repo` field in the checkout's .tot/config.json).
|
|
329
|
+
// standaloneGraftPlan ALSO appends `.tot/config.json` unconditionally — see its
|
|
330
|
+
// own doc comment. Its success does NOT count toward "did we graft a usable
|
|
331
|
+
// site" below: a checkout with config but no content/public/theme is still
|
|
332
|
+
// nothing to serve.
|
|
333
|
+
let mappedCount = 0;
|
|
334
|
+
for (const { workspace, isDir } of standaloneGraftPlan(mappings)) {
|
|
335
|
+
const grafted = graftOne(workspace, isDir);
|
|
336
|
+
if (workspace === ".tot/config.json") {
|
|
337
|
+
if (!grafted) console.warn(` ⚠ checkout has no .tot/config.json — compliance/capabilities/siteType resolve undeclared.`);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (grafted) mappedCount++;
|
|
341
|
+
else console.warn(` ⚠ checkout has no ${workspace} — skipping`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (mappedCount === 0) {
|
|
328
345
|
console.error(`✗ nothing to graft — the checkout had none of the mapped prefixes.`);
|
|
329
346
|
cleanupGrafts();
|
|
330
347
|
process.exit(1);
|
|
@@ -354,12 +371,19 @@ if (workspacePath) {
|
|
|
354
371
|
// which then mismatches the URL we (and the CLI's liveness poll) advertise.
|
|
355
372
|
// Instead we pick the first free port ourselves and PRINT it, so a busy default
|
|
356
373
|
// port "just works" (better UX than failing). See port-check.mjs.
|
|
374
|
+
//
|
|
375
|
+
// BEFORE drifting, classify WHY the requested port is busy (port-lock.mjs). When
|
|
376
|
+
// an earlier `tot dev` session holds it — still running but possibly wedged after
|
|
377
|
+
// a renderer crash, or exited with the socket lingering — the developer's browser
|
|
378
|
+
// tab on that port is NOT this session, so say so and name the process to stop.
|
|
357
379
|
const requestedPort = Number(flag("port", "4321"));
|
|
380
|
+
const busy = await classifyBusyPort(requestedPort, { isFree: isPortFree });
|
|
358
381
|
const resolvedPort = await firstFreePort(requestedPort);
|
|
359
382
|
const port = String(resolvedPort);
|
|
360
383
|
if (resolvedPort !== requestedPort) {
|
|
361
|
-
console.log(
|
|
384
|
+
console.log(busyPortMessage(requestedPort, resolvedPort, busy));
|
|
362
385
|
}
|
|
386
|
+
lockPort(resolvedPort);
|
|
363
387
|
|
|
364
388
|
// Land the developer on their store ROOT (plain save→reload HMR against their own
|
|
365
389
|
// site) — not the /dev cockpit, which is a hosted-only surface excluded from the
|
|
@@ -429,15 +453,20 @@ const child = spawn(
|
|
|
429
453
|
{ cwd: appDir, stdio: "inherit", env: spawnEnv },
|
|
430
454
|
);
|
|
431
455
|
|
|
432
|
-
// Clean up grafts on every exit path so we never leave
|
|
456
|
+
// Clean up grafts + the port lock on every exit path so we never leave
|
|
457
|
+
// symlinks in the tree, or a lock a LATER run would misread as still live.
|
|
433
458
|
function shutdown(signal) {
|
|
434
459
|
child.kill(signal);
|
|
435
460
|
}
|
|
436
|
-
|
|
461
|
+
function cleanupAll() {
|
|
437
462
|
cleanupGrafts();
|
|
463
|
+
unlockPort(resolvedPort);
|
|
464
|
+
}
|
|
465
|
+
child.on("exit", (code) => {
|
|
466
|
+
cleanupAll();
|
|
438
467
|
process.exit(code ?? 0);
|
|
439
468
|
});
|
|
440
469
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
441
470
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
442
471
|
// Belt-and-suspenders: if the parent dies without the child exit firing.
|
|
443
|
-
process.on("exit",
|
|
472
|
+
process.on("exit", cleanupAll);
|