@diviops/mcp-server 1.5.11 → 1.5.13
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 +20 -2
- package/dist/preset-cli/__tests__/cli.test.js +385 -0
- package/dist/preset-cli/__tests__/heading-font-emitter.test.d.ts +12 -0
- package/dist/preset-cli/__tests__/heading-font-emitter.test.js +249 -0
- package/dist/preset-cli/__tests__/registry.test.js +41 -0
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.d.ts +14 -0
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.js +191 -0
- package/dist/preset-cli/__tests__/write-path.test.js +110 -1
- package/dist/preset-cli/cli.d.ts +6 -0
- package/dist/preset-cli/cli.js +198 -11
- package/dist/preset-cli/heading-font-emitter.d.ts +128 -0
- package/dist/preset-cli/heading-font-emitter.js +166 -0
- package/dist/preset-cli/registry.d.ts +23 -9
- package/dist/preset-cli/registry.js +37 -13
- package/dist/preset-cli/text-body-font-emitter.d.ts +127 -0
- package/dist/preset-cli/text-body-font-emitter.js +169 -0
- package/dist/preset-cli/write-path.d.ts +31 -0
- package/dist/preset-cli/write-path.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,18 +85,36 @@ See [server-reference.md](../docs/server-reference.md) for per-tool descriptions
|
|
|
85
85
|
The package also ships a standalone command-line preset emitter, `diviops-preset`,
|
|
86
86
|
that produces byte-canonical Divi 5.5.x preset JSON gated by the verified-attrs
|
|
87
87
|
registry (`data/verified-attrs.json`). It is independent of the MCP stdio server —
|
|
88
|
-
run it directly:
|
|
88
|
+
run it directly. Current commands:
|
|
89
|
+
|
|
90
|
+
| Command | Emits |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `diviops-preset button [options]` | `divi/button` group preset |
|
|
93
|
+
| `diviops-preset heading-font [options]` | `divi/font` group preset for `divi/heading` (Pattern A — Google Fonts — or Pattern B — local-hosted) |
|
|
94
|
+
| `diviops-preset text-body-font [options]` | `divi/font-body` group preset for `divi/text` — **Pattern A (Google Fonts) only**; Pattern B for body-text has no registered canonical shape and is refused |
|
|
89
95
|
|
|
90
96
|
```bash
|
|
91
97
|
diviops-preset button --name "Primary" --bg-color gcid-primary-color \
|
|
92
98
|
--bg-color-hover gcid-secondary-color --radius 8px \
|
|
93
99
|
--font-family Inter --font-weight 600 --font-color gcid-body-color
|
|
100
|
+
|
|
101
|
+
diviops-preset heading-font --name "Heading H1" --pattern google \
|
|
102
|
+
--font-family Inter --font-weight 700 \
|
|
103
|
+
--font-color gcid-heading-color --font-size 48px
|
|
104
|
+
|
|
105
|
+
diviops-preset text-body-font --name "Body Text" --pattern google \
|
|
106
|
+
--font-family Inter --font-weight 400 \
|
|
107
|
+
--font-color gcid-body-color --font-size 16px
|
|
94
108
|
```
|
|
95
109
|
|
|
96
110
|
`--dry-run` (the default) composes and prints the canonical JSON with no
|
|
97
111
|
credentials and no network. `--apply` posts to the existing `/preset/create`
|
|
98
112
|
REST route, reusing the same `WP_URL` / `WP_USER` / `WP_APP_PASSWORD` env vars.
|
|
99
|
-
|
|
113
|
+
|
|
114
|
+
The CLI's coverage is intentionally narrow: only the (module, group, variant)
|
|
115
|
+
combinations whose canonical shape is VB-verified in the registry are
|
|
116
|
+
emittable. It is **not** an all-module or all-font-family emitter — each
|
|
117
|
+
additional vertical slice lands with its own verified evidence. See the
|
|
100
118
|
[preset-cli reference](https://github.com/oaris-dev/diviops/blob/main/diviops-server/src/preset-cli/README.md)
|
|
101
119
|
for the full command reference (the `src/` tree is not part of the published
|
|
102
120
|
npm package — this link resolves on the repository).
|
|
@@ -39,6 +39,14 @@ test("unknown command exits 1 (invalid input)", async () => {
|
|
|
39
39
|
assert.equal(code, EXIT.INVALID_INPUT);
|
|
40
40
|
assert.match(io.stderr.join("\n"), /Unknown command/);
|
|
41
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
|
+
});
|
|
42
50
|
test("unknown flag exits 1 (invalid input)", async () => {
|
|
43
51
|
const io = capture();
|
|
44
52
|
const code = await run(["button", "--name", "X", "--bogus", "y"], io);
|
|
@@ -138,6 +146,383 @@ test("parseArgs: --radius-sync rejects values outside on|off", () => {
|
|
|
138
146
|
const parsed = parseArgs(["button", "--name", "P", "--radius-sync", "maybe"]);
|
|
139
147
|
assert.throws(() => buildButtonInput(parsed), /radius-sync must be/);
|
|
140
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
|
+
});
|
|
141
526
|
test("dry-run output includes the bypass corner when requested", async () => {
|
|
142
527
|
const io = capture();
|
|
143
528
|
const code = await run(["button", "--name", "P", "--bg-color", "#111", "--bypass-hover-padding-gate"], io);
|
|
@@ -0,0 +1,12 @@
|
|
|
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 {};
|