@diviops/mcp-server 1.5.13 → 1.5.16
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 +14 -1
- package/data/verified-attrs-backlog.json +6 -6
- package/data/verified-attrs.json +32 -18
- 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 +7 -3
- package/dist/preset-cli/cli.js +131 -5
- 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 +132 -0
- package/dist/preset-cli/spacing-emitter.js +276 -0
- package/dist/preset-cli/write-path.d.ts +16 -0
- package/dist/preset-cli/write-path.js +22 -0
- 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 -534
- 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__/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 -229
|
@@ -1,534 +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
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `divi/font` heading emitter shape + gating coverage:
|
|
3
|
-
* - fixture-based shape assertion against round-1a (Pattern A) and
|
|
4
|
-
* round-1b (Pattern B) canonical captures;
|
|
5
|
-
* - Pattern B no-weight discriminator: omitted `weight` produces no key;
|
|
6
|
-
* - Pattern B + explicit weight refused as unverified/out-of-scope;
|
|
7
|
-
* - variant-aware registry gating: missing applicability or under-
|
|
8
|
-
* verified evidence on the chosen variant throws with attr / pattern /
|
|
9
|
-
* effective-level / source in the message; Pattern A evidence does NOT
|
|
10
|
-
* vouch for Pattern B and vice versa.
|
|
11
|
-
*/
|
|
12
|
-
export {};
|