@tokenoftrust/cli 4.7.5 → 4.7.6
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/package.json +1 -1
- package/src/declared-config.d.mts +3 -0
- package/src/declared-config.mjs +42 -1
- package/src/validate.mjs +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.6",
|
|
4
4
|
"description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Token of Trust",
|
|
@@ -22,6 +22,9 @@ export const DEFAULT_CURRENCY: "USD";
|
|
|
22
22
|
export const CURRENCY_CODES: readonly string[];
|
|
23
23
|
export function resolveStoreCurrency(declared: unknown): string;
|
|
24
24
|
export function currencyDeclarationProblems(currency: unknown): string[];
|
|
25
|
+
export const DEFAULT_TIMEZONE: "UTC";
|
|
26
|
+
export function resolveStoreTimeZone(declared: unknown): string;
|
|
27
|
+
export function timezoneDeclarationProblems(timezone: unknown): string[];
|
|
25
28
|
export const SHIPPING_KEYS: readonly string[];
|
|
26
29
|
export const SHIPPING_TIER_KEYS: readonly string[];
|
|
27
30
|
export const MAX_SHIPPING_AMOUNT: number;
|
package/src/declared-config.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
export const TOT_CONFIG_KEYS = [
|
|
21
21
|
"$schema", "tenant", "scope", "mappings", "hostPlatform", "sample", "seed",
|
|
22
|
-
"displayName", "siteType", "compliance", "capabilities", "features", "embeds", "seo", "currency", "shipping",
|
|
22
|
+
"displayName", "siteType", "compliance", "capabilities", "features", "embeds", "seo", "currency", "timezone", "shipping",
|
|
23
23
|
];
|
|
24
24
|
// Keys other tooling has written that the platform does NOT read, with the key it does.
|
|
25
25
|
const RENAMED_CONFIG_KEYS = { appDomain: "scope", tenantId: "tenant" };
|
|
@@ -69,6 +69,42 @@ export function currencyDeclarationProblems(currency) {
|
|
|
69
69
|
: [`\`currency\` is ${JSON.stringify(currency)} — must be an uppercase ISO 4217 code such as "USD" or "CAD"`];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** The IANA time zone a store's days are counted in when `.tot/config.json` declares none. */
|
|
73
|
+
export const DEFAULT_TIMEZONE = "UTC";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* What is wrong with a declared `timezone`, one sentence; `[]` when it is valid. A valid one is
|
|
77
|
+
* an IANA zone name the runtime's Intl knows, such as "America/Los_Angeles" or "UTC"; a numeric
|
|
78
|
+
* offset is not a zone, so "today" would drift across daylight-saving changes.
|
|
79
|
+
*
|
|
80
|
+
* @param {unknown} timezone
|
|
81
|
+
* @returns {string[]}
|
|
82
|
+
*/
|
|
83
|
+
export function timezoneDeclarationProblems(timezone) {
|
|
84
|
+
const named = typeof timezone === "string" && /^[A-Za-z][A-Za-z0-9_+-]*(\/[A-Za-z0-9_+-]+)*$/.test(timezone);
|
|
85
|
+
if (named) {
|
|
86
|
+
try {
|
|
87
|
+
new Intl.DateTimeFormat("en-US", { timeZone: /** @type {string} */ (timezone) });
|
|
88
|
+
return [];
|
|
89
|
+
} catch {
|
|
90
|
+
/* not a zone the runtime knows */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [`\`timezone\` is ${JSON.stringify(timezone)} — must be an IANA time zone name such as "America/Los_Angeles" or "UTC"`];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The ONE place the time zone a store's days are counted in is resolved. A declared IANA zone
|
|
98
|
+
* wins; anything else (absent, wrong shape, unknown zone) is UTC. "Today", a daily total and an
|
|
99
|
+
* order's displayed time take the zone from here, never from the server's clock.
|
|
100
|
+
*
|
|
101
|
+
* @param {unknown} declared
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
export function resolveStoreTimeZone(declared) {
|
|
105
|
+
return timezoneDeclarationProblems(declared).length === 0 ? /** @type {string} */ (declared) : DEFAULT_TIMEZONE;
|
|
106
|
+
}
|
|
107
|
+
|
|
72
108
|
|
|
73
109
|
/** Every `seo` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
|
|
74
110
|
export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimSeoTitles", "structuredData"]);
|
|
@@ -737,6 +773,11 @@ export function validateDeclaredConfig(config, file = ".tot/config.json") {
|
|
|
737
773
|
out.push(mk(ERROR, "config-currency", file, `${problem} — the store sells in ${DEFAULT_CURRENCY} until it is fixed`));
|
|
738
774
|
}
|
|
739
775
|
}
|
|
776
|
+
if (config.timezone !== undefined) {
|
|
777
|
+
for (const problem of timezoneDeclarationProblems(config.timezone)) {
|
|
778
|
+
out.push(mk(ERROR, "config-timezone", file, `${problem} — the store counts its days in ${DEFAULT_TIMEZONE} until it is fixed`));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
740
781
|
if (config.seo !== undefined) {
|
|
741
782
|
for (const problem of seoDeclarationProblems(config.seo)) {
|
|
742
783
|
out.push(mk(ERROR, "config-seo", `${file} seo`, `${problem} — the whole \`seo\` declaration is ignored until it is fixed`));
|
package/src/validate.mjs
CHANGED
|
@@ -441,8 +441,11 @@ export const BLOCK_PALETTE_SUPPORTED_CONTRACTS = Object.freeze([
|
|
|
441
441
|
"block-palette@5",
|
|
442
442
|
"block-palette@6",
|
|
443
443
|
"block-palette@7",
|
|
444
|
+
"block-palette@8",
|
|
444
445
|
]);
|
|
445
446
|
export const FEATURED_PRODUCT_PRESENTATIONS = Object.freeze(["grid", "carousel"]);
|
|
447
|
+
export const LATEST_POSTS_DEFAULT_LIMIT = 3;
|
|
448
|
+
export const LATEST_POSTS_MAX_LIMIT = 12;
|
|
446
449
|
const SUPPORTED_BLOCK_CONTRACTS = new Set(BLOCK_PALETTE_SUPPORTED_CONTRACTS);
|
|
447
450
|
const KNOWN_BLOCKS = new Set([
|
|
448
451
|
"hero",
|
|
@@ -452,6 +455,7 @@ const KNOWN_BLOCKS = new Set([
|
|
|
452
455
|
"featured_products",
|
|
453
456
|
"editorial",
|
|
454
457
|
"newsletter",
|
|
458
|
+
"latest_posts",
|
|
455
459
|
"membership_band",
|
|
456
460
|
"marketing_hero",
|
|
457
461
|
"trust_bar",
|
|
@@ -736,6 +740,20 @@ function validateEditorial(block, file, at, out) {
|
|
|
736
740
|
}
|
|
737
741
|
}
|
|
738
742
|
|
|
743
|
+
function validateLatestPosts(block, file, at, out) {
|
|
744
|
+
if (block.limit !== undefined && (!Number.isInteger(block.limit) || block.limit < 1 || block.limit > LATEST_POSTS_MAX_LIMIT)) {
|
|
745
|
+
out.push(mk(ERROR, "block-prop", file, `${at}.limit must be a whole number from 1 to ${LATEST_POSTS_MAX_LIMIT}`));
|
|
746
|
+
}
|
|
747
|
+
for (const prop of ["eyebrow", "title", "tag", "ctaLabel", "ctaHref"]) {
|
|
748
|
+
if (block[prop] !== undefined && (typeof block[prop] !== "string" || !block[prop].trim())) {
|
|
749
|
+
out.push(mk(ERROR, "block-prop", file, `${at}.${prop} must be a non-empty string`));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (typeof block.ctaHref === "string" && block.ctaHref.trim() && !isSafeStorefrontHref(block.ctaHref)) {
|
|
753
|
+
out.push(mk(ERROR, "navigation-href", file, `${at}.ctaHref must be a same-storefront root-relative path`));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
739
757
|
function validateHomeDoc(doc, file, tenantId) {
|
|
740
758
|
const out = [];
|
|
741
759
|
if (doc == null || typeof doc !== "object" || Array.isArray(doc)) {
|
|
@@ -800,6 +818,7 @@ function validateHomeDoc(doc, file, tenantId) {
|
|
|
800
818
|
if (component === "promo_tiles") validatePromoTiles(block, file, at, out, tenantId);
|
|
801
819
|
if (component === "trust_strip") validateTrustStrip(block, file, at, out, tenantId);
|
|
802
820
|
if (component === "faq") validateFaq(block, file, at, out);
|
|
821
|
+
if (component === "latest_posts") validateLatestPosts(block, file, at, out);
|
|
803
822
|
});
|
|
804
823
|
return out;
|
|
805
824
|
}
|