@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
|
@@ -20,6 +20,7 @@ import type { DiviopsResponse } from "../envelope.js";
|
|
|
20
20
|
import type { ButtonPresetEntry } from "./button-emitter.js";
|
|
21
21
|
import type { HeadingFontPresetEntry } from "./heading-font-emitter.js";
|
|
22
22
|
import type { TextBodyFontPresetEntry } from "./text-body-font-emitter.js";
|
|
23
|
+
import type { SpacingPresetEntry } from "./spacing-emitter.js";
|
|
23
24
|
/** The plugin capability the storage-path capability contract ships. */
|
|
24
25
|
export declare const STORAGE_CAPABILITY = "storage_multipath_probe_v1";
|
|
25
26
|
/** The REST route the CLI posts to — same route `diviops_preset_create` uses. */
|
|
@@ -88,3 +89,18 @@ export declare function applyTextBodyFontPreset(client: PresetWriteClient, entry
|
|
|
88
89
|
serverVersion: string;
|
|
89
90
|
dry_run?: boolean;
|
|
90
91
|
}): Promise<DiviopsResponse<unknown>>;
|
|
92
|
+
/**
|
|
93
|
+
* Apply a `divi/spacing` section group preset: capability-gate, then POST
|
|
94
|
+
* to `/preset/create`. Mirrors `applyTextBodyFontPreset`'s sequence — the
|
|
95
|
+
* capability check runs BEFORE the write, and the write reuses the
|
|
96
|
+
* existing storage-routed route (no plugin route is added).
|
|
97
|
+
*
|
|
98
|
+
* Unlike the font emitters, the spacing entry carries `primary_attr_name`
|
|
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
|
+
*/
|
|
103
|
+
export declare function applySpacingPreset(client: PresetWriteClient, entry: SpacingPresetEntry, opts: {
|
|
104
|
+
serverVersion: string;
|
|
105
|
+
dry_run?: boolean;
|
|
106
|
+
}): Promise<DiviopsResponse<unknown>>;
|
|
@@ -18,6 +18,7 @@ import { WPClient } from "../wp-client.js";
|
|
|
18
18
|
import { buildPresetCreateBody } from "./button-emitter.js";
|
|
19
19
|
import { buildHeadingFontPresetCreateBody } from "./heading-font-emitter.js";
|
|
20
20
|
import { buildTextBodyFontPresetCreateBody } from "./text-body-font-emitter.js";
|
|
21
|
+
import { buildSpacingPresetCreateBody } from "./spacing-emitter.js";
|
|
21
22
|
/** The plugin capability the storage-path capability contract ships. */
|
|
22
23
|
export const STORAGE_CAPABILITY = "storage_multipath_probe_v1";
|
|
23
24
|
/** The REST route the CLI posts to — same route `diviops_preset_create` uses. */
|
|
@@ -130,3 +131,24 @@ export async function applyTextBodyFontPreset(client, entry, opts) {
|
|
|
130
131
|
body,
|
|
131
132
|
});
|
|
132
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Apply a `divi/spacing` section group preset: capability-gate, then POST
|
|
136
|
+
* to `/preset/create`. Mirrors `applyTextBodyFontPreset`'s sequence — the
|
|
137
|
+
* capability check runs BEFORE the write, and the write reuses the
|
|
138
|
+
* existing storage-routed route (no plugin route is added).
|
|
139
|
+
*
|
|
140
|
+
* Unlike the font emitters, the spacing entry carries `primary_attr_name`
|
|
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
|
+
*/
|
|
145
|
+
export async function applySpacingPreset(client, entry, opts) {
|
|
146
|
+
await assertStorageCapability(client, opts.serverVersion);
|
|
147
|
+
const body = buildSpacingPresetCreateBody(entry, {
|
|
148
|
+
dry_run: opts.dry_run,
|
|
149
|
+
});
|
|
150
|
+
return client.requestEnveloped(PRESET_CREATE_ROUTE, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -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.16",
|
|
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 {};
|