@tokenoftrust/storefront-runner 4.7.10 → 4.8.1
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.config.mjs +2 -1
- package/apps/storefront/dev-plugins/dev-auth-outbox.mjs +181 -0
- package/apps/storefront/src/components/content/storefront/blog/PostGrid.astro +8 -4
- package/apps/storefront/src/lib/access/auth/devAuth.ts +34 -2
- package/apps/storefront/src/lib/access/auth/route.ts +36 -2
- package/apps/storefront/src/lib/access/auth/totAccessClient.ts +6 -18
- package/apps/storefront/src/lib/access/dashboard/staffStoreEntry.ts +2 -1
- package/apps/storefront/src/lib/access/dashboard/staffVendorAccess.ts +6 -10
- package/apps/storefront/src/lib/access/shared/accessExplain.ts +106 -28
- package/apps/storefront/src/lib/access/shared/developerInvites.ts +40 -38
- package/apps/storefront/src/lib/access/shared/invitePreflight.ts +1 -1
- package/apps/storefront/src/lib/access/shared/principalGrants.ts +16 -17
- package/apps/storefront/src/lib/content/shared/homeVisualParity.ts +69 -0
- package/apps/storefront/src/lib/content/storyblok/content-model.ts +21 -0
- package/apps/storefront/src/lib/engagement/messaging/messagesClient.ts +13 -7
- package/apps/storefront/src/lib/observability/diag/failureReason.ts +37 -0
- package/apps/storefront/src/lib/observability/diag/redact.ts +11 -14
- package/apps/storefront/src/lib/orders/admin/orderSource.ts +26 -5
- package/apps/storefront/src/lib/orders/admin/services/orders.ts +121 -22
- package/apps/storefront/src/lib/orders/admin/viewmodels/orders.ts +7 -0
- package/apps/storefront/src/lib/orders/fixtures/orders.ts +2 -0
- package/apps/storefront/src/pages/api/auth/magic-exchange.ts +4 -7
- package/apps/storefront/src/pages/api/auth/send.ts +12 -12
- package/apps/storefront/src/pages/api/auth/step-up-verify.ts +2 -1
- package/apps/storefront/src/pages/api/auth/verify.ts +7 -3
- package/apps/storefront/src/pages/auth/login.astro +24 -6
- package/apps/storefront/src/pages/auth/magic.astro +4 -7
- package/apps/storefront/src/pages/index.astro +43 -1
- package/package.json +1 -1
- package/packages/cli/src/declared-config.mjs +42 -1
- package/packages/public-runtime/src/declared-tenant-config.d.mts +4 -1
- package/packages/public-runtime/src/declared-tenant-config.mjs +40 -1
- package/packages/public-runtime/src/extension-contract-values.d.mts +4 -1
- package/packages/public-runtime/src/extension-contract-values.mjs +6 -1
- package/packages/public-runtime/src/extension-contract.ts +2 -0
- package/packages/public-runtime/src/tenant.ts +6 -0
- package/scripts/tenant/validate.mjs +17 -0
|
@@ -27,6 +27,7 @@ export const DECLARED_TENANT_CONFIG_FIELDS = Object.freeze([
|
|
|
27
27
|
"embeds",
|
|
28
28
|
"seo",
|
|
29
29
|
"currency",
|
|
30
|
+
"timezone",
|
|
30
31
|
"shipping",
|
|
31
32
|
]);
|
|
32
33
|
|
|
@@ -149,6 +150,42 @@ export function currencyDeclarationProblems(currency) {
|
|
|
149
150
|
: [`\`currency\` is ${JSON.stringify(currency)} — must be an uppercase ISO 4217 code such as "USD" or "CAD"`];
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
/** The IANA time zone a store's days are counted in when `.tot/config.json` declares none. */
|
|
154
|
+
export const DEFAULT_TIMEZONE = "UTC";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* What is wrong with a declared `timezone`, one sentence; `[]` when it is valid. A valid one is
|
|
158
|
+
* an IANA zone name the runtime's Intl knows, such as "America/Los_Angeles" or "UTC"; a numeric
|
|
159
|
+
* offset is not a zone, so "today" would drift across daylight-saving changes.
|
|
160
|
+
*
|
|
161
|
+
* @param {unknown} timezone
|
|
162
|
+
* @returns {string[]}
|
|
163
|
+
*/
|
|
164
|
+
export function timezoneDeclarationProblems(timezone) {
|
|
165
|
+
const named = typeof timezone === "string" && /^[A-Za-z][A-Za-z0-9_+-]*(\/[A-Za-z0-9_+-]+)*$/.test(timezone);
|
|
166
|
+
if (named) {
|
|
167
|
+
try {
|
|
168
|
+
new Intl.DateTimeFormat("en-US", { timeZone: /** @type {string} */ (timezone) });
|
|
169
|
+
return [];
|
|
170
|
+
} catch {
|
|
171
|
+
/* not a zone the runtime knows */
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return [`\`timezone\` is ${JSON.stringify(timezone)} — must be an IANA time zone name such as "America/Los_Angeles" or "UTC"`];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The ONE place the time zone a store's days are counted in is resolved. A declared IANA zone
|
|
179
|
+
* wins; anything else (absent, wrong shape, unknown zone) is UTC. "Today", a daily total and an
|
|
180
|
+
* order's displayed time take the zone from here, never from the server's clock.
|
|
181
|
+
*
|
|
182
|
+
* @param {unknown} declared
|
|
183
|
+
* @returns {string}
|
|
184
|
+
*/
|
|
185
|
+
export function resolveStoreTimeZone(declared) {
|
|
186
|
+
return timezoneDeclarationProblems(declared).length === 0 ? /** @type {string} */ (declared) : DEFAULT_TIMEZONE;
|
|
187
|
+
}
|
|
188
|
+
|
|
152
189
|
const SITE_TYPES = new Set(SITE_TYPES_LIST);
|
|
153
190
|
|
|
154
191
|
const isPlainObject = (value) =>
|
|
@@ -343,7 +380,7 @@ export function declaredTenantConfig(config) {
|
|
|
343
380
|
/** @type {Record<string, unknown>} */
|
|
344
381
|
const out = {};
|
|
345
382
|
if (!isPlainObject(config)) return out;
|
|
346
|
-
const { displayName, siteType, compliance, capabilities, features, embeds, seo, currency, shipping } = /** @type {Record<string, unknown>} */ (config);
|
|
383
|
+
const { displayName, siteType, compliance, capabilities, features, embeds, seo, currency, timezone, shipping } = /** @type {Record<string, unknown>} */ (config);
|
|
347
384
|
// The store's name as visitors read it: the `<title>` suffix, og:site_name,
|
|
348
385
|
// Organization JSON-LD, breadcrumbs, the default blog heading, the copyright line.
|
|
349
386
|
if (typeof displayName === "string" && displayName.trim()) out.displayName = displayName.trim();
|
|
@@ -356,6 +393,8 @@ export function declaredTenantConfig(config) {
|
|
|
356
393
|
if (seo !== undefined && seoDeclarationProblems(seo).length === 0) out.seo = seo;
|
|
357
394
|
// Wrong-shaped or unknown codes are omitted: the store sells in USD (`tot validate` reports it).
|
|
358
395
|
if (currency !== undefined && currencyDeclarationProblems(currency).length === 0) out.currency = currency;
|
|
396
|
+
// Wrong-shaped or unknown zones are omitted: the store counts its days in UTC (`tot validate` reports it).
|
|
397
|
+
if (timezone !== undefined && timezoneDeclarationProblems(timezone).length === 0) out.timezone = timezone;
|
|
359
398
|
// The shipping charge: refused whole when any part is malformed (`tot validate` and the
|
|
360
399
|
// reconcile report it), so checkout keeps charging no shipping rather than a guess.
|
|
361
400
|
if (shipping !== undefined && shippingDeclarationProblems(shipping).length === 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const BLOCK_PALETTE_CURRENT_CONTRACT: "block-palette@
|
|
1
|
+
export declare const BLOCK_PALETTE_CURRENT_CONTRACT: "block-palette@8";
|
|
2
2
|
export declare const BLOCK_PALETTE_SUPPORTED_CONTRACTS: readonly [
|
|
3
3
|
"block-palette@1",
|
|
4
4
|
"block-palette@2",
|
|
@@ -7,8 +7,11 @@ export declare const BLOCK_PALETTE_SUPPORTED_CONTRACTS: readonly [
|
|
|
7
7
|
"block-palette@5",
|
|
8
8
|
"block-palette@6",
|
|
9
9
|
"block-palette@7",
|
|
10
|
+
"block-palette@8",
|
|
10
11
|
];
|
|
11
12
|
export declare const FEATURED_PRODUCT_PRESENTATIONS: readonly ["grid", "carousel"];
|
|
13
|
+
export declare const LATEST_POSTS_DEFAULT_LIMIT: 3;
|
|
14
|
+
export declare const LATEST_POSTS_MAX_LIMIT: 12;
|
|
12
15
|
export declare const PROMO_TILE_PRESENTATIONS: readonly [
|
|
13
16
|
"overlay",
|
|
14
17
|
"portrait-cards",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* runtime and repository Node tooling. The separately published CLI mirrors
|
|
6
6
|
* these values and pins that mirror with a parity test.
|
|
7
7
|
*/
|
|
8
|
-
export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@
|
|
8
|
+
export const BLOCK_PALETTE_CURRENT_CONTRACT = "block-palette@8";
|
|
9
9
|
|
|
10
10
|
export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = Object.freeze([
|
|
11
11
|
"block-palette@1",
|
|
@@ -14,11 +14,16 @@ export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = Object.freeze([
|
|
|
14
14
|
"block-palette@4",
|
|
15
15
|
"block-palette@5",
|
|
16
16
|
"block-palette@6",
|
|
17
|
+
"block-palette@7",
|
|
17
18
|
BLOCK_PALETTE_CURRENT_CONTRACT,
|
|
18
19
|
]);
|
|
19
20
|
|
|
20
21
|
export const FEATURED_PRODUCT_PRESENTATIONS = Object.freeze(["grid", "carousel"]);
|
|
21
22
|
|
|
23
|
+
/** `latest_posts` card count: the default when `limit` is absent, and the most a block may show. */
|
|
24
|
+
export const LATEST_POSTS_DEFAULT_LIMIT = 3;
|
|
25
|
+
export const LATEST_POSTS_MAX_LIMIT = 12;
|
|
26
|
+
|
|
22
27
|
export const PROMO_TILE_PRESENTATIONS = Object.freeze([
|
|
23
28
|
"overlay",
|
|
24
29
|
"portrait-cards",
|
|
@@ -524,6 +524,12 @@ export interface TenantConfig {
|
|
|
524
524
|
* (declared-tenant-config.mjs), never as a literal.
|
|
525
525
|
*/
|
|
526
526
|
currency: string;
|
|
527
|
+
/**
|
|
528
|
+
* The IANA time zone the store's days are counted in: the Orders "Today" total and the order
|
|
529
|
+
* times the admin shows. UTC unless `.tot/config.json` declares another; read it through
|
|
530
|
+
* `resolveStoreTimeZone` (declared-tenant-config.mjs), never as a literal.
|
|
531
|
+
*/
|
|
532
|
+
timezone?: string;
|
|
527
533
|
locale: string;
|
|
528
534
|
/** Explicit publication spaces for this tenant, including one canonical root. */
|
|
529
535
|
publicationSpaces?: readonly PublicationSpaceManifestV1[];
|
|
@@ -34,6 +34,7 @@ import { listTenantDirIds, tenantDirSegments } from "../lib/tenant-dirs.mjs";
|
|
|
34
34
|
import {
|
|
35
35
|
BLOCK_PALETTE_SUPPORTED_CONTRACTS,
|
|
36
36
|
FEATURED_PRODUCT_PRESENTATIONS,
|
|
37
|
+
LATEST_POSTS_MAX_LIMIT,
|
|
37
38
|
PROMO_TILE_PRESENTATIONS,
|
|
38
39
|
THEME_HEADING_LEVELS,
|
|
39
40
|
isPageBlockImageReference,
|
|
@@ -364,6 +365,7 @@ const KNOWN_BLOCKS = new Set([
|
|
|
364
365
|
"membership_band",
|
|
365
366
|
"editorial",
|
|
366
367
|
"newsletter",
|
|
368
|
+
"latest_posts",
|
|
367
369
|
"marketing_hero",
|
|
368
370
|
"trust_bar",
|
|
369
371
|
"split_compare",
|
|
@@ -584,6 +586,20 @@ function validateEditorial(block, file, at, out) {
|
|
|
584
586
|
}
|
|
585
587
|
}
|
|
586
588
|
|
|
589
|
+
function validateLatestPosts(block, file, at, out) {
|
|
590
|
+
if (block.limit !== undefined && (!Number.isInteger(block.limit) || block.limit < 1 || block.limit > LATEST_POSTS_MAX_LIMIT)) {
|
|
591
|
+
out.push(mk(ERROR, "block-prop", file, `${at}.limit must be a whole number from 1 to ${LATEST_POSTS_MAX_LIMIT}`));
|
|
592
|
+
}
|
|
593
|
+
for (const prop of ["eyebrow", "title", "tag", "ctaLabel", "ctaHref"]) {
|
|
594
|
+
if (block[prop] !== undefined && (typeof block[prop] !== "string" || !block[prop].trim())) {
|
|
595
|
+
out.push(mk(ERROR, "block-prop", file, `${at}.${prop} must be a non-empty string`));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (typeof block.ctaHref === "string" && block.ctaHref.trim() && !isSafeStorefrontHref(block.ctaHref)) {
|
|
599
|
+
out.push(mk(ERROR, "navigation-href", file, `${at}.ctaHref must be a same-storefront root-relative path`));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
587
603
|
function validateHomeDoc(doc, file, tenantId) {
|
|
588
604
|
const out = [];
|
|
589
605
|
if (doc == null || typeof doc !== "object" || Array.isArray(doc)) {
|
|
@@ -648,6 +664,7 @@ function validateHomeDoc(doc, file, tenantId) {
|
|
|
648
664
|
if (component === "promo_tiles") validatePromoTiles(block, file, at, out, tenantId);
|
|
649
665
|
if (component === "trust_strip") validateTrustStrip(block, file, at, out, tenantId);
|
|
650
666
|
if (component === "faq") validateFaq(block, file, at, out);
|
|
667
|
+
if (component === "latest_posts") validateLatestPosts(block, file, at, out);
|
|
651
668
|
});
|
|
652
669
|
return out;
|
|
653
670
|
}
|