@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
|
@@ -1,791 +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
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { run, parseArgs, buildButtonInput, EXIT, UsageError } from "../cli.js";
|
|
12
|
-
/** Capture CLI output without touching real stdio. */
|
|
13
|
-
function capture() {
|
|
14
|
-
const stdout = [];
|
|
15
|
-
const stderr = [];
|
|
16
|
-
return {
|
|
17
|
-
stdout,
|
|
18
|
-
stderr,
|
|
19
|
-
out: (t) => stdout.push(t),
|
|
20
|
-
err: (t) => stderr.push(t),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
test("--help prints usage and exits 0", async () => {
|
|
24
|
-
const io = capture();
|
|
25
|
-
const code = await run(["--help"], io);
|
|
26
|
-
assert.equal(code, EXIT.OK);
|
|
27
|
-
assert.match(io.stdout.join("\n"), /diviops-preset/);
|
|
28
|
-
assert.match(io.stdout.join("\n"), /EXIT CODES/);
|
|
29
|
-
});
|
|
30
|
-
test("no args prints help and exits 0", async () => {
|
|
31
|
-
const io = capture();
|
|
32
|
-
const code = await run([], io);
|
|
33
|
-
assert.equal(code, EXIT.OK);
|
|
34
|
-
assert.match(io.stdout.join("\n"), /USAGE/);
|
|
35
|
-
});
|
|
36
|
-
test("unknown command exits 1 (invalid input)", async () => {
|
|
37
|
-
const io = capture();
|
|
38
|
-
const code = await run(["section", "--name", "X"], io);
|
|
39
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
40
|
-
assert.match(io.stderr.join("\n"), /Unknown command/);
|
|
41
|
-
});
|
|
42
|
-
test("--help advertises the heading-font command", async () => {
|
|
43
|
-
const io = capture();
|
|
44
|
-
const code = await run(["--help"], io);
|
|
45
|
-
assert.equal(code, EXIT.OK);
|
|
46
|
-
const help = io.stdout.join("\n");
|
|
47
|
-
assert.match(help, /heading-font/);
|
|
48
|
-
assert.match(help, /--pattern <google\|local>/);
|
|
49
|
-
});
|
|
50
|
-
test("unknown flag exits 1 (invalid input)", async () => {
|
|
51
|
-
const io = capture();
|
|
52
|
-
const code = await run(["button", "--name", "X", "--bogus", "y"], io);
|
|
53
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
54
|
-
assert.match(io.stderr.join("\n"), /Unknown flag/);
|
|
55
|
-
});
|
|
56
|
-
test("button without --name exits 1", async () => {
|
|
57
|
-
const io = capture();
|
|
58
|
-
const code = await run(["button", "--bg-color", "#111"], io);
|
|
59
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
60
|
-
assert.match(io.stderr.join("\n"), /requires --name/);
|
|
61
|
-
});
|
|
62
|
-
test("dry-run is the default and prints canonical JSON, exit 0", async () => {
|
|
63
|
-
const io = capture();
|
|
64
|
-
const code = await run(["button", "--name", "Primary", "--bg-color", "gcid-primary-color"], io);
|
|
65
|
-
assert.equal(code, EXIT.OK);
|
|
66
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
67
|
-
assert.equal(parsed.type, "group");
|
|
68
|
-
assert.equal(parsed.dry_run, true);
|
|
69
|
-
assert.equal(parsed.module_name, "divi/button");
|
|
70
|
-
assert.equal(parsed.attrs.button.decoration.background.desktop.value.color, '$variable({"type":"color","value":{"name":"gcid-primary-color","settings":{}}})$');
|
|
71
|
-
});
|
|
72
|
-
test("explicit --dry-run behaves the same as the default", async () => {
|
|
73
|
-
const io = capture();
|
|
74
|
-
const code = await run(["button", "--name", "P", "--bg-color", "#111", "--dry-run"], io);
|
|
75
|
-
assert.equal(code, EXIT.OK);
|
|
76
|
-
assert.equal(JSON.parse(io.stdout.join("\n")).dry_run, true);
|
|
77
|
-
});
|
|
78
|
-
test("dry-run requires no credentials and no network", async () => {
|
|
79
|
-
// No WP_* env vars set in this assertion's expectation: a clean dry-run
|
|
80
|
-
// run must not throw a CredentialsMissingError.
|
|
81
|
-
const io = capture();
|
|
82
|
-
const code = await run(["button", "--name", "P", "--bg-color", "#111"], io);
|
|
83
|
-
assert.equal(code, EXIT.OK);
|
|
84
|
-
assert.equal(io.stderr.length, 0, "no error output on a credential-free dry-run");
|
|
85
|
-
});
|
|
86
|
-
test("--apply without credentials exits 1 with a credentials hint", async () => {
|
|
87
|
-
const saved = {
|
|
88
|
-
WP_URL: process.env.WP_URL,
|
|
89
|
-
WP_USER: process.env.WP_USER,
|
|
90
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
91
|
-
};
|
|
92
|
-
delete process.env.WP_URL;
|
|
93
|
-
delete process.env.WP_USER;
|
|
94
|
-
delete process.env.WP_APP_PASSWORD;
|
|
95
|
-
try {
|
|
96
|
-
const io = capture();
|
|
97
|
-
const code = await run(["button", "--name", "P", "--bg-color", "#111", "--apply"], io);
|
|
98
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
99
|
-
assert.match(io.stderr.join("\n"), /requires WordPress credentials/);
|
|
100
|
-
}
|
|
101
|
-
finally {
|
|
102
|
-
if (saved.WP_URL !== undefined)
|
|
103
|
-
process.env.WP_URL = saved.WP_URL;
|
|
104
|
-
if (saved.WP_USER !== undefined)
|
|
105
|
-
process.env.WP_USER = saved.WP_USER;
|
|
106
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
107
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
test("--apply and --dry-run together exit 1 (mutually exclusive)", async () => {
|
|
111
|
-
const io = capture();
|
|
112
|
-
const code = await run(["button", "--name", "P", "--apply", "--dry-run"], io);
|
|
113
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
114
|
-
assert.match(io.stderr.join("\n"), /mutually exclusive/);
|
|
115
|
-
});
|
|
116
|
-
test("parseArgs: --radius shorthand and per-corner overrides", () => {
|
|
117
|
-
const parsed = parseArgs([
|
|
118
|
-
"button",
|
|
119
|
-
"--name",
|
|
120
|
-
"P",
|
|
121
|
-
"--radius",
|
|
122
|
-
"8px",
|
|
123
|
-
"--radius-top-left",
|
|
124
|
-
"12px",
|
|
125
|
-
]);
|
|
126
|
-
const input = buildButtonInput(parsed);
|
|
127
|
-
assert.deepEqual(input.radius, {
|
|
128
|
-
topLeft: "12px",
|
|
129
|
-
topRight: "8px",
|
|
130
|
-
bottomLeft: "8px",
|
|
131
|
-
bottomRight: "8px",
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
test("parseArgs: --bypass-hover-padding-gate sets the opt-in flag", () => {
|
|
135
|
-
const parsed = parseArgs(["button", "--name", "P", "--bypass-hover-padding-gate"]);
|
|
136
|
-
const input = buildButtonInput(parsed);
|
|
137
|
-
assert.equal(input.bypass_hover_padding_gate, true);
|
|
138
|
-
});
|
|
139
|
-
test("parseArgs: value flag without a value throws UsageError", () => {
|
|
140
|
-
assert.throws(() => parseArgs(["button", "--name"]), (err) => {
|
|
141
|
-
assert.ok(err instanceof UsageError);
|
|
142
|
-
return true;
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
test("parseArgs: --radius-sync rejects values outside on|off", () => {
|
|
146
|
-
const parsed = parseArgs(["button", "--name", "P", "--radius-sync", "maybe"]);
|
|
147
|
-
assert.throws(() => buildButtonInput(parsed), /radius-sync must be/);
|
|
148
|
-
});
|
|
149
|
-
// ------------------------------------------------------------------
|
|
150
|
-
// heading-font command — CLI integration (parse → emit → dry-run JSON)
|
|
151
|
-
// ------------------------------------------------------------------
|
|
152
|
-
test("heading-font dry-run (Pattern A) emits canonical JSON, exit 0", async () => {
|
|
153
|
-
const io = capture();
|
|
154
|
-
const code = await run([
|
|
155
|
-
"heading-font",
|
|
156
|
-
"--name",
|
|
157
|
-
"H1",
|
|
158
|
-
"--pattern",
|
|
159
|
-
"google",
|
|
160
|
-
"--font-family",
|
|
161
|
-
"Inter",
|
|
162
|
-
"--font-weight",
|
|
163
|
-
"700",
|
|
164
|
-
"--font-color",
|
|
165
|
-
"gcid-heading-color",
|
|
166
|
-
"--font-size",
|
|
167
|
-
"48px",
|
|
168
|
-
], io);
|
|
169
|
-
assert.equal(code, EXIT.OK);
|
|
170
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
171
|
-
assert.equal(parsed.type, "group");
|
|
172
|
-
assert.equal(parsed.dry_run, true);
|
|
173
|
-
assert.equal(parsed.module_name, "divi/heading");
|
|
174
|
-
assert.equal(parsed.group_name, "divi/font");
|
|
175
|
-
assert.equal(parsed.group_id, "designTitleText");
|
|
176
|
-
const value = parsed.attrs.title.decoration.font.font.desktop.value;
|
|
177
|
-
assert.equal(value.family, "Inter");
|
|
178
|
-
assert.equal(value.weight, "700");
|
|
179
|
-
assert.equal(value.size, "48px");
|
|
180
|
-
assert.equal(value.color, '$variable({"type":"color","value":{"name":"gcid-heading-color","settings":{}}})$');
|
|
181
|
-
});
|
|
182
|
-
test("heading-font dry-run (Pattern B) emits no `weight` key", async () => {
|
|
183
|
-
const io = capture();
|
|
184
|
-
const code = await run([
|
|
185
|
-
"heading-font",
|
|
186
|
-
"--name",
|
|
187
|
-
"H1-local",
|
|
188
|
-
"--pattern",
|
|
189
|
-
"local",
|
|
190
|
-
"--font-family",
|
|
191
|
-
"Sora 700",
|
|
192
|
-
"--font-color",
|
|
193
|
-
"gcid-heading-color",
|
|
194
|
-
"--font-size",
|
|
195
|
-
"48px",
|
|
196
|
-
], io);
|
|
197
|
-
assert.equal(code, EXIT.OK);
|
|
198
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
199
|
-
const value = parsed.attrs.title.decoration.font.font.desktop.value;
|
|
200
|
-
assert.equal(value.family, "Sora 700");
|
|
201
|
-
assert.equal("weight" in value, false, "Pattern B emits no weight key");
|
|
202
|
-
});
|
|
203
|
-
test("heading-font without --pattern exits 1 (invalid input)", async () => {
|
|
204
|
-
const io = capture();
|
|
205
|
-
const code = await run([
|
|
206
|
-
"heading-font",
|
|
207
|
-
"--name",
|
|
208
|
-
"H1",
|
|
209
|
-
"--font-family",
|
|
210
|
-
"Inter",
|
|
211
|
-
"--font-weight",
|
|
212
|
-
"700",
|
|
213
|
-
], io);
|
|
214
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
215
|
-
assert.match(io.stderr.join("\n"), /--pattern/);
|
|
216
|
-
assert.match(io.stderr.join("\n"), /google\|local/);
|
|
217
|
-
});
|
|
218
|
-
test("heading-font without --name exits 1", async () => {
|
|
219
|
-
const io = capture();
|
|
220
|
-
const code = await run(["heading-font", "--pattern", "google", "--font-family", "Inter"], io);
|
|
221
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
222
|
-
assert.match(io.stderr.join("\n"), /requires --name/);
|
|
223
|
-
});
|
|
224
|
-
test("heading-font --pattern local + --font-weight is refused (exit 1)", async () => {
|
|
225
|
-
const io = capture();
|
|
226
|
-
const code = await run([
|
|
227
|
-
"heading-font",
|
|
228
|
-
"--name",
|
|
229
|
-
"H1-bad",
|
|
230
|
-
"--pattern",
|
|
231
|
-
"local",
|
|
232
|
-
"--font-family",
|
|
233
|
-
"Sora 700",
|
|
234
|
-
"--font-weight",
|
|
235
|
-
"700",
|
|
236
|
-
], io);
|
|
237
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
238
|
-
assert.match(io.stderr.join("\n"), /Pattern B/);
|
|
239
|
-
});
|
|
240
|
-
test("heading-font --pattern with an invalid value exits 1", async () => {
|
|
241
|
-
const io = capture();
|
|
242
|
-
const code = await run([
|
|
243
|
-
"heading-font",
|
|
244
|
-
"--name",
|
|
245
|
-
"H1",
|
|
246
|
-
"--pattern",
|
|
247
|
-
"auto",
|
|
248
|
-
"--font-family",
|
|
249
|
-
"Inter",
|
|
250
|
-
], io);
|
|
251
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
252
|
-
assert.match(io.stderr.join("\n"), /--pattern must be/);
|
|
253
|
-
});
|
|
254
|
-
test("heading-font dry-run requires no credentials and no network", async () => {
|
|
255
|
-
// Mirrors the dry-run-no-creds button assertion: a heading-font dry-run
|
|
256
|
-
// must not throw a CredentialsMissingError. Exercises AC: --apply is
|
|
257
|
-
// the only path that touches credentials/handshake/network.
|
|
258
|
-
const saved = {
|
|
259
|
-
WP_URL: process.env.WP_URL,
|
|
260
|
-
WP_USER: process.env.WP_USER,
|
|
261
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
262
|
-
};
|
|
263
|
-
delete process.env.WP_URL;
|
|
264
|
-
delete process.env.WP_USER;
|
|
265
|
-
delete process.env.WP_APP_PASSWORD;
|
|
266
|
-
try {
|
|
267
|
-
const io = capture();
|
|
268
|
-
const code = await run([
|
|
269
|
-
"heading-font",
|
|
270
|
-
"--name",
|
|
271
|
-
"H1",
|
|
272
|
-
"--pattern",
|
|
273
|
-
"google",
|
|
274
|
-
"--font-family",
|
|
275
|
-
"Inter",
|
|
276
|
-
], io);
|
|
277
|
-
assert.equal(code, EXIT.OK);
|
|
278
|
-
assert.equal(io.stderr.length, 0, "no error output on credential-free dry-run");
|
|
279
|
-
}
|
|
280
|
-
finally {
|
|
281
|
-
if (saved.WP_URL !== undefined)
|
|
282
|
-
process.env.WP_URL = saved.WP_URL;
|
|
283
|
-
if (saved.WP_USER !== undefined)
|
|
284
|
-
process.env.WP_USER = saved.WP_USER;
|
|
285
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
286
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
test("heading-font --apply without credentials exits 1 with a credentials hint", async () => {
|
|
290
|
-
const saved = {
|
|
291
|
-
WP_URL: process.env.WP_URL,
|
|
292
|
-
WP_USER: process.env.WP_USER,
|
|
293
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
294
|
-
};
|
|
295
|
-
delete process.env.WP_URL;
|
|
296
|
-
delete process.env.WP_USER;
|
|
297
|
-
delete process.env.WP_APP_PASSWORD;
|
|
298
|
-
try {
|
|
299
|
-
const io = capture();
|
|
300
|
-
const code = await run([
|
|
301
|
-
"heading-font",
|
|
302
|
-
"--name",
|
|
303
|
-
"H1",
|
|
304
|
-
"--pattern",
|
|
305
|
-
"google",
|
|
306
|
-
"--font-family",
|
|
307
|
-
"Inter",
|
|
308
|
-
"--apply",
|
|
309
|
-
], io);
|
|
310
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
311
|
-
assert.match(io.stderr.join("\n"), /requires WordPress credentials/);
|
|
312
|
-
}
|
|
313
|
-
finally {
|
|
314
|
-
if (saved.WP_URL !== undefined)
|
|
315
|
-
process.env.WP_URL = saved.WP_URL;
|
|
316
|
-
if (saved.WP_USER !== undefined)
|
|
317
|
-
process.env.WP_USER = saved.WP_USER;
|
|
318
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
319
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
// ------------------------------------------------------------------
|
|
323
|
-
// text-body-font command — CLI integration (parse → emit → dry-run JSON)
|
|
324
|
-
// Track 6: Pattern A only; Pattern B refused via registry-absence.
|
|
325
|
-
// ------------------------------------------------------------------
|
|
326
|
-
test("--help advertises the text-body-font command (Pattern A only)", async () => {
|
|
327
|
-
const io = capture();
|
|
328
|
-
const code = await run(["--help"], io);
|
|
329
|
-
assert.equal(code, EXIT.OK);
|
|
330
|
-
const help = io.stdout.join("\n");
|
|
331
|
-
assert.match(help, /text-body-font/);
|
|
332
|
-
assert.match(help, /Pattern A/);
|
|
333
|
-
});
|
|
334
|
-
test("text-body-font dry-run (Pattern A) emits canonical JSON, exit 0", async () => {
|
|
335
|
-
const io = capture();
|
|
336
|
-
const code = await run([
|
|
337
|
-
"text-body-font",
|
|
338
|
-
"--name",
|
|
339
|
-
"Body",
|
|
340
|
-
"--pattern",
|
|
341
|
-
"google",
|
|
342
|
-
"--font-family",
|
|
343
|
-
"Inter",
|
|
344
|
-
"--font-weight",
|
|
345
|
-
"400",
|
|
346
|
-
"--font-color",
|
|
347
|
-
"gcid-body-color",
|
|
348
|
-
"--font-size",
|
|
349
|
-
"16px",
|
|
350
|
-
], io);
|
|
351
|
-
assert.equal(code, EXIT.OK);
|
|
352
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
353
|
-
assert.equal(parsed.type, "group");
|
|
354
|
-
assert.equal(parsed.dry_run, true);
|
|
355
|
-
assert.equal(parsed.module_name, "divi/text");
|
|
356
|
-
assert.equal(parsed.group_name, "divi/font-body");
|
|
357
|
-
assert.equal(parsed.group_id, "designText");
|
|
358
|
-
const value = parsed.attrs.content.decoration.bodyFont.body.font.desktop.value;
|
|
359
|
-
assert.equal(value.family, "Inter");
|
|
360
|
-
assert.equal(value.weight, "400");
|
|
361
|
-
assert.equal(value.size, "16px");
|
|
362
|
-
assert.equal(value.color, '$variable({"type":"color","value":{"name":"gcid-body-color","settings":{}}})$');
|
|
363
|
-
});
|
|
364
|
-
test("text-body-font dry-run omits weight/size/lineHeight keys when not specified", async () => {
|
|
365
|
-
const io = capture();
|
|
366
|
-
const code = await run([
|
|
367
|
-
"text-body-font",
|
|
368
|
-
"--name",
|
|
369
|
-
"Body",
|
|
370
|
-
"--pattern",
|
|
371
|
-
"google",
|
|
372
|
-
"--font-family",
|
|
373
|
-
"Inter",
|
|
374
|
-
], io);
|
|
375
|
-
assert.equal(code, EXIT.OK);
|
|
376
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
377
|
-
const value = parsed.attrs.content.decoration.bodyFont.body.font.desktop.value;
|
|
378
|
-
assert.deepEqual(Object.keys(value), ["family"]);
|
|
379
|
-
});
|
|
380
|
-
test("text-body-font without --pattern exits 1 (invalid input)", async () => {
|
|
381
|
-
const io = capture();
|
|
382
|
-
const code = await run([
|
|
383
|
-
"text-body-font",
|
|
384
|
-
"--name",
|
|
385
|
-
"Body",
|
|
386
|
-
"--font-family",
|
|
387
|
-
"Inter",
|
|
388
|
-
"--font-weight",
|
|
389
|
-
"400",
|
|
390
|
-
], io);
|
|
391
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
392
|
-
assert.match(io.stderr.join("\n"), /--pattern/);
|
|
393
|
-
assert.match(io.stderr.join("\n"), /google\|local/);
|
|
394
|
-
});
|
|
395
|
-
test("text-body-font without --name exits 1", async () => {
|
|
396
|
-
const io = capture();
|
|
397
|
-
const code = await run(["text-body-font", "--pattern", "google", "--font-family", "Inter"], io);
|
|
398
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
399
|
-
assert.match(io.stderr.join("\n"), /requires --name/);
|
|
400
|
-
});
|
|
401
|
-
test("text-body-font --pattern local is refused (registry-absence), exit 2", async () => {
|
|
402
|
-
// Track 6 contract: Pattern B has NO registry entry for `divi/font-body`.
|
|
403
|
-
// The CLI must exit non-zero. The registry-absence throw is NOT an
|
|
404
|
-
// EvidenceGateError (no resolution constructed) — it surfaces as a
|
|
405
|
-
// plain Error and the CLI's fallback branch returns INVALID_INPUT (1).
|
|
406
|
-
// What matters here is: (a) non-zero exit; (b) the error message names
|
|
407
|
-
// both family and variant so the operator can file the gap.
|
|
408
|
-
const io = capture();
|
|
409
|
-
const code = await run([
|
|
410
|
-
"text-body-font",
|
|
411
|
-
"--name",
|
|
412
|
-
"Body",
|
|
413
|
-
"--pattern",
|
|
414
|
-
"local",
|
|
415
|
-
"--font-family",
|
|
416
|
-
"Inter",
|
|
417
|
-
"--font-color",
|
|
418
|
-
"#666666",
|
|
419
|
-
], io);
|
|
420
|
-
assert.notEqual(code, EXIT.OK, "refusal MUST exit non-zero");
|
|
421
|
-
const err = io.stderr.join("\n");
|
|
422
|
-
assert.match(err, /absent from verified-attrs\.json/);
|
|
423
|
-
assert.match(err, /divi\/font-body/);
|
|
424
|
-
assert.match(err, /local_hosted_pattern_b/);
|
|
425
|
-
});
|
|
426
|
-
test("text-body-font --pattern local --font-weight is ALSO refused (compound input)", async () => {
|
|
427
|
-
// Compound-input parity: adding --font-weight (or any other field) does
|
|
428
|
-
// NOT change the refusal — the gate fires on the missing variant entry
|
|
429
|
-
// regardless of which other fields are set.
|
|
430
|
-
const io = capture();
|
|
431
|
-
const code = await run([
|
|
432
|
-
"text-body-font",
|
|
433
|
-
"--name",
|
|
434
|
-
"Body",
|
|
435
|
-
"--pattern",
|
|
436
|
-
"local",
|
|
437
|
-
"--font-family",
|
|
438
|
-
"Inter",
|
|
439
|
-
"--font-weight",
|
|
440
|
-
"700",
|
|
441
|
-
], io);
|
|
442
|
-
assert.notEqual(code, EXIT.OK);
|
|
443
|
-
const err = io.stderr.join("\n");
|
|
444
|
-
assert.match(err, /absent from verified-attrs\.json/);
|
|
445
|
-
assert.match(err, /local_hosted_pattern_b/);
|
|
446
|
-
});
|
|
447
|
-
test("text-body-font --pattern with an invalid value exits 1", async () => {
|
|
448
|
-
const io = capture();
|
|
449
|
-
const code = await run([
|
|
450
|
-
"text-body-font",
|
|
451
|
-
"--name",
|
|
452
|
-
"Body",
|
|
453
|
-
"--pattern",
|
|
454
|
-
"auto",
|
|
455
|
-
"--font-family",
|
|
456
|
-
"Inter",
|
|
457
|
-
], io);
|
|
458
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
459
|
-
assert.match(io.stderr.join("\n"), /--pattern must be/);
|
|
460
|
-
});
|
|
461
|
-
test("text-body-font dry-run requires no credentials and no network", async () => {
|
|
462
|
-
const saved = {
|
|
463
|
-
WP_URL: process.env.WP_URL,
|
|
464
|
-
WP_USER: process.env.WP_USER,
|
|
465
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
466
|
-
};
|
|
467
|
-
delete process.env.WP_URL;
|
|
468
|
-
delete process.env.WP_USER;
|
|
469
|
-
delete process.env.WP_APP_PASSWORD;
|
|
470
|
-
try {
|
|
471
|
-
const io = capture();
|
|
472
|
-
const code = await run([
|
|
473
|
-
"text-body-font",
|
|
474
|
-
"--name",
|
|
475
|
-
"Body",
|
|
476
|
-
"--pattern",
|
|
477
|
-
"google",
|
|
478
|
-
"--font-family",
|
|
479
|
-
"Inter",
|
|
480
|
-
], io);
|
|
481
|
-
assert.equal(code, EXIT.OK);
|
|
482
|
-
assert.equal(io.stderr.length, 0, "no error output on credential-free dry-run");
|
|
483
|
-
}
|
|
484
|
-
finally {
|
|
485
|
-
if (saved.WP_URL !== undefined)
|
|
486
|
-
process.env.WP_URL = saved.WP_URL;
|
|
487
|
-
if (saved.WP_USER !== undefined)
|
|
488
|
-
process.env.WP_USER = saved.WP_USER;
|
|
489
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
490
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
test("text-body-font --apply without credentials exits 1 with a credentials hint", async () => {
|
|
494
|
-
const saved = {
|
|
495
|
-
WP_URL: process.env.WP_URL,
|
|
496
|
-
WP_USER: process.env.WP_USER,
|
|
497
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
498
|
-
};
|
|
499
|
-
delete process.env.WP_URL;
|
|
500
|
-
delete process.env.WP_USER;
|
|
501
|
-
delete process.env.WP_APP_PASSWORD;
|
|
502
|
-
try {
|
|
503
|
-
const io = capture();
|
|
504
|
-
const code = await run([
|
|
505
|
-
"text-body-font",
|
|
506
|
-
"--name",
|
|
507
|
-
"Body",
|
|
508
|
-
"--pattern",
|
|
509
|
-
"google",
|
|
510
|
-
"--font-family",
|
|
511
|
-
"Inter",
|
|
512
|
-
"--apply",
|
|
513
|
-
], io);
|
|
514
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
515
|
-
assert.match(io.stderr.join("\n"), /requires WordPress credentials/);
|
|
516
|
-
}
|
|
517
|
-
finally {
|
|
518
|
-
if (saved.WP_URL !== undefined)
|
|
519
|
-
process.env.WP_URL = saved.WP_URL;
|
|
520
|
-
if (saved.WP_USER !== undefined)
|
|
521
|
-
process.env.WP_USER = saved.WP_USER;
|
|
522
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
523
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
test("dry-run output includes the bypass corner when requested", async () => {
|
|
527
|
-
const io = capture();
|
|
528
|
-
const code = await run(["button", "--name", "P", "--bg-color", "#111", "--bypass-hover-padding-gate"], io);
|
|
529
|
-
assert.equal(code, EXIT.OK);
|
|
530
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
531
|
-
assert.deepEqual(parsed.attrs.button.decoration.button, {
|
|
532
|
-
desktop: { value: { padding: { top: "0px" } } },
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
// ------------------------------------------------------------------
|
|
536
|
-
// spacing command — CLI integration (parse → emit → dry-run JSON)
|
|
537
|
-
// Track 7b: divi/section only; other modules refused by registry gate.
|
|
538
|
-
// ------------------------------------------------------------------
|
|
539
|
-
test("--help advertises the spacing command (divi/section only caveat)", async () => {
|
|
540
|
-
const io = capture();
|
|
541
|
-
const code = await run(["--help"], io);
|
|
542
|
-
assert.equal(code, EXIT.OK);
|
|
543
|
-
const help = io.stdout.join("\n");
|
|
544
|
-
assert.match(help, /spacing/);
|
|
545
|
-
assert.match(help, /divi\/section/);
|
|
546
|
-
});
|
|
547
|
-
test("spacing dry-run emits canonical sparse-emit JSON, exit 0", async () => {
|
|
548
|
-
const io = capture();
|
|
549
|
-
const code = await run([
|
|
550
|
-
"spacing",
|
|
551
|
-
"--name",
|
|
552
|
-
"Section Rhythm",
|
|
553
|
-
"--module",
|
|
554
|
-
"divi/section",
|
|
555
|
-
"--padding-top",
|
|
556
|
-
"40px",
|
|
557
|
-
], io);
|
|
558
|
-
assert.equal(code, EXIT.OK);
|
|
559
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
560
|
-
assert.equal(parsed.type, "group");
|
|
561
|
-
assert.equal(parsed.dry_run, true);
|
|
562
|
-
assert.equal(parsed.module_name, "divi/section");
|
|
563
|
-
assert.equal(parsed.group_name, "divi/spacing");
|
|
564
|
-
assert.equal(parsed.group_id, "designSpacing");
|
|
565
|
-
assert.equal(parsed.primary_attr_name, "module");
|
|
566
|
-
const value = parsed.attrs.module.decoration.spacing.desktop.value;
|
|
567
|
-
assert.deepEqual(value.padding, {
|
|
568
|
-
top: "40px",
|
|
569
|
-
syncVertical: "off",
|
|
570
|
-
syncHorizontal: "off",
|
|
571
|
-
});
|
|
572
|
-
assert.equal("margin" in value, false, "no margin bag when only padding flag passed");
|
|
573
|
-
});
|
|
574
|
-
test("spacing dry-run emits both bags when padding + margin flags passed", async () => {
|
|
575
|
-
const io = capture();
|
|
576
|
-
const code = await run([
|
|
577
|
-
"spacing",
|
|
578
|
-
"--name",
|
|
579
|
-
"Both",
|
|
580
|
-
"--module",
|
|
581
|
-
"divi/section",
|
|
582
|
-
"--padding-top",
|
|
583
|
-
"40px",
|
|
584
|
-
"--margin-bottom",
|
|
585
|
-
"80px",
|
|
586
|
-
], io);
|
|
587
|
-
assert.equal(code, EXIT.OK);
|
|
588
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
589
|
-
const value = parsed.attrs.module.decoration.spacing.desktop.value;
|
|
590
|
-
assert.deepEqual(value.padding, {
|
|
591
|
-
top: "40px",
|
|
592
|
-
syncVertical: "off",
|
|
593
|
-
syncHorizontal: "off",
|
|
594
|
-
});
|
|
595
|
-
assert.deepEqual(value.margin, {
|
|
596
|
-
bottom: "80px",
|
|
597
|
-
syncVertical: "off",
|
|
598
|
-
syncHorizontal: "off",
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
test("spacing honors --padding-sync-vertical on", async () => {
|
|
602
|
-
const io = capture();
|
|
603
|
-
const code = await run([
|
|
604
|
-
"spacing",
|
|
605
|
-
"--name",
|
|
606
|
-
"Sync",
|
|
607
|
-
"--module",
|
|
608
|
-
"divi/section",
|
|
609
|
-
"--padding-top",
|
|
610
|
-
"40px",
|
|
611
|
-
"--padding-bottom",
|
|
612
|
-
"40px",
|
|
613
|
-
"--padding-sync-vertical",
|
|
614
|
-
"on",
|
|
615
|
-
], io);
|
|
616
|
-
assert.equal(code, EXIT.OK);
|
|
617
|
-
const parsed = JSON.parse(io.stdout.join("\n"));
|
|
618
|
-
const padding = parsed.attrs.module.decoration.spacing.desktop.value.padding;
|
|
619
|
-
assert.equal(padding.syncVertical, "on");
|
|
620
|
-
assert.equal(padding.syncHorizontal, "off");
|
|
621
|
-
});
|
|
622
|
-
test("spacing without --name exits 1", async () => {
|
|
623
|
-
const io = capture();
|
|
624
|
-
const code = await run(["spacing", "--module", "divi/section", "--padding-top", "40px"], io);
|
|
625
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
626
|
-
assert.match(io.stderr.join("\n"), /requires --name/);
|
|
627
|
-
});
|
|
628
|
-
test("spacing without --module exits 1", async () => {
|
|
629
|
-
const io = capture();
|
|
630
|
-
const code = await run(["spacing", "--name", "X", "--padding-top", "40px"], io);
|
|
631
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
632
|
-
assert.match(io.stderr.join("\n"), /requires --module/);
|
|
633
|
-
});
|
|
634
|
-
test("spacing with no corners exits non-zero (empty preset)", async () => {
|
|
635
|
-
const io = capture();
|
|
636
|
-
const code = await run(["spacing", "--name", "X", "--module", "divi/section"], io);
|
|
637
|
-
assert.notEqual(code, EXIT.OK);
|
|
638
|
-
assert.match(io.stderr.join("\n"), /empty preset/);
|
|
639
|
-
});
|
|
640
|
-
test("spacing --module divi/heading is refused by evidence gate, exit 2", async () => {
|
|
641
|
-
const io = capture();
|
|
642
|
-
const code = await run([
|
|
643
|
-
"spacing",
|
|
644
|
-
"--name",
|
|
645
|
-
"H",
|
|
646
|
-
"--module",
|
|
647
|
-
"divi/heading",
|
|
648
|
-
"--padding-top",
|
|
649
|
-
"40px",
|
|
650
|
-
], io);
|
|
651
|
-
assert.equal(code, EXIT.EVIDENCE_GATE);
|
|
652
|
-
const err = io.stderr.join("\n");
|
|
653
|
-
assert.match(err, /Evidence-gate refusal/);
|
|
654
|
-
assert.match(err, /divi\/heading/);
|
|
655
|
-
assert.match(err, /divi\/spacing/);
|
|
656
|
-
});
|
|
657
|
-
test("spacing --module divi/text is refused (exit 2)", async () => {
|
|
658
|
-
const io = capture();
|
|
659
|
-
const code = await run([
|
|
660
|
-
"spacing",
|
|
661
|
-
"--name",
|
|
662
|
-
"T",
|
|
663
|
-
"--module",
|
|
664
|
-
"divi/text",
|
|
665
|
-
"--padding-top",
|
|
666
|
-
"40px",
|
|
667
|
-
], io);
|
|
668
|
-
assert.equal(code, EXIT.EVIDENCE_GATE);
|
|
669
|
-
});
|
|
670
|
-
test("spacing --module divi/button is refused (exit 2)", async () => {
|
|
671
|
-
const io = capture();
|
|
672
|
-
const code = await run([
|
|
673
|
-
"spacing",
|
|
674
|
-
"--name",
|
|
675
|
-
"B",
|
|
676
|
-
"--module",
|
|
677
|
-
"divi/button",
|
|
678
|
-
"--padding-top",
|
|
679
|
-
"40px",
|
|
680
|
-
], io);
|
|
681
|
-
assert.equal(code, EXIT.EVIDENCE_GATE);
|
|
682
|
-
});
|
|
683
|
-
test("spacing rejects bare gvid-* token in --padding-top", async () => {
|
|
684
|
-
const io = capture();
|
|
685
|
-
const code = await run([
|
|
686
|
-
"spacing",
|
|
687
|
-
"--name",
|
|
688
|
-
"V",
|
|
689
|
-
"--module",
|
|
690
|
-
"divi/section",
|
|
691
|
-
"--padding-top",
|
|
692
|
-
"gvid-space-1",
|
|
693
|
-
], io);
|
|
694
|
-
assert.notEqual(code, EXIT.OK);
|
|
695
|
-
assert.match(io.stderr.join("\n"), /Variable-token support deferred/);
|
|
696
|
-
});
|
|
697
|
-
test("spacing rejects $variable(...) form in --margin-bottom", async () => {
|
|
698
|
-
const io = capture();
|
|
699
|
-
const code = await run([
|
|
700
|
-
"spacing",
|
|
701
|
-
"--name",
|
|
702
|
-
"V",
|
|
703
|
-
"--module",
|
|
704
|
-
"divi/section",
|
|
705
|
-
"--margin-bottom",
|
|
706
|
-
'$variable({"type":"length","value":{"name":"gvid-space-1","settings":{}}})$',
|
|
707
|
-
], io);
|
|
708
|
-
assert.notEqual(code, EXIT.OK);
|
|
709
|
-
assert.match(io.stderr.join("\n"), /Variable-token support deferred/);
|
|
710
|
-
});
|
|
711
|
-
test("spacing --padding-sync-vertical with invalid value exits 1", async () => {
|
|
712
|
-
const io = capture();
|
|
713
|
-
const code = await run([
|
|
714
|
-
"spacing",
|
|
715
|
-
"--name",
|
|
716
|
-
"X",
|
|
717
|
-
"--module",
|
|
718
|
-
"divi/section",
|
|
719
|
-
"--padding-top",
|
|
720
|
-
"40px",
|
|
721
|
-
"--padding-sync-vertical",
|
|
722
|
-
"maybe",
|
|
723
|
-
], io);
|
|
724
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
725
|
-
assert.match(io.stderr.join("\n"), /must be "on" or "off"/);
|
|
726
|
-
});
|
|
727
|
-
test("spacing dry-run requires no credentials and no network", async () => {
|
|
728
|
-
const saved = {
|
|
729
|
-
WP_URL: process.env.WP_URL,
|
|
730
|
-
WP_USER: process.env.WP_USER,
|
|
731
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
732
|
-
};
|
|
733
|
-
delete process.env.WP_URL;
|
|
734
|
-
delete process.env.WP_USER;
|
|
735
|
-
delete process.env.WP_APP_PASSWORD;
|
|
736
|
-
try {
|
|
737
|
-
const io = capture();
|
|
738
|
-
const code = await run([
|
|
739
|
-
"spacing",
|
|
740
|
-
"--name",
|
|
741
|
-
"Sec",
|
|
742
|
-
"--module",
|
|
743
|
-
"divi/section",
|
|
744
|
-
"--padding-top",
|
|
745
|
-
"40px",
|
|
746
|
-
], io);
|
|
747
|
-
assert.equal(code, EXIT.OK);
|
|
748
|
-
assert.equal(io.stderr.length, 0);
|
|
749
|
-
}
|
|
750
|
-
finally {
|
|
751
|
-
if (saved.WP_URL !== undefined)
|
|
752
|
-
process.env.WP_URL = saved.WP_URL;
|
|
753
|
-
if (saved.WP_USER !== undefined)
|
|
754
|
-
process.env.WP_USER = saved.WP_USER;
|
|
755
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
756
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
test("spacing --apply without credentials exits 1 with a credentials hint", async () => {
|
|
760
|
-
const saved = {
|
|
761
|
-
WP_URL: process.env.WP_URL,
|
|
762
|
-
WP_USER: process.env.WP_USER,
|
|
763
|
-
WP_APP_PASSWORD: process.env.WP_APP_PASSWORD,
|
|
764
|
-
};
|
|
765
|
-
delete process.env.WP_URL;
|
|
766
|
-
delete process.env.WP_USER;
|
|
767
|
-
delete process.env.WP_APP_PASSWORD;
|
|
768
|
-
try {
|
|
769
|
-
const io = capture();
|
|
770
|
-
const code = await run([
|
|
771
|
-
"spacing",
|
|
772
|
-
"--name",
|
|
773
|
-
"Sec",
|
|
774
|
-
"--module",
|
|
775
|
-
"divi/section",
|
|
776
|
-
"--padding-top",
|
|
777
|
-
"40px",
|
|
778
|
-
"--apply",
|
|
779
|
-
], io);
|
|
780
|
-
assert.equal(code, EXIT.INVALID_INPUT);
|
|
781
|
-
assert.match(io.stderr.join("\n"), /requires WordPress credentials/);
|
|
782
|
-
}
|
|
783
|
-
finally {
|
|
784
|
-
if (saved.WP_URL !== undefined)
|
|
785
|
-
process.env.WP_URL = saved.WP_URL;
|
|
786
|
-
if (saved.WP_USER !== undefined)
|
|
787
|
-
process.env.WP_USER = saved.WP_USER;
|
|
788
|
-
if (saved.WP_APP_PASSWORD !== undefined)
|
|
789
|
-
process.env.WP_APP_PASSWORD = saved.WP_APP_PASSWORD;
|
|
790
|
-
}
|
|
791
|
-
});
|