@tokenoftrust/cli 2.2.14 → 2.2.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "2.2.14",
3
+ "version": "2.2.15",
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",
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The `.tot/config.json` keys the platform reads, and the refusal of everything
3
+ * else. Shared by `tot validate` (src/validate.mjs) and the monorepo/dev-loop
4
+ * validator (scripts/tenant/validate.mjs), so both refuse the same keys.
5
+ *
6
+ * Mirrors packages/public-runtime/src/declared-tenant-config.mjs (this package ships
7
+ * to npm on its own); test/declared-config-parity.test.mjs pins the two together, so
8
+ * a field the platform honors can never be refused here, and a key no surface reads
9
+ * can never pass. Dependency-free: the storefront runner copies this file as-is.
10
+ */
11
+
12
+ export const TOT_CONFIG_KEYS = [
13
+ "$schema", "tenant", "scope", "mappings", "hostPlatform", "sample", "seed",
14
+ "siteType", "compliance", "capabilities", "features",
15
+ ];
16
+ // Keys other tooling has written that the platform does NOT read, with the key it does.
17
+ const RENAMED_CONFIG_KEYS = { appDomain: "scope", tenantId: "tenant" };
18
+ export const COMPLIANCE_KEYS = [
19
+ "ruleProfile", "minAge", "nicotineWarning", "shippingRestriction", "pactAct",
20
+ "adultSignature", "prop65", "purchaseLimit", "stateEligibility", "exciseTax",
21
+ ];
22
+ export const FEATURE_KEYS = [
23
+ "catalog", "productSearch", "savedItems", "quickView", "newsletter", "commerceSitemap", "productJsonLd", "ageGate",
24
+ ];
25
+ export const SITE_TYPES = ["commerce", "marketing"];
26
+
27
+ const ERROR = "error";
28
+ const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
29
+
30
+ /** @typedef {{level:string, rule:string, file:string, message:string, fix?:string}} Finding */
31
+ function mk(level, rule, file, message, fix) {
32
+ return { level, rule, file, message, fix };
33
+ }
34
+
35
+ /**
36
+ * Refuse what the platform would silently ignore: an unknown top-level key, an
37
+ * unknown `compliance`/`features` key, or a declared field of the wrong shape. Each
38
+ * one passes JSON parsing and renders nothing — on a regulated store that is a
39
+ * mandated notice that never appears.
40
+ * @param {Record<string, any>} config parsed `.tot/config.json` (an object)
41
+ * @param {string} [file]
42
+ * @returns {Finding[]}
43
+ */
44
+ export function validateDeclaredConfig(config, file = ".tot/config.json") {
45
+ const out = [];
46
+ for (const key of Object.keys(config)) {
47
+ if (!TOT_CONFIG_KEYS.includes(key)) {
48
+ const renamed = RENAMED_CONFIG_KEYS[/** @type {keyof typeof RENAMED_CONFIG_KEYS} */ (key)];
49
+ out.push(mk(ERROR, "config-unknown-key", file,
50
+ `\`${key}\` is not a .tot/config.json key — the platform never reads it`,
51
+ renamed ? `use \`${renamed}\`` : `known keys: ${TOT_CONFIG_KEYS.join(", ")}`));
52
+ }
53
+ }
54
+ if (config.siteType !== undefined && !SITE_TYPES.includes(config.siteType)) {
55
+ out.push(mk(ERROR, "config-site-type", file, `\`siteType\` is "${config.siteType}" — must be one of ${SITE_TYPES.join(", ")}`));
56
+ }
57
+ /** @type {Array<[string, string[]]>} */
58
+ const objectFields = [["compliance", COMPLIANCE_KEYS], ["features", FEATURE_KEYS]];
59
+ for (const [field, known] of objectFields) {
60
+ const value = config[field];
61
+ if (value === undefined) continue;
62
+ if (!isObject(value)) {
63
+ out.push(mk(ERROR, `config-${field}`, file, `\`${field}\` must be an object`));
64
+ continue;
65
+ }
66
+ for (const key of Object.keys(value)) {
67
+ if (!known.includes(key)) {
68
+ out.push(mk(ERROR, `config-${field}-unknown-key`, `${file} ${field}`,
69
+ `\`${field}.${key}\` is not a ${field} key — nothing renders from it`,
70
+ `known keys: ${known.join(", ")}`));
71
+ }
72
+ }
73
+ }
74
+ if (config.capabilities !== undefined && !isObject(config.capabilities)) {
75
+ out.push(mk(ERROR, "config-capabilities", file, "`capabilities` must be an object"));
76
+ }
77
+ return out;
78
+ }
package/src/validate.mjs CHANGED
@@ -17,12 +17,21 @@
17
17
  */
18
18
  import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
19
19
  import { join, relative } from "node:path";
20
+ import { validateDeclaredConfig } from "./declared-config.mjs";
20
21
  import {
21
22
  foreignTenantSegments,
22
23
  parseMappingTarget,
23
24
  workspacePathForTenantRelative,
24
25
  } from "./tot-repo-mappings.mjs";
25
26
 
27
+ export {
28
+ TOT_CONFIG_KEYS,
29
+ COMPLIANCE_KEYS,
30
+ FEATURE_KEYS,
31
+ SITE_TYPES,
32
+ validateDeclaredConfig,
33
+ } from "./declared-config.mjs";
34
+
26
35
  const NUL = String.fromCharCode(0);
27
36
  export const ERROR = "error";
28
37
  export const WARN = "warn";
@@ -68,70 +77,6 @@ const hasScanExt = (p) => CONFLICT_SCAN_EXT.has((p.match(/\.[^./\\]+$/) || [""])
68
77
  const KNOWN_KINDS = new Set(["file", "tree"]);
69
78
  const REQUIRED_WORKSPACES = ["content/", "public/", "theme.json"];
70
79
 
71
- // The keys the platform reads in `.tot/config.json`. Mirrors
72
- // packages/public-runtime/src/declared-tenant-config.mjs (this package ships to npm
73
- // on its own); test/declared-config-parity.test.mjs pins the two together, so a
74
- // field the platform honors can never be refused here, and a key no surface reads
75
- // can never pass.
76
- export const TOT_CONFIG_KEYS = [
77
- "$schema", "tenant", "scope", "mappings", "hostPlatform", "sample", "seed",
78
- "siteType", "compliance", "capabilities", "features",
79
- ];
80
- // Keys other tooling has written that the platform does NOT read, with the key it does.
81
- const RENAMED_CONFIG_KEYS = { appDomain: "scope", tenantId: "tenant" };
82
- export const COMPLIANCE_KEYS = [
83
- "ruleProfile", "minAge", "nicotineWarning", "shippingRestriction", "pactAct",
84
- "adultSignature", "prop65", "purchaseLimit", "stateEligibility", "exciseTax",
85
- ];
86
- export const FEATURE_KEYS = [
87
- "catalog", "productSearch", "savedItems", "quickView", "newsletter", "commerceSitemap", "productJsonLd", "ageGate",
88
- ];
89
- export const SITE_TYPES = ["commerce", "marketing"];
90
-
91
- const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
92
-
93
- /**
94
- * Refuse what the platform would silently ignore: an unknown top-level key, an
95
- * unknown `compliance`/`features` key, or a declared field of the wrong shape. Each
96
- * one passes JSON parsing and renders nothing — on a regulated store that is a
97
- * mandated notice that never appears.
98
- * @returns {Finding[]}
99
- */
100
- export function validateDeclaredConfig(config, file = ".tot/config.json") {
101
- const out = [];
102
- for (const key of Object.keys(config)) {
103
- if (!TOT_CONFIG_KEYS.includes(key)) {
104
- const renamed = RENAMED_CONFIG_KEYS[key];
105
- out.push(mk(ERROR, "config-unknown-key", file,
106
- `\`${key}\` is not a .tot/config.json key — the platform never reads it`,
107
- renamed ? `use \`${renamed}\`` : `known keys: ${TOT_CONFIG_KEYS.join(", ")}`));
108
- }
109
- }
110
- if (config.siteType !== undefined && !SITE_TYPES.includes(config.siteType)) {
111
- out.push(mk(ERROR, "config-site-type", file, `\`siteType\` is "${config.siteType}" — must be one of ${SITE_TYPES.join(", ")}`));
112
- }
113
- /** @type {Array<[string, string[]]>} */
114
- const objectFields = [["compliance", COMPLIANCE_KEYS], ["features", FEATURE_KEYS]];
115
- for (const [field, known] of objectFields) {
116
- const value = config[field];
117
- if (value === undefined) continue;
118
- if (!isObject(value)) {
119
- out.push(mk(ERROR, `config-${field}`, file, `\`${field}\` must be an object`));
120
- continue;
121
- }
122
- for (const key of Object.keys(value)) {
123
- if (!known.includes(key)) {
124
- out.push(mk(ERROR, `config-${field}-unknown-key`, `${file} ${field}`,
125
- `\`${field}.${key}\` is not a ${field} key — nothing renders from it`,
126
- `known keys: ${known.join(", ")}`));
127
- }
128
- }
129
- }
130
- if (config.capabilities !== undefined && !isObject(config.capabilities)) {
131
- out.push(mk(ERROR, "config-capabilities", file, "`capabilities` must be an object"));
132
- }
133
- return out;
134
- }
135
80
 
136
81
  /**
137
82
  * Validate a parsed `.tot/config.json` against the shape the reconcile pipeline