@tokenoftrust/storefront-runner 4.10.0 → 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.
@@ -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 — only the field path
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 ? `${base}.${key}` : key, code: "unknown_field" });
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: at, code: "unknown_enum" });
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;
@@ -14,7 +14,7 @@
14
14
  "preview": "wrangler dev",
15
15
  "typecheck": "astro check && tsc --noEmit",
16
16
  "astro": "astro",
17
- "test": "NODE_OPTIONS=--experimental-sqlite vitest run --exclude src/lib/integrations/apps/v84CleanCloneReplay.test.ts --exclude src/lib/integrations/apps/v84NonportableRouteDogfood.test.ts --exclude src/lib/integrations/apps/v84CloneLifecycleEvidence.test.ts && NODE_OPTIONS=--experimental-sqlite vitest run src/lib/integrations/apps/v84CleanCloneReplay.test.ts src/lib/integrations/apps/v84NonportableRouteDogfood.test.ts src/lib/integrations/apps/v84CloneLifecycleEvidence.test.ts --fileParallelism=false && pnpm run test:pipeline && pnpm run test:pipeline-faults",
17
+ "test": "NODE_OPTIONS=--experimental-sqlite node ../../scripts/vitest-run.mjs run --exclude src/lib/integrations/apps/v84CleanCloneReplay.test.ts --exclude src/lib/integrations/apps/v84NonportableRouteDogfood.test.ts --exclude src/lib/integrations/apps/v84CloneLifecycleEvidence.test.ts && NODE_OPTIONS=--experimental-sqlite node ../../scripts/vitest-run.mjs run src/lib/integrations/apps/v84CleanCloneReplay.test.ts src/lib/integrations/apps/v84NonportableRouteDogfood.test.ts src/lib/integrations/apps/v84CloneLifecycleEvidence.test.ts --fileParallelism=false && pnpm run test:pipeline && pnpm run test:pipeline-faults",
18
18
  "depcruise": "depcruise src --config .runner-dependency-cruiser.cjs"
19
19
  },
20
20
  "dependencies": {
@@ -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.10.0",
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",
@@ -47,7 +47,7 @@
47
47
  "scripts": {
48
48
  "typecheck": "tsc --noEmit",
49
49
  "build": "tsc",
50
- "test": "vitest run",
50
+ "test": "node ../../scripts/vitest-run.mjs run",
51
51
  "test:watch": "vitest",
52
52
  "generate:managed-analytics-schemas": "node --experimental-strip-types scripts/generate-managed-analytics-api-v1-schemas.mjs"
53
53
  },
@@ -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(`ids.${key}`, "unknown_enum", `Unknown environment "${key}"; one of: ${INTEGRATION_ENVIRONMENTS.join(", ")}.`);
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
- joinPath(path, key),
68
+ rejectedKeyPath(path, ordinal),
60
69
  "unknown_field",
61
- `Unknown field "${key}"; this contract is closed.`,
70
+ "Unknown field; this contract is closed.",
62
71
  );
72
+ ordinal += 1;
63
73
  }
64
74
  }
65
75
  }