@diviops/mcp-server 1.5.14 → 1.5.17
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/README.md +10 -1
- package/data/verified-attrs-backlog.json +5 -5
- package/data/verified-attrs.json +8 -8
- package/dist/compatibility.d.ts +25 -0
- package/dist/index.js +349 -4
- package/dist/preset-cli/button-emitter.d.ts +1 -1
- package/dist/preset-cli/button-emitter.js +1 -1
- package/dist/preset-cli/cli.d.ts +4 -3
- package/dist/preset-cli/cli.js +11 -10
- package/dist/preset-cli/heading-font-emitter.d.ts +1 -1
- package/dist/preset-cli/heading-font-emitter.js +3 -3
- package/dist/preset-cli/spacing-emitter.d.ts +13 -13
- package/dist/preset-cli/spacing-emitter.js +23 -23
- package/dist/preset-cli/write-path.d.ts +3 -3
- package/dist/preset-cli/write-path.js +3 -3
- package/dist/wp-cli-fs-validator.d.ts +1 -1
- package/dist/wp-cli-fs-validator.js +1 -1
- package/dist/wp-cli.js +1 -2
- package/dist/wp-client.js +17 -0
- package/package.json +3 -2
- package/dist/preset-cli/__tests__/button-emitter.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/button-emitter.test.js +0 -188
- package/dist/preset-cli/__tests__/cli.test.d.ts +0 -9
- package/dist/preset-cli/__tests__/cli.test.js +0 -791
- package/dist/preset-cli/__tests__/heading-font-emitter.test.d.ts +0 -12
- package/dist/preset-cli/__tests__/heading-font-emitter.test.js +0 -249
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.d.ts +0 -13
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.js +0 -64
- package/dist/preset-cli/__tests__/registry.test.d.ts +0 -5
- package/dist/preset-cli/__tests__/registry.test.js +0 -175
- package/dist/preset-cli/__tests__/spacing-emitter.test.d.ts +0 -20
- package/dist/preset-cli/__tests__/spacing-emitter.test.js +0 -409
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.d.ts +0 -14
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.js +0 -191
- package/dist/preset-cli/__tests__/write-path.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/write-path.test.js +0 -287
package/dist/preset-cli/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `diviops-preset` — standalone preset-emitter CLI
|
|
2
|
+
* `diviops-preset` — standalone preset-emitter CLI.
|
|
3
3
|
*
|
|
4
4
|
* Emits byte-canonical Divi 5.5.x preset JSON, gated by the verified-attrs
|
|
5
|
-
* registry and routed through the existing storage-path contract.
|
|
6
|
-
*
|
|
5
|
+
* registry and routed through the existing storage-path contract. The
|
|
6
|
+
* initial slice shipped the `divi/button` group emitter; subsequent slices
|
|
7
|
+
* added the heading/font, color, and spacing emitters.
|
|
7
8
|
*
|
|
8
9
|
* Usage:
|
|
9
10
|
* diviops-preset button [options] Emit a divi/button group preset
|
|
@@ -129,13 +130,13 @@ text-body-font OPTIONS (all styling fields optional; emit-on-specification only)
|
|
|
129
130
|
spacing OPTIONS (sparse-emit per axis; paired sync flags per axis)
|
|
130
131
|
--name <string> Preset display name (required).
|
|
131
132
|
--module <name> Required. Currently only divi/section is wired
|
|
132
|
-
(
|
|
133
|
-
Other modules (divi/heading,
|
|
134
|
-
divi/button, etc.) resolve to the
|
|
135
|
-
and are refused with
|
|
136
|
-
promoting them requires a
|
|
137
|
-
canonical
|
|
138
|
-
implementation/docs
|
|
133
|
+
(the canonical capture verified only the
|
|
134
|
+
divi/section cell). Other modules (divi/heading,
|
|
135
|
+
divi/text, divi/button, etc.) resolve to the
|
|
136
|
+
registry gate and are refused with
|
|
137
|
+
EvidenceGateError — promoting them requires a
|
|
138
|
+
canonical-capture change plus a follow-up
|
|
139
|
+
implementation/docs change (NOT a free dispatch-
|
|
139
140
|
clear via the gate alone).
|
|
140
141
|
--padding-top <v> Desktop padding corners. Pass any subset; only
|
|
141
142
|
--padding-right <v> passed corners emit (sparse-emit per axis). v1
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `divi/font` heading group-preset emitter
|
|
2
|
+
* `divi/font` heading group-preset emitter.
|
|
3
3
|
*
|
|
4
4
|
* Emits a byte-canonical Divi 5.5.x `type: "group"` `divi/font` preset
|
|
5
5
|
* targeting `divi/heading` at `attrs.title.decoration.font.font.desktop.value.*`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `divi/font` heading group-preset emitter
|
|
2
|
+
* `divi/font` heading group-preset emitter.
|
|
3
3
|
*
|
|
4
4
|
* Emits a byte-canonical Divi 5.5.x `type: "group"` `divi/font` preset
|
|
5
5
|
* targeting `divi/heading` at `attrs.title.decoration.font.font.desktop.value.*`,
|
|
@@ -109,8 +109,8 @@ export function emitHeadingFontGroupPreset(input, registry = loadRegistry()) {
|
|
|
109
109
|
throw new Error(`Heading-font emitter requires \`pattern\` to be "google" or "local"; got ${JSON.stringify(input.pattern)}. ` +
|
|
110
110
|
`There is no default — Pattern A (google) and Pattern B (local) are distinct registry/evidence variants.`);
|
|
111
111
|
}
|
|
112
|
-
// Pattern B + explicit weight is out-of-scope for
|
|
113
|
-
// Pattern B capture proves `weight` is absent; no registry entry
|
|
112
|
+
// Pattern B + explicit weight is out-of-scope for the current emitter. The
|
|
113
|
+
// verified Pattern B capture proves `weight` is absent; no registry entry
|
|
114
114
|
// currently authorizes a local-hosted-with-explicit-weight shape.
|
|
115
115
|
if (input.pattern === "local" && input.weight !== undefined) {
|
|
116
116
|
throw new UnsupportedVariantCombinationError(`Pattern B (local-hosted) does not support an explicit \`weight\` — the verified ` +
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `divi/spacing` section group-preset emitter
|
|
2
|
+
* `divi/spacing` section group-preset emitter.
|
|
3
3
|
*
|
|
4
4
|
* Emits a byte-canonical Divi 5.6.0 `type: "group"` `divi/spacing` preset
|
|
5
5
|
* targeting `divi/section` at
|
|
6
6
|
* `attrs.module.decoration.spacing.desktop.value.{padding|margin}.*`,
|
|
7
|
-
* gated by the verified-attrs registry. Canonical shape
|
|
7
|
+
* gated by the verified-attrs registry. Canonical shape captured under
|
|
8
8
|
* `docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json`.
|
|
9
9
|
*
|
|
10
10
|
* Scope: `divi/section` ONLY. Heading / text / button spacing cells remain
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
* - `attrs == styleAttrs == renderAttrs` at the route layer (the plugin's
|
|
24
24
|
* `/preset/create` route mirrors `attrs` into all three buckets to match
|
|
25
25
|
* VB save semantics); the CLI request body only carries `attrs`. The
|
|
26
|
-
*
|
|
27
|
-
* `styleAttrs` / `renderAttrs` keys to the emitter output.
|
|
26
|
+
* canonical capture fixture documents the post-write storage shape — do
|
|
27
|
+
* NOT add `styleAttrs` / `renderAttrs` keys to the emitter output.
|
|
28
28
|
* - `groupId: "designSpacing"` — the Composable Settings panel id, NOT a
|
|
29
29
|
* dotted attr path (the prior schema-inferred `module.decoration.spacing`
|
|
30
|
-
* note was a misread, corrected
|
|
30
|
+
* note was a misread, now corrected).
|
|
31
31
|
* - `primaryAttrName: "module"` for section spacing.
|
|
32
32
|
* - Variable tokens (`$variable(...)` / bare `gvid-*`) in length flags are
|
|
33
|
-
* REFUSED —
|
|
33
|
+
* REFUSED — the canonical capture exercised literal CSS lengths only;
|
|
34
34
|
* variable-token shape needs its own capture before this emitter writes
|
|
35
35
|
* it.
|
|
36
36
|
*/
|
|
@@ -46,8 +46,8 @@ export declare const SPACING_PATTERN_FAMILY = "divi/spacing";
|
|
|
46
46
|
* `divi/section` is wired with fixtures + tests + canonical shape today.
|
|
47
47
|
*
|
|
48
48
|
* Promoting another module is NOT a free dispatch-clear via the registry
|
|
49
|
-
* gate — each new cell needs a
|
|
50
|
-
*
|
|
49
|
+
* gate — each new cell needs a canonical-capture change landing first AND
|
|
50
|
+
* a follow-up implementation/docs change.
|
|
51
51
|
*/
|
|
52
52
|
export declare const SPACING_SUPPORTED_MODULES: readonly ["divi/section"];
|
|
53
53
|
export type SpacingCornerInput = {
|
|
@@ -111,8 +111,8 @@ export declare function composeSpacingAttrs(input: SpacingEmitterInput): {
|
|
|
111
111
|
*
|
|
112
112
|
* `styleAttrs` and `renderAttrs` are intentionally NOT part of this entry:
|
|
113
113
|
* the plugin's `/preset/create` route mirrors the single `attrs` bag into
|
|
114
|
-
* all three buckets at write time. The
|
|
115
|
-
* post-write storage shape (which is why both `attrs` and `styleAttrs`
|
|
114
|
+
* all three buckets at write time. The canonical capture fixture documents
|
|
115
|
+
* the post-write storage shape (which is why both `attrs` and `styleAttrs`
|
|
116
116
|
* appear there byte-identical); the CLI emits the request shape only.
|
|
117
117
|
*/
|
|
118
118
|
export declare function emitSpacingGroupPreset(input: SpacingEmitterInput, registry?: VerifiedAttrsRegistry): SpacingPresetEntry;
|
|
@@ -123,9 +123,9 @@ export declare function emitSpacingGroupPreset(input: SpacingEmitterInput, regis
|
|
|
123
123
|
*
|
|
124
124
|
* `primary_attr_name` IS sent on the wire (the plugin's `/preset/create`
|
|
125
125
|
* route accepts it as an optional snake_case param and stores it as
|
|
126
|
-
* `primaryAttrName` in the preset).
|
|
127
|
-
* their preset types do not carry it; the divi/spacing
|
|
128
|
-
*
|
|
126
|
+
* `primaryAttrName` in the preset). The button / font / color emitters
|
|
127
|
+
* omit it because their preset types do not carry it; the divi/spacing
|
|
128
|
+
* cell does, per the canonical capture's load-bearing finding #2.
|
|
129
129
|
*/
|
|
130
130
|
export declare function buildSpacingPresetCreateBody(entry: SpacingPresetEntry, opts?: {
|
|
131
131
|
dry_run?: boolean;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `divi/spacing` section group-preset emitter
|
|
2
|
+
* `divi/spacing` section group-preset emitter.
|
|
3
3
|
*
|
|
4
4
|
* Emits a byte-canonical Divi 5.6.0 `type: "group"` `divi/spacing` preset
|
|
5
5
|
* targeting `divi/section` at
|
|
6
6
|
* `attrs.module.decoration.spacing.desktop.value.{padding|margin}.*`,
|
|
7
|
-
* gated by the verified-attrs registry. Canonical shape
|
|
7
|
+
* gated by the verified-attrs registry. Canonical shape captured under
|
|
8
8
|
* `docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json`.
|
|
9
9
|
*
|
|
10
10
|
* Scope: `divi/section` ONLY. Heading / text / button spacing cells remain
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
* - `attrs == styleAttrs == renderAttrs` at the route layer (the plugin's
|
|
24
24
|
* `/preset/create` route mirrors `attrs` into all three buckets to match
|
|
25
25
|
* VB save semantics); the CLI request body only carries `attrs`. The
|
|
26
|
-
*
|
|
27
|
-
* `styleAttrs` / `renderAttrs` keys to the emitter output.
|
|
26
|
+
* canonical capture fixture documents the post-write storage shape — do
|
|
27
|
+
* NOT add `styleAttrs` / `renderAttrs` keys to the emitter output.
|
|
28
28
|
* - `groupId: "designSpacing"` — the Composable Settings panel id, NOT a
|
|
29
29
|
* dotted attr path (the prior schema-inferred `module.decoration.spacing`
|
|
30
|
-
* note was a misread, corrected
|
|
30
|
+
* note was a misread, now corrected).
|
|
31
31
|
* - `primaryAttrName: "module"` for section spacing.
|
|
32
32
|
* - Variable tokens (`$variable(...)` / bare `gvid-*`) in length flags are
|
|
33
|
-
* REFUSED —
|
|
33
|
+
* REFUSED — the canonical capture exercised literal CSS lengths only;
|
|
34
34
|
* variable-token shape needs its own capture before this emitter writes
|
|
35
35
|
* it.
|
|
36
36
|
*/
|
|
@@ -46,8 +46,8 @@ export const SPACING_PATTERN_FAMILY = "divi/spacing";
|
|
|
46
46
|
* `divi/section` is wired with fixtures + tests + canonical shape today.
|
|
47
47
|
*
|
|
48
48
|
* Promoting another module is NOT a free dispatch-clear via the registry
|
|
49
|
-
* gate — each new cell needs a
|
|
50
|
-
*
|
|
49
|
+
* gate — each new cell needs a canonical-capture change landing first AND
|
|
50
|
+
* a follow-up implementation/docs change.
|
|
51
51
|
*/
|
|
52
52
|
export const SPACING_SUPPORTED_MODULES = ["divi/section"];
|
|
53
53
|
/** Detect bare `gvid-*` / `gcid-*` variable token names. */
|
|
@@ -56,7 +56,7 @@ const BARE_VARIABLE_TOKEN_RE = /^(gvid|gcid)-/;
|
|
|
56
56
|
* Positive validator for CSS length values. Accepts only the v1 unit set
|
|
57
57
|
* from the brief: integer or decimal numbers (including a leading minus
|
|
58
58
|
* sign and bare `0`) followed by `px`, `rem`, `em`, `%`, `vw`, or `vh`.
|
|
59
|
-
*
|
|
59
|
+
* The canonical capture exercised literal values only — variable tokens,
|
|
60
60
|
* `var(...)`, `calc(...)`, and free-form strings like `banana` are all
|
|
61
61
|
* refused before emission. Adding any new unit requires a brief update
|
|
62
62
|
* (canonical-capture re-test if the unit changes server-side rendering).
|
|
@@ -71,7 +71,7 @@ const LITERAL_CSS_LENGTH_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|vw|vh)$/;
|
|
|
71
71
|
* 2. Bare `gvid-*` / `gcid-*` token name — same deferred shape.
|
|
72
72
|
* 3. Anything else that does NOT match the v1 literal CSS length grammar
|
|
73
73
|
* (`px`, `rem`, `em`, `%`, `vw`, `vh`). Catches `banana`, `var(--x)`,
|
|
74
|
-
* `calc(8px + 2vw)`, etc. — none of which
|
|
74
|
+
* `calc(8px + 2vw)`, etc. — none of which the canonical capture verified.
|
|
75
75
|
*
|
|
76
76
|
* The variable-token branches fire BEFORE the generic literal check so
|
|
77
77
|
* the operator gets the precise "variable-token support deferred" hint
|
|
@@ -86,17 +86,17 @@ function assertLiteralLength(value, flagLabel) {
|
|
|
86
86
|
if (trimmed.startsWith("$variable(")) {
|
|
87
87
|
throw new Error(`${flagLabel} value ${JSON.stringify(value)} is a $variable() token. ` +
|
|
88
88
|
`Variable-token support deferred — pending canonical capture. ` +
|
|
89
|
-
`
|
|
89
|
+
`Canonical capture exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
|
|
90
90
|
}
|
|
91
91
|
if (BARE_VARIABLE_TOKEN_RE.test(trimmed)) {
|
|
92
92
|
throw new Error(`${flagLabel} value ${JSON.stringify(value)} is a bare ${trimmed.split("-")[0]}-* variable token. ` +
|
|
93
93
|
`Variable-token support deferred — pending canonical capture. ` +
|
|
94
|
-
`
|
|
94
|
+
`Canonical capture exercised literal CSS length values only (px / rem / em / % / vw / vh).`);
|
|
95
95
|
}
|
|
96
96
|
if (!LITERAL_CSS_LENGTH_RE.test(trimmed)) {
|
|
97
97
|
throw new Error(`${flagLabel} value ${JSON.stringify(value)} is not a literal CSS length. ` +
|
|
98
98
|
`v1 accepts only \`<number><unit>\` where unit is one of px / rem / em / % / vw / vh ` +
|
|
99
|
-
`(e.g. "40px", "1.5rem", "100%").
|
|
99
|
+
`(e.g. "40px", "1.5rem", "100%"). The canonical capture exercised literal ` +
|
|
100
100
|
`values only; broader value grammars (var(...), calc(...), etc.) need their own capture.`);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -190,8 +190,8 @@ export function composeSpacingAttrs(input) {
|
|
|
190
190
|
*
|
|
191
191
|
* `styleAttrs` and `renderAttrs` are intentionally NOT part of this entry:
|
|
192
192
|
* the plugin's `/preset/create` route mirrors the single `attrs` bag into
|
|
193
|
-
* all three buckets at write time. The
|
|
194
|
-
* post-write storage shape (which is why both `attrs` and `styleAttrs`
|
|
193
|
+
* all three buckets at write time. The canonical capture fixture documents
|
|
194
|
+
* the post-write storage shape (which is why both `attrs` and `styleAttrs`
|
|
195
195
|
* appear there byte-identical); the CLI emits the request shape only.
|
|
196
196
|
*/
|
|
197
197
|
export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
|
|
@@ -201,14 +201,14 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
|
|
|
201
201
|
if (!input.module || typeof input.module !== "string") {
|
|
202
202
|
throw new Error("spacing emitter requires `module` — currently only `divi/section` is " +
|
|
203
203
|
"wired (other module cells are SCHEMA_OBSERVED in the registry; " +
|
|
204
|
-
"promoting them requires a
|
|
205
|
-
"
|
|
204
|
+
"promoting them requires a canonical-capture change + a follow-up " +
|
|
205
|
+
"implementation/docs change).");
|
|
206
206
|
}
|
|
207
207
|
const { attrs, value } = composeSpacingAttrs(input);
|
|
208
208
|
// Empty-input rejection: at least one corner across either axis MUST be
|
|
209
209
|
// specified. An empty value bag is a usage error — there is nothing to
|
|
210
210
|
// write, and the resulting preset would be the empty-shell VB authoring
|
|
211
|
-
// footgun documented in the
|
|
211
|
+
// footgun documented in the canonical capture.
|
|
212
212
|
if (Object.keys(value).length === 0) {
|
|
213
213
|
throw new Error("spacing emitter produced an empty preset — pass at least one " +
|
|
214
214
|
"padding or margin corner (--padding-top, --margin-bottom, etc.).");
|
|
@@ -235,8 +235,8 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
|
|
|
235
235
|
`(this emitter hard-codes the divi/section wrapper "module" and ` +
|
|
236
236
|
`primaryAttrName "module"). Supported modules: ` +
|
|
237
237
|
`${SPACING_SUPPORTED_MODULES.join(", ")}. Adding a new module ` +
|
|
238
|
-
`requires a
|
|
239
|
-
`a follow-up implementation
|
|
238
|
+
`requires a canonical-capture change landing first AND ` +
|
|
239
|
+
`a follow-up implementation change extending this constant alongside ` +
|
|
240
240
|
`fixtures + tests + README updates.`);
|
|
241
241
|
}
|
|
242
242
|
return {
|
|
@@ -256,9 +256,9 @@ export function emitSpacingGroupPreset(input, registry = loadRegistry()) {
|
|
|
256
256
|
*
|
|
257
257
|
* `primary_attr_name` IS sent on the wire (the plugin's `/preset/create`
|
|
258
258
|
* route accepts it as an optional snake_case param and stores it as
|
|
259
|
-
* `primaryAttrName` in the preset).
|
|
260
|
-
* their preset types do not carry it; the divi/spacing
|
|
261
|
-
*
|
|
259
|
+
* `primaryAttrName` in the preset). The button / font / color emitters
|
|
260
|
+
* omit it because their preset types do not carry it; the divi/spacing
|
|
261
|
+
* cell does, per the canonical capture's load-bearing finding #2.
|
|
262
262
|
*/
|
|
263
263
|
export function buildSpacingPresetCreateBody(entry, opts = {}) {
|
|
264
264
|
const body = {
|
|
@@ -96,9 +96,9 @@ export declare function applyTextBodyFontPreset(client: PresetWriteClient, entry
|
|
|
96
96
|
* existing storage-routed route (no plugin route is added).
|
|
97
97
|
*
|
|
98
98
|
* Unlike the font emitters, the spacing entry carries `primary_attr_name`
|
|
99
|
-
* (`"module"` for the section cell per
|
|
100
|
-
* wire — the `/preset/create` route accepts it as an optional
|
|
101
|
-
* param and stores it as `primaryAttrName` in the preset.
|
|
99
|
+
* (`"module"` for the section cell per the canonical capture), which IS
|
|
100
|
+
* sent on the wire — the `/preset/create` route accepts it as an optional
|
|
101
|
+
* snake_case param and stores it as `primaryAttrName` in the preset.
|
|
102
102
|
*/
|
|
103
103
|
export declare function applySpacingPreset(client: PresetWriteClient, entry: SpacingPresetEntry, opts: {
|
|
104
104
|
serverVersion: string;
|
|
@@ -138,9 +138,9 @@ export async function applyTextBodyFontPreset(client, entry, opts) {
|
|
|
138
138
|
* existing storage-routed route (no plugin route is added).
|
|
139
139
|
*
|
|
140
140
|
* Unlike the font emitters, the spacing entry carries `primary_attr_name`
|
|
141
|
-
* (`"module"` for the section cell per
|
|
142
|
-
* wire — the `/preset/create` route accepts it as an optional
|
|
143
|
-
* param and stores it as `primaryAttrName` in the preset.
|
|
141
|
+
* (`"module"` for the section cell per the canonical capture), which IS
|
|
142
|
+
* sent on the wire — the `/preset/create` route accepts it as an optional
|
|
143
|
+
* snake_case param and stores it as `primaryAttrName` in the preset.
|
|
144
144
|
*/
|
|
145
145
|
export async function applySpacingPreset(client, entry, opts) {
|
|
146
146
|
await assertStorageCapability(client, opts.serverVersion);
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* This module adds a second-pass validator specifically for DEFAULT-tier
|
|
12
12
|
* FS commands. EXTENDED-tier FS commands (`import`, `eval-file`) are
|
|
13
13
|
* already opt-in via DIVIOPS_WP_CLI_ALLOW; their validation is tracked
|
|
14
|
-
* separately
|
|
14
|
+
* separately as a future scope expansion.
|
|
15
15
|
*
|
|
16
16
|
* Guarantees:
|
|
17
17
|
* - All path arguments must resolve under SAFE_FS_ROOT after canonicalization
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* This module adds a second-pass validator specifically for DEFAULT-tier
|
|
12
12
|
* FS commands. EXTENDED-tier FS commands (`import`, `eval-file`) are
|
|
13
13
|
* already opt-in via DIVIOPS_WP_CLI_ALLOW; their validation is tracked
|
|
14
|
-
* separately
|
|
14
|
+
* separately as a future scope expansion.
|
|
15
15
|
*
|
|
16
16
|
* Guarantees:
|
|
17
17
|
* - All path arguments must resolve under SAFE_FS_ROOT after canonicalization
|
package/dist/wp-cli.js
CHANGED
|
@@ -83,8 +83,7 @@ const DEFAULT_COMMANDS = [
|
|
|
83
83
|
'core verify-checksums',
|
|
84
84
|
'core language list',
|
|
85
85
|
// DB (read-only introspection only — `db query` stays out; it's arbitrary SQL with no scoping
|
|
86
|
-
// and belongs in a higher-risk tier, tracked separately).
|
|
87
|
-
// opt-in design discussion.
|
|
86
|
+
// and belongs in a higher-risk tier, tracked separately).
|
|
88
87
|
'db columns',
|
|
89
88
|
'db size',
|
|
90
89
|
'db tables',
|
package/dist/wp-client.js
CHANGED
|
@@ -361,6 +361,23 @@ export class WPClient {
|
|
|
361
361
|
typeof result.capabilities !== 'object') {
|
|
362
362
|
result.capabilities = {};
|
|
363
363
|
}
|
|
364
|
+
// ADR-003 / ADR-007 Pro-extension fields. Free-only sites omit
|
|
365
|
+
// them entirely; pre-V1 Pro plugins might emit partial shapes.
|
|
366
|
+
// Normalize defensively so downstream gates can read uniform
|
|
367
|
+
// types without per-call shape checks.
|
|
368
|
+
if (typeof result.pro_active !== 'boolean') {
|
|
369
|
+
result.pro_active = false;
|
|
370
|
+
}
|
|
371
|
+
if (result.available_targets === null ||
|
|
372
|
+
typeof result.available_targets !== 'object' ||
|
|
373
|
+
Array.isArray(result.available_targets)) {
|
|
374
|
+
result.available_targets = {};
|
|
375
|
+
}
|
|
376
|
+
if (result.active_modules === null ||
|
|
377
|
+
typeof result.active_modules !== 'object' ||
|
|
378
|
+
Array.isArray(result.active_modules)) {
|
|
379
|
+
result.active_modules = {};
|
|
380
|
+
}
|
|
364
381
|
return result;
|
|
365
382
|
}
|
|
366
383
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diviops/mcp-server",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.17",
|
|
4
4
|
"description": "MCP server exposing Divi 5 Visual Builder as tools for Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"diviops-preset": "dist/preset-cli/bin.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
|
-
"dist",
|
|
12
|
+
"dist/**/*",
|
|
13
|
+
"!dist/**/__tests__/**",
|
|
13
14
|
"templates",
|
|
14
15
|
"data/verified-attrs.json",
|
|
15
16
|
"data/verified-attrs-backlog.json",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `divi/button` emitter shape coverage:
|
|
3
|
-
* - fixture-based shape assertion against round-3-button-canonical-complete.json
|
|
4
|
-
* - emit-on-specification discriminator (with vs without font.weight)
|
|
5
|
-
* - hover shape (NO `value` wrapper), radius vocabulary, variable tokens,
|
|
6
|
-
* the do-not-emit list, and the opt-in bypass corner.
|
|
7
|
-
*/
|
|
8
|
-
export {};
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `divi/button` emitter shape coverage:
|
|
3
|
-
* - fixture-based shape assertion against round-3-button-canonical-complete.json
|
|
4
|
-
* - emit-on-specification discriminator (with vs without font.weight)
|
|
5
|
-
* - hover shape (NO `value` wrapper), radius vocabulary, variable tokens,
|
|
6
|
-
* the do-not-emit list, and the opt-in bypass corner.
|
|
7
|
-
*/
|
|
8
|
-
import { test } from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { dirname, join } from "node:path";
|
|
13
|
-
import { emitButtonGroupPreset, composeButtonAttrs, buildPresetCreateBody, } from "../button-emitter.js";
|
|
14
|
-
import { loadRegistry } from "../registry.js";
|
|
15
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const REPO_ROOT = join(__dirname, "..", "..", "..", "..");
|
|
17
|
-
const FIXTURE = join(REPO_ROOT, "docs/verification/evidence/canonical-shape-dumps-2026-05-18/round-3-button-canonical-complete.json");
|
|
18
|
-
const registry = loadRegistry();
|
|
19
|
-
test("emitter byte-matches the round-3 canonical button fixture attrs", () => {
|
|
20
|
-
const fixture = JSON.parse(readFileSync(FIXTURE, "utf8"));
|
|
21
|
-
const canonicalAttrs = fixture.canonical_button_primary_preset.attrs;
|
|
22
|
-
const entry = emitButtonGroupPreset({
|
|
23
|
-
name: "canonical-button-primary-vb-2026-05-18",
|
|
24
|
-
bg_color: "gcid-primary-color",
|
|
25
|
-
bg_color_hover: "gcid-secondary-color",
|
|
26
|
-
radius: {
|
|
27
|
-
topLeft: "8px",
|
|
28
|
-
topRight: "8px",
|
|
29
|
-
bottomLeft: "8px",
|
|
30
|
-
bottomRight: "8px",
|
|
31
|
-
},
|
|
32
|
-
font: {
|
|
33
|
-
family: "Inter",
|
|
34
|
-
weight: "600",
|
|
35
|
-
color: "gcid-body-color",
|
|
36
|
-
},
|
|
37
|
-
}, registry);
|
|
38
|
-
assert.deepEqual(entry.attrs, canonicalAttrs, "emitted attrs must byte-match the canonical capture");
|
|
39
|
-
assert.equal(entry.type, "group");
|
|
40
|
-
assert.equal(entry.group_name, "divi/button");
|
|
41
|
-
assert.equal(entry.group_id, "button");
|
|
42
|
-
assert.equal(entry.module_name, "divi/button");
|
|
43
|
-
});
|
|
44
|
-
test("hover bg color emits at desktop.hover.color with NO value wrapper", () => {
|
|
45
|
-
const attrs = composeButtonAttrs({
|
|
46
|
-
name: "hover-only",
|
|
47
|
-
bg_color: "#111111",
|
|
48
|
-
bg_color_hover: "#222222",
|
|
49
|
-
});
|
|
50
|
-
const bg = attrs.button.decoration.background;
|
|
51
|
-
assert.equal(bg.desktop.value.color, "#111111", "desktop is 3-level (value.color)");
|
|
52
|
-
assert.equal(bg.desktop.hover.color, "#222222", "hover is 2-level (hover.color)");
|
|
53
|
-
assert.equal("value" in bg.desktop.hover, false, "hover must NOT carry a `value` wrapper");
|
|
54
|
-
});
|
|
55
|
-
test("emit-on-specification discriminator: font.weight present vs absent", () => {
|
|
56
|
-
const withWeight = composeButtonAttrs({
|
|
57
|
-
name: "with-weight",
|
|
58
|
-
font: { family: "Inter", weight: "600" },
|
|
59
|
-
});
|
|
60
|
-
const withoutWeight = composeButtonAttrs({
|
|
61
|
-
name: "without-weight",
|
|
62
|
-
font: { family: "Inter" },
|
|
63
|
-
});
|
|
64
|
-
const wv = withWeight.button.decoration.font.font.desktop.value;
|
|
65
|
-
const wov = withoutWeight.button.decoration.font.font.desktop.value;
|
|
66
|
-
assert.equal(wv.weight, "600", "weight emitted when specified");
|
|
67
|
-
assert.equal("weight" in wv, true);
|
|
68
|
-
assert.equal("weight" in wov, false, "weight absent when not specified");
|
|
69
|
-
assert.deepEqual(Object.keys(wov), ["family"], "ONLY the touched key emitted");
|
|
70
|
-
});
|
|
71
|
-
test("radius vocabulary is {topLeft,topRight,bottomLeft,bottomRight,sync}", () => {
|
|
72
|
-
const attrs = composeButtonAttrs({
|
|
73
|
-
name: "r",
|
|
74
|
-
radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px", bottomRight: "8px" },
|
|
75
|
-
});
|
|
76
|
-
const radius = attrs.button.decoration.border.desktop.value.radius;
|
|
77
|
-
assert.deepEqual(Object.keys(radius).sort(), [
|
|
78
|
-
"bottomLeft",
|
|
79
|
-
"bottomRight",
|
|
80
|
-
"sync",
|
|
81
|
-
"topLeft",
|
|
82
|
-
"topRight",
|
|
83
|
-
]);
|
|
84
|
-
assert.equal(radius.sync, "on", "equal corners derive sync=on");
|
|
85
|
-
});
|
|
86
|
-
test("radius sync derives off when corners differ; partial corners stay absent", () => {
|
|
87
|
-
const attrs = composeButtonAttrs({
|
|
88
|
-
name: "r",
|
|
89
|
-
radius: { topLeft: "8px", bottomRight: "4px" },
|
|
90
|
-
});
|
|
91
|
-
const radius = attrs.button.decoration.border.desktop.value.radius;
|
|
92
|
-
assert.equal(radius.sync, "off");
|
|
93
|
-
assert.equal("topRight" in radius, false, "untouched corner absent");
|
|
94
|
-
assert.equal("bottomLeft" in radius, false, "untouched corner absent");
|
|
95
|
-
});
|
|
96
|
-
test("radius sync derives off for a single specified corner (Rule 1a)", () => {
|
|
97
|
-
// Per Rule 1a in canonical-preset-shapes: a partial corner set must derive
|
|
98
|
-
// sync="off". A 1-element corner array is trivially "all equal" — auto-
|
|
99
|
-
// deriving "on" is only correct when all 4 corners are present and identical.
|
|
100
|
-
const attrs = composeButtonAttrs({
|
|
101
|
-
name: "r",
|
|
102
|
-
radius: { topLeft: "8px" },
|
|
103
|
-
});
|
|
104
|
-
const radius = attrs.button.decoration.border.desktop.value.radius;
|
|
105
|
-
assert.equal(radius.sync, "off", "single corner must NOT derive sync=on");
|
|
106
|
-
assert.equal("topRight" in radius, false, "untouched corner absent");
|
|
107
|
-
});
|
|
108
|
-
test("radius sync stays off for 3 identical corners; on only with all 4", () => {
|
|
109
|
-
const three = composeButtonAttrs({
|
|
110
|
-
name: "r",
|
|
111
|
-
radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px" },
|
|
112
|
-
});
|
|
113
|
-
assert.equal(three.button.decoration.border.desktop.value.radius.sync, "off", "3 identical corners is still a partial set — sync=off");
|
|
114
|
-
const all = composeButtonAttrs({
|
|
115
|
-
name: "r",
|
|
116
|
-
radius: { topLeft: "8px", topRight: "8px", bottomLeft: "8px", bottomRight: "8px" },
|
|
117
|
-
});
|
|
118
|
-
assert.equal(all.button.decoration.border.desktop.value.radius.sync, "on", "all 4 identical corners derive sync=on");
|
|
119
|
-
});
|
|
120
|
-
test("explicit radius.sync wins over derivation", () => {
|
|
121
|
-
const attrs = composeButtonAttrs({
|
|
122
|
-
name: "r",
|
|
123
|
-
radius: { topLeft: "8px", sync: "on" },
|
|
124
|
-
});
|
|
125
|
-
assert.equal(attrs.button.decoration.border.desktop.value.radius.sync, "on", "an explicitly-passed sync flag is honored regardless of corner count");
|
|
126
|
-
});
|
|
127
|
-
test("variable tokens get the {name,settings} object shape + trailing )$", () => {
|
|
128
|
-
const attrs = composeButtonAttrs({
|
|
129
|
-
name: "v",
|
|
130
|
-
bg_color: "gcid-primary-color",
|
|
131
|
-
});
|
|
132
|
-
const color = attrs.button.decoration.background.desktop.value.color;
|
|
133
|
-
assert.ok(color.endsWith(")$"), "token must end with )$");
|
|
134
|
-
assert.match(color, /^\$variable\(/);
|
|
135
|
-
const payload = JSON.parse(color.slice("$variable(".length, -2));
|
|
136
|
-
assert.deepEqual(payload, {
|
|
137
|
-
type: "color",
|
|
138
|
-
value: { name: "gcid-primary-color", settings: {} },
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
test("literal hex color is emitted verbatim, not wrapped", () => {
|
|
142
|
-
const attrs = composeButtonAttrs({ name: "h", bg_color: "#2563eb" });
|
|
143
|
-
assert.equal(attrs.button.decoration.background.desktop.value.color, "#2563eb");
|
|
144
|
-
});
|
|
145
|
-
test("do-not-emit list: no attrs.font/spacing top-level, no renderAttrs, no button slot", () => {
|
|
146
|
-
const entry = emitButtonGroupPreset({
|
|
147
|
-
name: "clean",
|
|
148
|
-
bg_color: "#111",
|
|
149
|
-
font: { family: "Inter" },
|
|
150
|
-
}, registry);
|
|
151
|
-
assert.equal("font" in entry.attrs, false, "no top-level attrs.font");
|
|
152
|
-
assert.equal("spacing" in entry.attrs, false, "no top-level attrs.spacing");
|
|
153
|
-
assert.equal("renderAttrs" in entry, false, "no renderAttrs");
|
|
154
|
-
const decoration = entry.attrs.button.decoration;
|
|
155
|
-
assert.equal("button" in decoration, false, "no button.decoration.button slot by default");
|
|
156
|
-
});
|
|
157
|
-
test("bypass_hover_padding_gate emits ONLY the padding.top corner", () => {
|
|
158
|
-
const attrs = composeButtonAttrs({
|
|
159
|
-
name: "bypass",
|
|
160
|
-
bg_color: "#111",
|
|
161
|
-
bypass_hover_padding_gate: true,
|
|
162
|
-
});
|
|
163
|
-
const buttonSlot = attrs.button.decoration.button;
|
|
164
|
-
assert.deepEqual(buttonSlot, {
|
|
165
|
-
desktop: { value: { padding: { top: "0px" } } },
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
test("emitter rejects an empty preset (no styling specified)", () => {
|
|
169
|
-
assert.throws(() => emitButtonGroupPreset({ name: "empty" }, registry), /empty preset/);
|
|
170
|
-
});
|
|
171
|
-
test("emitter rejects a missing name", () => {
|
|
172
|
-
assert.throws(() => emitButtonGroupPreset({ name: "" }, registry), /requires a non-empty `name`/);
|
|
173
|
-
});
|
|
174
|
-
test("buildPresetCreateBody mirrors the diviops_preset_create body shape", () => {
|
|
175
|
-
const entry = emitButtonGroupPreset({ name: "Primary", bg_color: "#111" }, registry);
|
|
176
|
-
const body = buildPresetCreateBody(entry, { dry_run: true });
|
|
177
|
-
assert.deepEqual(body, {
|
|
178
|
-
module_name: "divi/button",
|
|
179
|
-
name: "Primary",
|
|
180
|
-
attrs: entry.attrs,
|
|
181
|
-
type: "group",
|
|
182
|
-
group_name: "divi/button",
|
|
183
|
-
group_id: "button",
|
|
184
|
-
dry_run: true,
|
|
185
|
-
});
|
|
186
|
-
const noDry = buildPresetCreateBody(entry);
|
|
187
|
-
assert.equal("dry_run" in noDry, false, "dry_run omitted when not requested");
|
|
188
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI integration coverage: --help, arg parsing, structured exit codes,
|
|
3
|
-
* dry-run output, evidence-gate exit, capability-missing exit.
|
|
4
|
-
*
|
|
5
|
-
* Apply-mode here is exercised through the credential-missing path only
|
|
6
|
-
* (no live write — #725 AC #8). The mocked write-path proper lives in
|
|
7
|
-
* write-path.test.ts.
|
|
8
|
-
*/
|
|
9
|
-
export {};
|