@delacour/warden-e2e 0.12.1 → 0.14.0
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/{chunk-rbbn127e.mjs → chunk-zk3wxa70.mjs} +557 -230
- package/index.mjs +226 -12
- package/package.json +2 -2
- package/preload.mjs +125 -25
- package/types/core/src/affected/affected.schema.d.ts +2 -0
- package/types/core/src/builds/config.schema.d.ts +1 -0
- package/types/core/src/builds/define-config.d.ts +10 -0
- package/types/e2e/src/driver/bridge-stall.d.ts +37 -0
- package/types/e2e/src/driver/driver.types.d.ts +9 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +22 -1
- package/types/e2e/src/driver/ios/simd-client.d.ts +2 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +30 -0
- package/types/e2e/src/driver/settle.d.ts +8 -0
- package/types/e2e/src/errors.d.ts +3 -1
- package/types/e2e/src/expect/matchers.d.ts +5 -0
- package/types/e2e/src/index.d.ts +2 -0
- package/types/e2e/src/run.d.ts +48 -0
- package/types/e2e/src/runner/bridge-evidence.d.ts +12 -0
- package/types/e2e/src/runner/results.d.ts +29 -0
- package/types/e2e/src/screen/context.d.ts +8 -1
- package/types/e2e/src/screen/degraded-read.d.ts +23 -0
- package/types/e2e/src/screen/locator.d.ts +46 -1
- package/types/e2e/src/screen/react-locator.d.ts +17 -2
- package/types/e2e/src/screen/react-merge.d.ts +10 -0
- package/types/e2e/src/session.d.ts +2 -0
package/index.mjs
CHANGED
|
@@ -3,21 +3,25 @@ import {
|
|
|
3
3
|
E2eError2,
|
|
4
4
|
a11yTargetOf,
|
|
5
5
|
canObserve,
|
|
6
|
+
isOnScreen,
|
|
6
7
|
fieldValue,
|
|
7
8
|
normalizeText,
|
|
8
9
|
matchesText,
|
|
9
10
|
renderSnapshot,
|
|
10
11
|
renderHeader,
|
|
11
|
-
resolve,
|
|
12
12
|
isLocator,
|
|
13
13
|
pickOne,
|
|
14
14
|
textOf,
|
|
15
15
|
formatNativeTail,
|
|
16
16
|
captureNativeLog,
|
|
17
|
+
bridgeStallFromSpan,
|
|
18
|
+
formatBridgeStall,
|
|
17
19
|
isRecoverableLoss,
|
|
20
|
+
parseDuration,
|
|
18
21
|
runStep,
|
|
19
22
|
attemptSession,
|
|
20
23
|
getSession,
|
|
24
|
+
peekSession,
|
|
21
25
|
accessibleName,
|
|
22
26
|
announce,
|
|
23
27
|
failing,
|
|
@@ -42,7 +46,7 @@ import {
|
|
|
42
46
|
registrationDefaults,
|
|
43
47
|
markOnly,
|
|
44
48
|
WARDEN_E2E_VERSION2
|
|
45
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-zk3wxa70.mjs";
|
|
46
50
|
// ../../packages/e2e/src/expect/expect.ts
|
|
47
51
|
import { expect as bunExpect } from "bun:test";
|
|
48
52
|
|
|
@@ -146,6 +150,15 @@ ${screen}`, screen);
|
|
|
146
150
|
var NEGATION_GRACE_MS = 1000;
|
|
147
151
|
var NEGATION_QUIET_MS = 250;
|
|
148
152
|
var NOT_FOUND = "element not found";
|
|
153
|
+
var UNKNOWN = "unknown (degraded read)";
|
|
154
|
+
function reactShown(matches, screen) {
|
|
155
|
+
const [only] = matches;
|
|
156
|
+
return only !== undefined && only.frame.width > 0 && only.frame.height > 0 && isOnScreen(only.tapPoint, screen);
|
|
157
|
+
}
|
|
158
|
+
function reactText(matches) {
|
|
159
|
+
const [only] = matches;
|
|
160
|
+
return only?.text === undefined ? undefined : normalizeText(only.text);
|
|
161
|
+
}
|
|
149
162
|
function missing() {
|
|
150
163
|
return { kind: "missing" };
|
|
151
164
|
}
|
|
@@ -203,7 +216,11 @@ class LocatorAssertions {
|
|
|
203
216
|
call: "toBeVisible()",
|
|
204
217
|
expected: "visible",
|
|
205
218
|
strict: true,
|
|
206
|
-
judge: (entry) => entry === undefined ? judged(false, "not found") : judged(entry.visible, entry.visible ? "visible" : "hidden")
|
|
219
|
+
judge: (entry) => entry === undefined ? judged(false, "not found") : judged(entry.visible, entry.visible ? "visible" : "hidden"),
|
|
220
|
+
judgeReact: (matches, screen) => {
|
|
221
|
+
const shown = reactShown(matches, screen);
|
|
222
|
+
return judged(shown, shown ? "visible (React tree)" : "hidden (React tree)");
|
|
223
|
+
}
|
|
207
224
|
}, options);
|
|
208
225
|
}
|
|
209
226
|
async toBeHidden(options = {}) {
|
|
@@ -212,7 +229,11 @@ class LocatorAssertions {
|
|
|
212
229
|
call: "toBeHidden()",
|
|
213
230
|
expected: "hidden",
|
|
214
231
|
strict: true,
|
|
215
|
-
judge: (entry) => entry === undefined ? judged(true, "not found") : judged(!entry.visible, entry.visible ? "visible" : "hidden")
|
|
232
|
+
judge: (entry) => entry === undefined ? judged(true, "not found") : judged(!entry.visible, entry.visible ? "visible" : "hidden"),
|
|
233
|
+
judgeReact: (matches, screen) => {
|
|
234
|
+
const shown = reactShown(matches, screen);
|
|
235
|
+
return judged(!shown, shown ? "visible (React tree)" : "hidden (React tree)");
|
|
236
|
+
}
|
|
216
237
|
}, options);
|
|
217
238
|
}
|
|
218
239
|
async toHaveText(expected, options = {}) {
|
|
@@ -227,6 +248,12 @@ class LocatorAssertions {
|
|
|
227
248
|
return missing();
|
|
228
249
|
const value = textOf(entry);
|
|
229
250
|
return judged(matchesText(value, expected, { exact }), JSON.stringify(value));
|
|
251
|
+
},
|
|
252
|
+
judgeReact: (matches) => {
|
|
253
|
+
const value = reactText(matches);
|
|
254
|
+
if (value === undefined)
|
|
255
|
+
return;
|
|
256
|
+
return judged(matchesText(value, expected, { exact }), `${JSON.stringify(value)} (React tree)`);
|
|
230
257
|
}
|
|
231
258
|
}, options);
|
|
232
259
|
}
|
|
@@ -242,6 +269,12 @@ class LocatorAssertions {
|
|
|
242
269
|
return missing();
|
|
243
270
|
const value = textOf(entry);
|
|
244
271
|
return judged(containsText(value, expected, ignoreCase), JSON.stringify(value));
|
|
272
|
+
},
|
|
273
|
+
judgeReact: (matches) => {
|
|
274
|
+
const value = reactText(matches);
|
|
275
|
+
if (value === undefined)
|
|
276
|
+
return;
|
|
277
|
+
return judged(containsText(value, expected, ignoreCase), `${JSON.stringify(value)} (React tree)`);
|
|
245
278
|
}
|
|
246
279
|
}, options);
|
|
247
280
|
}
|
|
@@ -377,7 +410,8 @@ class LocatorAssertions {
|
|
|
377
410
|
call: `toHaveCount(${expected})`,
|
|
378
411
|
expected: String(expected),
|
|
379
412
|
strict: false,
|
|
380
|
-
judge: (_entry, count) => judged(count === expected, String(count))
|
|
413
|
+
judge: (_entry, count) => judged(count === expected, String(count)),
|
|
414
|
+
judgeReact: (matches) => judged(matches.length === expected, `${matches.length} (React tree)`)
|
|
381
415
|
}, options);
|
|
382
416
|
}
|
|
383
417
|
async assert(spec, options) {
|
|
@@ -405,11 +439,12 @@ class LocatorAssertions {
|
|
|
405
439
|
return { done: false, state: { received: "(screen unreadable)" } };
|
|
406
440
|
}
|
|
407
441
|
const { observed } = observation;
|
|
408
|
-
const
|
|
409
|
-
if (
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
442
|
+
const look = await this.judgeLook(spec, observed);
|
|
443
|
+
if (look.kind === "unsettled") {
|
|
444
|
+
hold.broken();
|
|
445
|
+
return { done: false, state: look.last };
|
|
446
|
+
}
|
|
447
|
+
const { judgement, seen } = look;
|
|
413
448
|
const verdict = await hold.next(judgement.kind === "judged" && judgement.matches !== negate, observed.driver);
|
|
414
449
|
if (verdict === "pass")
|
|
415
450
|
return { done: true, value: true };
|
|
@@ -424,8 +459,35 @@ class LocatorAssertions {
|
|
|
424
459
|
}, timeoutMs);
|
|
425
460
|
if (result.status === "done")
|
|
426
461
|
return;
|
|
462
|
+
const degraded = result.last?.degraded;
|
|
463
|
+
if (degraded !== undefined)
|
|
464
|
+
throw this.locator.degradedFailure(degraded, timeoutMs, result.last?.snapshot);
|
|
427
465
|
throw this.failure(spec, result.last);
|
|
428
466
|
}
|
|
467
|
+
async judgeLook(spec, observed) {
|
|
468
|
+
const rt = this.locator.runtime;
|
|
469
|
+
const resolved = await this.locator.resolveIn(observed);
|
|
470
|
+
if (resolved.kind === "unknown") {
|
|
471
|
+
return { kind: "unsettled", last: { received: UNKNOWN, snapshot: observed.snapshot, degraded: resolved.read } };
|
|
472
|
+
}
|
|
473
|
+
if (resolved.kind === "react") {
|
|
474
|
+
const { matches } = resolved;
|
|
475
|
+
if (spec.strict && matches.length > 1 && spec.judgeReact !== undefined) {
|
|
476
|
+
const message = `${this.locator} matched ${matches.length} components in the React tree`;
|
|
477
|
+
throw new E2eError2("LOCATOR_AMBIGUOUS", message, rt.excerpt(observed.snapshot), matches.length);
|
|
478
|
+
}
|
|
479
|
+
const judgement = spec.judgeReact?.(matches, observed.snapshot.screen);
|
|
480
|
+
if (judgement === undefined) {
|
|
481
|
+
return { kind: "unsettled", last: { received: "only in the React tree", snapshot: observed.snapshot } };
|
|
482
|
+
}
|
|
483
|
+
return { kind: "judged", judgement, seen: observed.snapshot };
|
|
484
|
+
}
|
|
485
|
+
const { entries } = resolved;
|
|
486
|
+
if (spec.strict && entries.length > 1)
|
|
487
|
+
pickOne(rt, this.locator.expr, entries, observed);
|
|
488
|
+
const { seen, context } = await judgementContext(spec, observed);
|
|
489
|
+
return { kind: "judged", judgement: spec.judge(entries[0], entries.length, context), seen };
|
|
490
|
+
}
|
|
429
491
|
failure(spec, last) {
|
|
430
492
|
const { negate } = this.context;
|
|
431
493
|
const lines = [
|
|
@@ -572,6 +634,116 @@ function createExpect(deps) {
|
|
|
572
634
|
return call;
|
|
573
635
|
}
|
|
574
636
|
var expect = createExpect({ softSink: currentSoftSink });
|
|
637
|
+
// ../../packages/e2e/src/run.ts
|
|
638
|
+
var DEFAULT_TIMEOUT_MS = 60000;
|
|
639
|
+
var MESSAGE_TAIL = 2000;
|
|
640
|
+
var invalid = (message) => new E2eError2("INVALID_ARGUMENT", `run: ${message}`);
|
|
641
|
+
function timeoutMs(value) {
|
|
642
|
+
if (value === undefined)
|
|
643
|
+
return DEFAULT_TIMEOUT_MS;
|
|
644
|
+
if (typeof value === "number") {
|
|
645
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
646
|
+
throw invalid(`timeout must be over 0, got ${String(value)}`);
|
|
647
|
+
return value;
|
|
648
|
+
}
|
|
649
|
+
const parsed = parseDuration(value);
|
|
650
|
+
if (!parsed.success)
|
|
651
|
+
throw invalid(`timeout: ${parsed.error}`);
|
|
652
|
+
return parsed.data;
|
|
653
|
+
}
|
|
654
|
+
function argvOf(command) {
|
|
655
|
+
if (typeof command === "string") {
|
|
656
|
+
if (command.trim() === "")
|
|
657
|
+
throw invalid("command is empty");
|
|
658
|
+
return ["sh", "-c", command];
|
|
659
|
+
}
|
|
660
|
+
if (command.length === 0 || command[0] === "")
|
|
661
|
+
throw invalid("argv is empty");
|
|
662
|
+
return [...command];
|
|
663
|
+
}
|
|
664
|
+
var programOf = (command) => typeof command === "string" ? "sh -c" : command[0] ?? "";
|
|
665
|
+
function envOf(extra) {
|
|
666
|
+
const merged = { ...process.env, ...extra };
|
|
667
|
+
const env = {};
|
|
668
|
+
for (const [key, value] of Object.entries(merged))
|
|
669
|
+
if (value !== undefined)
|
|
670
|
+
env[key] = value;
|
|
671
|
+
return env;
|
|
672
|
+
}
|
|
673
|
+
function tail(text) {
|
|
674
|
+
const trimmed = text.trim();
|
|
675
|
+
return trimmed.length > MESSAGE_TAIL ? `\u2026${trimmed.slice(-MESSAGE_TAIL)}` : trimmed;
|
|
676
|
+
}
|
|
677
|
+
function resultOf(exitCode, stdout, stderr, ms) {
|
|
678
|
+
return {
|
|
679
|
+
ok: exitCode === 0,
|
|
680
|
+
exitCode,
|
|
681
|
+
stdout,
|
|
682
|
+
stderr,
|
|
683
|
+
ms,
|
|
684
|
+
json() {
|
|
685
|
+
try {
|
|
686
|
+
return JSON.parse(stdout);
|
|
687
|
+
} catch {
|
|
688
|
+
throw invalid(`stdout is not JSON: ${tail(stdout).slice(0, 200) || "(empty)"}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
async function exec(command, options) {
|
|
694
|
+
const argv = argvOf(command);
|
|
695
|
+
const limit = timeoutMs(options.timeout);
|
|
696
|
+
const started = Date.now();
|
|
697
|
+
let proc;
|
|
698
|
+
try {
|
|
699
|
+
proc = Bun.spawn(argv, {
|
|
700
|
+
cwd: options.cwd,
|
|
701
|
+
env: envOf(options.env),
|
|
702
|
+
stdin: "pipe",
|
|
703
|
+
stdout: "pipe",
|
|
704
|
+
stderr: "pipe"
|
|
705
|
+
});
|
|
706
|
+
} catch (error) {
|
|
707
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
708
|
+
throw new E2eError2("RUN_FAILED", `run: could not start ${programOf(command)}: ${reason}`);
|
|
709
|
+
}
|
|
710
|
+
if (options.input !== undefined)
|
|
711
|
+
proc.stdin.write(options.input);
|
|
712
|
+
await proc.stdin.end();
|
|
713
|
+
let timedOut = false;
|
|
714
|
+
const timer = setTimeout(() => {
|
|
715
|
+
timedOut = true;
|
|
716
|
+
proc.kill("SIGKILL");
|
|
717
|
+
}, limit);
|
|
718
|
+
try {
|
|
719
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
720
|
+
new Response(proc.stdout).text(),
|
|
721
|
+
new Response(proc.stderr).text(),
|
|
722
|
+
proc.exited
|
|
723
|
+
]);
|
|
724
|
+
const result = resultOf(exitCode, stdout, stderr, Date.now() - started);
|
|
725
|
+
if (timedOut) {
|
|
726
|
+
throw new E2eError2("TIMEOUT", `run: ${programOf(command)} did not finish within ${limit} ms (killed)`);
|
|
727
|
+
}
|
|
728
|
+
if (!result.ok && options.check !== false) {
|
|
729
|
+
const output = tail(stderr) || tail(stdout);
|
|
730
|
+
throw new E2eError2("RUN_FAILED", `run: ${programOf(command)} failed with exit ${exitCode}${output === "" ? "" : `
|
|
731
|
+
${output}`}`);
|
|
732
|
+
}
|
|
733
|
+
return result;
|
|
734
|
+
} finally {
|
|
735
|
+
clearTimeout(timer);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function createRun(deps) {
|
|
739
|
+
return (command, options = {}) => deps.span("run", () => exec(command, options), {
|
|
740
|
+
verdict: (result) => result.ok ? { ok: true } : { ok: false, code: "RUN_FAILED" },
|
|
741
|
+
detail: (result) => ({ exitCode: result.exitCode })
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
var run = createRun({
|
|
745
|
+
span: (op, fn, options) => peekSession()?.span(op, fn, options) ?? fn()
|
|
746
|
+
});
|
|
575
747
|
// ../../packages/e2e/src/runner/test.ts
|
|
576
748
|
import { afterAll as bunAfterAll, beforeAll as bunBeforeAll, describe as bunDescribe, test as bunTest } from "bun:test";
|
|
577
749
|
|
|
@@ -598,6 +770,36 @@ function missingRequirements(target, requires) {
|
|
|
598
770
|
import { readdirSync } from "fs";
|
|
599
771
|
import { join as join2 } from "path";
|
|
600
772
|
|
|
773
|
+
// ../../packages/e2e/src/runner/bridge-evidence.ts
|
|
774
|
+
function bridgeEvidence() {
|
|
775
|
+
let fallbacks = 0;
|
|
776
|
+
let first;
|
|
777
|
+
let last;
|
|
778
|
+
return {
|
|
779
|
+
observe(event) {
|
|
780
|
+
if (event.type !== "span" || event.detail === undefined)
|
|
781
|
+
return;
|
|
782
|
+
const detail = event.detail;
|
|
783
|
+
const stall = bridgeStallFromSpan(detail);
|
|
784
|
+
const reason = typeof detail.fallback === "string" ? detail.fallback : undefined;
|
|
785
|
+
const fellBack = reason !== undefined && detail.backend !== "axbridge";
|
|
786
|
+
if (stall === undefined && !fellBack)
|
|
787
|
+
return;
|
|
788
|
+
const read = {
|
|
789
|
+
...fellBack ? { reason } : {},
|
|
790
|
+
...event.ok || event.code === undefined ? {} : { code: event.code },
|
|
791
|
+
...stall
|
|
792
|
+
};
|
|
793
|
+
fallbacks += 1;
|
|
794
|
+
first ??= read;
|
|
795
|
+
last = read;
|
|
796
|
+
},
|
|
797
|
+
summary() {
|
|
798
|
+
return first === undefined || last === undefined ? undefined : { fallbacks, first, last };
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
601
803
|
// ../../packages/e2e/src/runner/failure.ts
|
|
602
804
|
import { mkdirSync, writeFileSync } from "fs";
|
|
603
805
|
import { join } from "path";
|
|
@@ -670,7 +872,9 @@ async function captureSnapshot(driver, dir, ms, capture) {
|
|
|
670
872
|
}
|
|
671
873
|
const rendered = renderSnapshot(result.data, { interactiveOnly: false, refs: new Map });
|
|
672
874
|
const header = renderHeader({ snapshot: result.data, rendered, interactiveOnly: false });
|
|
673
|
-
|
|
875
|
+
const fellBack = fallbackLine(result.data);
|
|
876
|
+
const lines = fellBack === undefined ? [header, ...rendered.lines] : [header, fellBack, ...rendered.lines];
|
|
877
|
+
writeFileSync(join(dir, SCREEN_AT_FAILURE), `${lines.join(`
|
|
674
878
|
`)}
|
|
675
879
|
`);
|
|
676
880
|
capture.files.push(SCREEN_AT_FAILURE);
|
|
@@ -678,6 +882,12 @@ async function captureSnapshot(driver, dir, ms, capture) {
|
|
|
678
882
|
capture.problems.push(`snapshot: ${message(cause)}`);
|
|
679
883
|
}
|
|
680
884
|
}
|
|
885
|
+
function fallbackLine(snapshot) {
|
|
886
|
+
if (snapshot.fellBackFrom === undefined)
|
|
887
|
+
return;
|
|
888
|
+
const head = `# read on ${snapshot.backend}: ${snapshot.fellBackFrom}`;
|
|
889
|
+
return snapshot.bridgeStall === undefined ? `${head} did not answer` : `${head} stalled (${formatBridgeStall(snapshot.bridgeStall)})`;
|
|
890
|
+
}
|
|
681
891
|
async function captureDevtoolsArtifacts(facet, dir, timeoutMs = CAPTURE_TIMEOUT_MS, budget = unlimited) {
|
|
682
892
|
const capture = { files: [], problems: [] };
|
|
683
893
|
const ms = Math.min(timeoutMs, budget());
|
|
@@ -947,8 +1157,10 @@ async function runAttemptOnce(session, spec) {
|
|
|
947
1157
|
};
|
|
948
1158
|
const files = new Set;
|
|
949
1159
|
let binding;
|
|
1160
|
+
const bridge = bridgeEvidence();
|
|
950
1161
|
const trace = openTrace(join2(dir, "trace.jsonl"), (event) => {
|
|
951
1162
|
files.add("trace.jsonl");
|
|
1163
|
+
bridge.observe(event);
|
|
952
1164
|
print(formatConsoleLine(event, { clock, short, depth: binding?.context.depth ?? 0 }));
|
|
953
1165
|
});
|
|
954
1166
|
const controller = new AbortController;
|
|
@@ -981,7 +1193,7 @@ async function runAttemptOnce(session, spec) {
|
|
|
981
1193
|
session.leaveAttempt(ctx);
|
|
982
1194
|
trace.flush();
|
|
983
1195
|
outcome = failIf(outcome, cleanupErrors.length > 0, () => cleanupErrors[0]);
|
|
984
|
-
return concludeAttempt(session, spec, ctx, { startedAt, device }, outcome, print, clock);
|
|
1196
|
+
return concludeAttempt(session, spec, ctx, { startedAt, device, bridge: bridge.summary() }, outcome, print, clock);
|
|
985
1197
|
}
|
|
986
1198
|
async function raceDeadline(controller, timeoutMs, run) {
|
|
987
1199
|
let deadlineHit = false;
|
|
@@ -1042,6 +1254,7 @@ function buildResult(spec, ctx, run, outcome) {
|
|
|
1042
1254
|
...spec.only ? { only: true } : {},
|
|
1043
1255
|
...outcome.failed ? { error: errorInfo(outcome.error) } : {},
|
|
1044
1256
|
...ctx.files.size > 0 ? { artifacts: { dir: ctx.dir, files: [...ctx.files].sort() } } : {},
|
|
1257
|
+
...run.bridge === undefined ? {} : { bridge: run.bridge },
|
|
1045
1258
|
device: run.device
|
|
1046
1259
|
};
|
|
1047
1260
|
}
|
|
@@ -1277,5 +1490,6 @@ export {
|
|
|
1277
1490
|
beforeEach,
|
|
1278
1491
|
describe,
|
|
1279
1492
|
expect,
|
|
1493
|
+
run,
|
|
1280
1494
|
test
|
|
1281
1495
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/warden-e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "TypeScript e2e testing for React Native and Expo on iOS simulators and Android emulators, run by `warden e2e`: test, expect, screen, device, app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"LICENSE"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@delacour/warden": "0.
|
|
44
|
+
"@delacour/warden": "0.14.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"zod": "^4.4.3"
|