@tokenoftrust/cli 3.0.2 → 3.0.4
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 +7 -0
- package/src/declared-config.mjs +93 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
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",
|
|
@@ -18,6 +18,13 @@ export const STRUCTURED_DATA_NODE_TOKENS: readonly string[];
|
|
|
18
18
|
export const STRUCTURED_DATA_NAMED_TYPES: readonly string[];
|
|
19
19
|
export const STRUCTURED_DATA_NAME_SOURCES: readonly string[];
|
|
20
20
|
export function seoDeclarationProblems(seo: unknown): string[];
|
|
21
|
+
export const DEFAULT_CURRENCY: "USD";
|
|
22
|
+
export const CURRENCY_CODES: readonly string[];
|
|
23
|
+
export function resolveStoreCurrency(declared: unknown): string;
|
|
24
|
+
export function currencyDeclarationProblems(currency: unknown): string[];
|
|
25
|
+
export const SHIPPING_KEYS: readonly string[];
|
|
26
|
+
export const MAX_SHIPPING_AMOUNT: number;
|
|
27
|
+
export function shippingDeclarationProblems(shipping: unknown): string[];
|
|
21
28
|
export const COMPLIANCE_NOTICES: Record<string, string>;
|
|
22
29
|
export const MANDATED_NOTICE_KEYS: string[];
|
|
23
30
|
export function nearestKey(key: string, known: readonly string[]): string | null;
|
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",
|
|
22
|
+
"displayName", "siteType", "compliance", "capabilities", "features", "embeds", "seo", "currency", "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" };
|
|
@@ -32,6 +32,44 @@ export const FEATURE_KEYS = [
|
|
|
32
32
|
];
|
|
33
33
|
export const SITE_TYPES = ["commerce", "marketing"];
|
|
34
34
|
|
|
35
|
+
/** The currency a store sells in when `.tot/config.json` declares none. */
|
|
36
|
+
export const DEFAULT_CURRENCY = "USD";
|
|
37
|
+
|
|
38
|
+
/** ISO 4217 codes a store may declare as its `currency`. */
|
|
39
|
+
export const CURRENCY_CODES = Object.freeze(
|
|
40
|
+
("AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF BMD BND BOB BRL BSD BTN BWP BYN BZD CAD CDF CHF CLP CNY COP " +
|
|
41
|
+
"CRC CUP CVE CZK DJF DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GHS GIP GMD GNF GTQ GYD HKD HNL HTG HUF IDR ILS INR IQD IRR " +
|
|
42
|
+
"ISK JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK LBP LKR LRD LSL LYD MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN " +
|
|
43
|
+
"MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SDG SEK SGD SHP SLE SOS SRD " +
|
|
44
|
+
"SSP STN SVC SYP SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VES VND VUV WST XAF XCD XCG XOF XPF YER ZAR ZMW ZWG").split(" "),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The ONE place a store's currency is resolved. A declared ISO 4217 code wins; anything
|
|
49
|
+
* else (absent, wrong shape, lowercase, unknown code) is USD. Every surface that prices,
|
|
50
|
+
* signs or formats an amount takes the currency from here, never from a literal. Only the
|
|
51
|
+
* store owner's `.tot/config.json` can change it: a shopper's request never reaches this.
|
|
52
|
+
*
|
|
53
|
+
* @param {unknown} declared
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function resolveStoreCurrency(declared) {
|
|
57
|
+
return typeof declared === "string" && CURRENCY_CODES.includes(declared) ? declared : DEFAULT_CURRENCY;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* What is wrong with a declared `currency`, one sentence; `[]` when it is valid.
|
|
62
|
+
*
|
|
63
|
+
* @param {unknown} currency
|
|
64
|
+
* @returns {string[]}
|
|
65
|
+
*/
|
|
66
|
+
export function currencyDeclarationProblems(currency) {
|
|
67
|
+
return typeof currency === "string" && CURRENCY_CODES.includes(currency)
|
|
68
|
+
? []
|
|
69
|
+
: [`\`currency\` is ${JSON.stringify(currency)} — must be an uppercase ISO 4217 code such as "USD" or "CAD"`];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
35
73
|
/** Every `seo` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
|
|
36
74
|
export const SEO_KEYS = Object.freeze(["trailingSlash", "verbatimSeoTitles", "structuredData"]);
|
|
37
75
|
|
|
@@ -158,6 +196,46 @@ export function seoDeclarationProblems(seo) {
|
|
|
158
196
|
return problems;
|
|
159
197
|
}
|
|
160
198
|
|
|
199
|
+
/** Every `shipping` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
|
|
200
|
+
export const SHIPPING_KEYS = Object.freeze(["flatRate", "freeAtOrAbove"]);
|
|
201
|
+
|
|
202
|
+
/** Largest shipping amount a declaration may state, in the cart's currency. */
|
|
203
|
+
export const MAX_SHIPPING_AMOUNT = 100000;
|
|
204
|
+
|
|
205
|
+
/** @param {unknown} value */
|
|
206
|
+
const isShippingAmount = (value) =>
|
|
207
|
+
typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= MAX_SHIPPING_AMOUNT
|
|
208
|
+
&& Math.abs(value * 100 - Math.round(value * 100)) < 1e-6;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* What is wrong with a declared `shipping` value, one sentence per problem; `[]` when it is
|
|
212
|
+
* valid. A policy with any problem is refused whole, so checkout never charges half of one.
|
|
213
|
+
*
|
|
214
|
+
* @param {unknown} shipping
|
|
215
|
+
* @returns {string[]}
|
|
216
|
+
*/
|
|
217
|
+
export function shippingDeclarationProblems(shipping) {
|
|
218
|
+
if (!isPlainObject(shipping)) return ["`shipping` must be an object"];
|
|
219
|
+
const problems = [];
|
|
220
|
+
const source = /** @type {Record<string, unknown>} */ (shipping);
|
|
221
|
+
for (const key of Object.keys(source)) {
|
|
222
|
+
if (!SHIPPING_KEYS.includes(key)) problems.push(`\`shipping.${key}\` is not a shipping key (${SHIPPING_KEYS.join(", ")})`);
|
|
223
|
+
}
|
|
224
|
+
if (!isShippingAmount(source.flatRate)) {
|
|
225
|
+
problems.push(`\`shipping.flatRate\` must be an amount from 0 to ${MAX_SHIPPING_AMOUNT} with at most two decimal places`);
|
|
226
|
+
}
|
|
227
|
+
if (source.freeAtOrAbove !== undefined && !(isShippingAmount(source.freeAtOrAbove) && /** @type {number} */ (source.freeAtOrAbove) > 0)) {
|
|
228
|
+
problems.push(`\`shipping.freeAtOrAbove\` must be an amount above 0 and at most ${MAX_SHIPPING_AMOUNT} with at most two decimal places`);
|
|
229
|
+
}
|
|
230
|
+
return problems;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The owner-facing sentence for an invalid `shipping` declaration. */
|
|
234
|
+
function shippingProblemMessage(problems) {
|
|
235
|
+
return `\`shipping\` is not a valid shipping policy (${problems.join("; ")}). ` +
|
|
236
|
+
"Checkout charges no shipping until it is fixed.";
|
|
237
|
+
}
|
|
238
|
+
|
|
161
239
|
|
|
162
240
|
/** What each compliance key puts on the page — the thing that is missing when it is. */
|
|
163
241
|
export const COMPLIANCE_NOTICES = {
|
|
@@ -530,6 +608,8 @@ export function declarationFindings(current, previous) {
|
|
|
530
608
|
if (warning.enabled && warning.problems.length > 0) {
|
|
531
609
|
out.push(`CONFIG: ${nicotineWarningProblemMessage(warning.problems)}`);
|
|
532
610
|
}
|
|
611
|
+
const shipping = current.shipping === undefined ? [] : shippingDeclarationProblems(current.shipping);
|
|
612
|
+
if (shipping.length > 0) out.push(`CONFIG: ${shippingProblemMessage(shipping)}`);
|
|
533
613
|
}
|
|
534
614
|
for (const { key, notice } of droppedMandatedNotices(previous, current)) {
|
|
535
615
|
out.push(
|
|
@@ -608,11 +688,23 @@ export function validateDeclaredConfig(config, file = ".tot/config.json") {
|
|
|
608
688
|
&& !(Array.isArray(config.embeds) && config.embeds.every((/** @type {unknown} */ s) => typeof s === "string"))) {
|
|
609
689
|
out.push(mk(ERROR, "config-embeds", file, '`embeds` must be an array of embed slugs, e.g. ["youtube"]'));
|
|
610
690
|
}
|
|
691
|
+
if (config.currency !== undefined) {
|
|
692
|
+
for (const problem of currencyDeclarationProblems(config.currency)) {
|
|
693
|
+
out.push(mk(ERROR, "config-currency", file, `${problem} — the store sells in ${DEFAULT_CURRENCY} until it is fixed`));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
611
696
|
if (config.seo !== undefined) {
|
|
612
697
|
for (const problem of seoDeclarationProblems(config.seo)) {
|
|
613
698
|
out.push(mk(ERROR, "config-seo", `${file} seo`, `${problem} — the whole \`seo\` declaration is ignored until it is fixed`));
|
|
614
699
|
}
|
|
615
700
|
}
|
|
701
|
+
if (config.shipping !== undefined) {
|
|
702
|
+
const problems = shippingDeclarationProblems(config.shipping);
|
|
703
|
+
if (problems.length > 0) {
|
|
704
|
+
out.push(mk(ERROR, "config-shipping", `${file} shipping`, shippingProblemMessage(problems),
|
|
705
|
+
'declare e.g. `"shipping": { "flatRate": 5.99, "freeAtOrAbove": 100 }`, or remove `shipping` to charge none'));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
616
708
|
return out;
|
|
617
709
|
}
|
|
618
710
|
|