@tokenoftrust/cli 3.7.3 → 3.7.5
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/commands/ship.mjs +19 -3
- package/src/declared-config.d.mts +3 -0
- package/src/declared-config.mjs +51 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5",
|
|
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",
|
package/src/commands/ship.mjs
CHANGED
|
@@ -291,6 +291,8 @@ export function normalizeShipResult(data) {
|
|
|
291
291
|
artifactDigest: typeof o.artifactDigest === "string" ? o.artifactDigest : null,
|
|
292
292
|
receiptId: typeof o.receiptId === "string" ? o.receiptId : null,
|
|
293
293
|
rollbackTarget: o.rollbackTarget && typeof o.rollbackTarget === "object" ? o.rollbackTarget : null,
|
|
294
|
+
retriable: o.retriable === false ? false : o.retriable === true ? true : null,
|
|
295
|
+
pointerWritten: o.pointerWritten === true,
|
|
294
296
|
};
|
|
295
297
|
}
|
|
296
298
|
|
|
@@ -699,20 +701,34 @@ function reportShipResult(result, { tenant, liveUrl, noOpen, openUrl }) {
|
|
|
699
701
|
return 1;
|
|
700
702
|
}
|
|
701
703
|
if (result.state === "promote_failed" || result.state === "record_failed") {
|
|
704
|
+
// A deterministic refusal (the live flip's compare-and-set, or any outcome that says a retry
|
|
705
|
+
// cannot change it) must not be offered as safe to retry.
|
|
706
|
+
const deterministic = result.retriable === false || result.reason === "live_moved";
|
|
702
707
|
console.error(
|
|
703
708
|
fail(
|
|
704
709
|
result.message || `ship ${result.state.replace("_", " ")}`,
|
|
705
|
-
|
|
710
|
+
deterministic
|
|
711
|
+
? "do not re-run `tot ship` — this refusal is deterministic and will not change"
|
|
712
|
+
: "re-run `tot ship` — the ship is idempotent and safe to retry",
|
|
706
713
|
),
|
|
707
714
|
);
|
|
708
715
|
return 1;
|
|
709
716
|
}
|
|
710
717
|
if (result.state === "publishing") {
|
|
711
|
-
|
|
718
|
+
// Dispatched is not written. Say the pointer moved only once this result says it was.
|
|
719
|
+
console.log(
|
|
720
|
+
result.pointerWritten
|
|
721
|
+
? `\n ⏳ ${tenant} is publishing — the live pointer moved, CDN delivery is still confirming.`
|
|
722
|
+
: `\n ⏳ ${tenant} is publishing — dispatched; the live pointer is not written yet.`,
|
|
723
|
+
);
|
|
712
724
|
if (result.pinnedSha) console.log(` pinned: ${result.pinnedSha}`);
|
|
713
725
|
if (result.receiptId) console.log(` receipt: ${result.receiptId}`);
|
|
714
726
|
if (result.message) console.log(` ${result.message}`);
|
|
715
|
-
console.log(
|
|
727
|
+
console.log(
|
|
728
|
+
result.pointerWritten
|
|
729
|
+
? " re-run `tot ship` in a bit to confirm — it dedupes onto this same dispatch, safe to retry."
|
|
730
|
+
: " re-run `tot ship` in a bit to confirm — it dedupes onto this same dispatch.",
|
|
731
|
+
);
|
|
716
732
|
return 1;
|
|
717
733
|
}
|
|
718
734
|
console.error(
|
|
@@ -23,7 +23,10 @@ export const CURRENCY_CODES: readonly string[];
|
|
|
23
23
|
export function resolveStoreCurrency(declared: unknown): string;
|
|
24
24
|
export function currencyDeclarationProblems(currency: unknown): string[];
|
|
25
25
|
export const SHIPPING_KEYS: readonly string[];
|
|
26
|
+
export const SHIPPING_TIER_KEYS: readonly string[];
|
|
26
27
|
export const MAX_SHIPPING_AMOUNT: number;
|
|
28
|
+
export const MAX_SHIPPING_TIERS: number;
|
|
29
|
+
export const MAX_SHIPPING_LABEL_LENGTH: number;
|
|
27
30
|
export function shippingDeclarationProblems(shipping: unknown): string[];
|
|
28
31
|
export const COMPLIANCE_NOTICES: Record<string, string>;
|
|
29
32
|
export const MANDATED_NOTICE_KEYS: string[];
|
package/src/declared-config.mjs
CHANGED
|
@@ -197,20 +197,38 @@ export function seoDeclarationProblems(seo) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/** Every `shipping` key (mirrors packages/public-runtime/src/declared-tenant-config.mjs). */
|
|
200
|
-
export const SHIPPING_KEYS = Object.freeze(["
|
|
200
|
+
export const SHIPPING_KEYS = Object.freeze(["tiers"]);
|
|
201
|
+
|
|
202
|
+
/** Every key of one `shipping.tiers` entry. */
|
|
203
|
+
export const SHIPPING_TIER_KEYS = Object.freeze(["atOrAbove", "rate", "label"]);
|
|
201
204
|
|
|
202
205
|
/** Largest shipping amount a declaration may state, in the cart's currency. */
|
|
203
206
|
export const MAX_SHIPPING_AMOUNT = 100000;
|
|
204
207
|
|
|
208
|
+
/** Most tiers one policy may declare. */
|
|
209
|
+
export const MAX_SHIPPING_TIERS = 10;
|
|
210
|
+
|
|
211
|
+
/** Longest shipping-line label, in characters. */
|
|
212
|
+
export const MAX_SHIPPING_LABEL_LENGTH = 100;
|
|
213
|
+
|
|
205
214
|
/** @param {unknown} value */
|
|
206
215
|
const isShippingAmount = (value) =>
|
|
207
216
|
typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= MAX_SHIPPING_AMOUNT
|
|
208
217
|
&& Math.abs(value * 100 - Math.round(value * 100)) < 1e-6;
|
|
209
218
|
|
|
219
|
+
/** @param {unknown} value */
|
|
220
|
+
const isShippingLabel = (value) =>
|
|
221
|
+
typeof value === "string" && value.trim() === value && value.length > 0
|
|
222
|
+
&& [...value].length <= MAX_SHIPPING_LABEL_LENGTH && !/[\u0000-\u001f\u007f]/.test(value);
|
|
223
|
+
|
|
210
224
|
/**
|
|
211
225
|
* What is wrong with a declared `shipping` value, one sentence per problem; `[]` when it is
|
|
212
226
|
* valid. A policy with any problem is refused whole, so checkout never charges half of one.
|
|
213
227
|
*
|
|
228
|
+
* Tiers are ordered by the item subtotal they start at. The first starts at 0 and states no
|
|
229
|
+
* `atOrAbove`; each later tier's `atOrAbove` is above the one before it and is inclusive, so an
|
|
230
|
+
* order whose subtotal equals it pays that tier's `rate`.
|
|
231
|
+
*
|
|
214
232
|
* @param {unknown} shipping
|
|
215
233
|
* @returns {string[]}
|
|
216
234
|
*/
|
|
@@ -221,12 +239,38 @@ export function shippingDeclarationProblems(shipping) {
|
|
|
221
239
|
for (const key of Object.keys(source)) {
|
|
222
240
|
if (!SHIPPING_KEYS.includes(key)) problems.push(`\`shipping.${key}\` is not a shipping key (${SHIPPING_KEYS.join(", ")})`);
|
|
223
241
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
problems.push(`\`shipping.freeAtOrAbove\` must be an amount above 0 and at most ${MAX_SHIPPING_AMOUNT} with at most two decimal places`);
|
|
242
|
+
const tiers = source.tiers;
|
|
243
|
+
if (!Array.isArray(tiers) || tiers.length === 0 || tiers.length > MAX_SHIPPING_TIERS) {
|
|
244
|
+
problems.push(`\`shipping.tiers\` must be a list of 1 to ${MAX_SHIPPING_TIERS} tiers`);
|
|
245
|
+
return problems;
|
|
229
246
|
}
|
|
247
|
+
let previous = 0;
|
|
248
|
+
tiers.forEach((tier, index) => {
|
|
249
|
+
const at = `shipping.tiers[${index}]`;
|
|
250
|
+
if (!isPlainObject(tier)) {
|
|
251
|
+
problems.push(`\`${at}\` must be an object`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const entry = /** @type {Record<string, unknown>} */ (tier);
|
|
255
|
+
for (const key of Object.keys(entry)) {
|
|
256
|
+
if (!SHIPPING_TIER_KEYS.includes(key)) problems.push(`\`${at}.${key}\` is not a tier key (${SHIPPING_TIER_KEYS.join(", ")})`);
|
|
257
|
+
}
|
|
258
|
+
if (!isShippingAmount(entry.rate)) {
|
|
259
|
+
problems.push(`\`${at}.rate\` must be an amount from 0 to ${MAX_SHIPPING_AMOUNT} with at most two decimal places`);
|
|
260
|
+
}
|
|
261
|
+
if (entry.label !== undefined && !isShippingLabel(entry.label)) {
|
|
262
|
+
problems.push(`\`${at}.label\` must be text of 1 to ${MAX_SHIPPING_LABEL_LENGTH} characters with no surrounding spaces or line breaks`);
|
|
263
|
+
}
|
|
264
|
+
if (index === 0) {
|
|
265
|
+
if (entry.atOrAbove !== undefined) problems.push(`\`${at}\` starts at 0, so it states no \`atOrAbove\``);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (!(isShippingAmount(entry.atOrAbove) && /** @type {number} */ (entry.atOrAbove) > previous)) {
|
|
269
|
+
problems.push(`\`${at}.atOrAbove\` must be an amount above ${previous}, where the tier before it starts, and at most ${MAX_SHIPPING_AMOUNT} with at most two decimal places`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
previous = /** @type {number} */ (entry.atOrAbove);
|
|
273
|
+
});
|
|
230
274
|
return problems;
|
|
231
275
|
}
|
|
232
276
|
|
|
@@ -702,7 +746,7 @@ export function validateDeclaredConfig(config, file = ".tot/config.json") {
|
|
|
702
746
|
const problems = shippingDeclarationProblems(config.shipping);
|
|
703
747
|
if (problems.length > 0) {
|
|
704
748
|
out.push(mk(ERROR, "config-shipping", `${file} shipping`, shippingProblemMessage(problems),
|
|
705
|
-
'declare e.g. `"shipping": { "
|
|
749
|
+
'declare e.g. `"shipping": { "tiers": [ { "rate": 5.99 }, { "atOrAbove": 100, "rate": 0, "label": "Free shipping" } ] }`, or remove `shipping` to charge none'));
|
|
706
750
|
}
|
|
707
751
|
}
|
|
708
752
|
return out;
|