@tokenoftrust/storefront-runner 4.10.1 → 4.11.0
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/analytics-declaration.mjs +21 -5
- package/apps/storefront/src/lib/integrations/integration/stageDestinationIds.ts +5 -2
- package/package.json +1 -1
- package/packages/public-runtime/src/integration/platform-adapters.ts +4 -2
- package/packages/public-runtime/src/integration/result.ts +12 -2
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* input; `agency-kit/tools/lib/fixtures/analytics-declarations.json` is run through both, so a
|
|
11
11
|
* drift on either side fails a test.
|
|
12
12
|
*
|
|
13
|
-
* Messages carry no value from the file — never an id, never a key's value
|
|
14
|
-
* and a fixed explanation, so a finding is safe to print, log or forward.
|
|
13
|
+
* Messages carry no value from the file — never an id, never a key's value, never an unrecognized
|
|
14
|
+
* key — only the field path and a fixed explanation, so a finding is safe to print, log or forward.
|
|
15
|
+
* An unrecognized key is anchored by its position among the rejected keys (`$property[0]`).
|
|
15
16
|
*
|
|
16
17
|
* Pure: callers pass the file's text. `packages/cli/src/analytics-declaration.mjs` is a
|
|
17
18
|
* byte-identical copy (a test pins it).
|
|
@@ -77,6 +78,17 @@ function isPlainObject(value) {
|
|
|
77
78
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The anchor for the `ordinal`-th rejected key of an object. A rejected key is untrusted text, so
|
|
83
|
+
* the path names its position among the rejected keys, never the key.
|
|
84
|
+
* @param {string} base
|
|
85
|
+
* @param {number} ordinal
|
|
86
|
+
*/
|
|
87
|
+
function rejectedKeyPath(base, ordinal) {
|
|
88
|
+
const anchor = `$property[${ordinal}]`;
|
|
89
|
+
return base ? `${base}.${anchor}` : anchor;
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
/**
|
|
81
93
|
* @param {Record<string, unknown>} obj
|
|
82
94
|
* @param {readonly string[]} allowed
|
|
@@ -84,9 +96,11 @@ function isPlainObject(value) {
|
|
|
84
96
|
* @param {AnalyticsViolation[]} out
|
|
85
97
|
*/
|
|
86
98
|
function unknownKeys(obj, allowed, base, out) {
|
|
99
|
+
let ordinal = 0;
|
|
87
100
|
for (const key of Object.keys(obj)) {
|
|
88
101
|
if (allowed.includes(key) || ANNOTATION_KEYS.includes(key)) continue;
|
|
89
|
-
out.push({ path: base
|
|
102
|
+
out.push({ path: rejectedKeyPath(base, ordinal), code: "unknown_field" });
|
|
103
|
+
ordinal += 1;
|
|
90
104
|
}
|
|
91
105
|
}
|
|
92
106
|
|
|
@@ -100,12 +114,14 @@ function unknownKeys(obj, allowed, base, out) {
|
|
|
100
114
|
function stageIdViolations(ids, adapter, base, out) {
|
|
101
115
|
/** @type {Map<string, string[]>} */
|
|
102
116
|
const valid = new Map();
|
|
117
|
+
let rejectedStages = 0;
|
|
103
118
|
for (const [stage, raw] of Object.entries(ids)) {
|
|
104
|
-
const at = `${base}.ids.${stage}`;
|
|
105
119
|
if (!ANALYTICS_STAGES.includes(stage)) {
|
|
106
|
-
out.push({ path:
|
|
120
|
+
out.push({ path: rejectedKeyPath(`${base}.ids`, rejectedStages), code: "unknown_enum" });
|
|
121
|
+
rejectedStages += 1;
|
|
107
122
|
continue;
|
|
108
123
|
}
|
|
124
|
+
const at = `${base}.ids.${stage}`;
|
|
109
125
|
if (!Array.isArray(raw)) {
|
|
110
126
|
out.push({ path: at, code: "type" });
|
|
111
127
|
continue;
|
|
@@ -66,16 +66,19 @@ export function parseStageDestinationIds(
|
|
|
66
66
|
const details: StageDestinationIdsError["details"] = [];
|
|
67
67
|
const value: DestinationIdsByEnvironment = {};
|
|
68
68
|
|
|
69
|
+
let rejectedStages = 0;
|
|
69
70
|
for (const [stage, raw] of entries) {
|
|
70
|
-
const path = `${base}.${stage}`;
|
|
71
71
|
if (!(ADAPTER_CONFIG_STAGES as readonly string[]).includes(stage)) {
|
|
72
|
+
// An unrecognized stage key is untrusted text: anchor it by position, never by name.
|
|
72
73
|
details.push({
|
|
73
|
-
path
|
|
74
|
+
path: `${base}.$property[${rejectedStages}]`,
|
|
74
75
|
code: "unknown_enum",
|
|
75
76
|
message: `Stage must be one of: ${ADAPTER_CONFIG_STAGES.join(", ")}.`,
|
|
76
77
|
});
|
|
78
|
+
rejectedStages += 1;
|
|
77
79
|
continue;
|
|
78
80
|
}
|
|
81
|
+
const path = `${base}.${stage}`;
|
|
79
82
|
if (!Array.isArray(raw)) {
|
|
80
83
|
details.push({
|
|
81
84
|
path,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
* value stays in server custody — never in this browser config.
|
|
50
50
|
*/
|
|
51
51
|
import type { ContractResult, ContractViolation } from "./result.js";
|
|
52
|
-
import { Violations, isPlainObject } from "./result.js";
|
|
52
|
+
import { Violations, isPlainObject, rejectedKeyPath } from "./result.js";
|
|
53
53
|
import type { CommerceEventType, CommerceEvent } from "./events.js";
|
|
54
54
|
import { APPROVED_MARKETING_ACTION_NAMES } from "./events.js";
|
|
55
55
|
import type {
|
|
@@ -599,9 +599,11 @@ function validateDestinationIds(
|
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
const out: DestinationIdsByEnvironment = {};
|
|
602
|
+
let rejectedStages = 0;
|
|
602
603
|
for (const [key, raw] of Object.entries(value)) {
|
|
603
604
|
if (!(INTEGRATION_ENVIRONMENTS as readonly string[]).includes(key)) {
|
|
604
|
-
v.add(
|
|
605
|
+
v.add(rejectedKeyPath("ids", rejectedStages), "unknown_enum", `Unknown environment; one of: ${INTEGRATION_ENVIRONMENTS.join(", ")}.`);
|
|
606
|
+
rejectedStages += 1;
|
|
605
607
|
continue;
|
|
606
608
|
}
|
|
607
609
|
if (!Array.isArray(raw)) {
|
|
@@ -46,6 +46,14 @@ export function joinPath(path: string, key: string): string {
|
|
|
46
46
|
return path ? `${path}.${key}` : key;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The path of the `ordinal`-th rejected key of the object at `path`. A rejected key is untrusted
|
|
51
|
+
* text, so the path names its position among the rejected keys (`$property[0]`), never the key.
|
|
52
|
+
*/
|
|
53
|
+
export function rejectedKeyPath(path: string, ordinal: number): string {
|
|
54
|
+
return joinPath(path, `$property[${ordinal}]`);
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
export function rejectUnknownKeys(
|
|
50
58
|
obj: Record<string, unknown>,
|
|
51
59
|
allowed: readonly string[],
|
|
@@ -53,13 +61,15 @@ export function rejectUnknownKeys(
|
|
|
53
61
|
path: string,
|
|
54
62
|
): void {
|
|
55
63
|
const set = new Set([...allowed, ...ANNOTATION_KEYS]);
|
|
64
|
+
let ordinal = 0;
|
|
56
65
|
for (const key of Object.keys(obj)) {
|
|
57
66
|
if (!set.has(key)) {
|
|
58
67
|
v.add(
|
|
59
|
-
|
|
68
|
+
rejectedKeyPath(path, ordinal),
|
|
60
69
|
"unknown_field",
|
|
61
|
-
|
|
70
|
+
"Unknown field; this contract is closed.",
|
|
62
71
|
);
|
|
72
|
+
ordinal += 1;
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
}
|