@ripplo/testing 0.0.6 → 0.0.8

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.
@@ -0,0 +1,615 @@
1
+ // src/lockfile.ts
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+
5
+ // ../spec/src/codec.ts
6
+ import { z } from "zod";
7
+ var envelopeSchema = z.object({
8
+ __codec: z.string().min(1),
9
+ data: z.unknown(),
10
+ version: z.number().int().positive()
11
+ });
12
+ var CodecVersionError = class extends Error {
13
+ codec;
14
+ currentVersion;
15
+ gotVersion;
16
+ constructor(params) {
17
+ super(
18
+ `Unsupported ${params.codec} version ${String(params.gotVersion)} (current ${String(params.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`
19
+ );
20
+ this.name = "CodecVersionError";
21
+ this.codec = params.codec;
22
+ this.currentVersion = params.currentVersion;
23
+ this.gotVersion = params.gotVersion;
24
+ }
25
+ };
26
+ var CodecMismatchError = class extends Error {
27
+ constructor(params) {
28
+ super(`Codec mismatch: expected "${params.expected}", got "${params.got}"`);
29
+ this.name = "CodecMismatchError";
30
+ }
31
+ };
32
+ function defineCodec(name) {
33
+ return makeInitial({ legacy: void 0, migrators: [], name, schemas: [] });
34
+ }
35
+ function decodeJson(codec, raw) {
36
+ const parsed = JSON.parse(raw);
37
+ return codec.decode(parsed);
38
+ }
39
+ function makeInitial(state) {
40
+ return {
41
+ initial: (schema) => makeBuilder(schema, { ...state, schemas: [schema] }),
42
+ legacy: (adapter) => makeInitial({ ...state, legacy: adapter })
43
+ };
44
+ }
45
+ function makeBuilder(latestSchema, state) {
46
+ return {
47
+ build: () => buildCodec(latestSchema, state),
48
+ legacy: (adapter) => makeBuilder(latestSchema, { ...state, legacy: adapter }),
49
+ upgrade: (schema, up) => {
50
+ const erased = up;
51
+ return makeBuilder(schema, {
52
+ ...state,
53
+ migrators: [...state.migrators, erased],
54
+ schemas: [...state.schemas, schema]
55
+ });
56
+ }
57
+ };
58
+ }
59
+ function buildCodec(latestSchema, state) {
60
+ const currentVersion = state.schemas.length;
61
+ return {
62
+ currentVersion,
63
+ name: state.name,
64
+ decode: (raw) => decodeWith(latestSchema, state, raw),
65
+ encode: (value) => ({
66
+ __codec: state.name,
67
+ data: value,
68
+ version: currentVersion
69
+ })
70
+ };
71
+ }
72
+ function decodeWith(latestSchema, state, raw) {
73
+ const { data, version } = unwrapEnvelope(state, raw);
74
+ const migrated = migrateStep(state, data, version);
75
+ return latestSchema.parse(migrated);
76
+ }
77
+ function unwrapEnvelope(state, raw) {
78
+ const envelopeResult = envelopeSchema.safeParse(raw);
79
+ if (envelopeResult.success) {
80
+ if (envelopeResult.data.__codec !== state.name) {
81
+ throw new CodecMismatchError({
82
+ expected: state.name,
83
+ got: envelopeResult.data.__codec
84
+ });
85
+ }
86
+ return {
87
+ data: envelopeResult.data.data,
88
+ version: envelopeResult.data.version
89
+ };
90
+ }
91
+ if (state.legacy != null && state.legacy.detect(raw)) {
92
+ return { data: raw, version: state.legacy.assumedVersion };
93
+ }
94
+ throw new Error(
95
+ `Cannot decode "${state.name}": value is not a codec envelope and no legacy detector matched.`
96
+ );
97
+ }
98
+ function migrateStep(state, data, version) {
99
+ const currentVersion = state.schemas.length;
100
+ if (version > currentVersion) {
101
+ throw new CodecVersionError({
102
+ codec: state.name,
103
+ currentVersion,
104
+ gotVersion: version
105
+ });
106
+ }
107
+ if (version === currentVersion) {
108
+ return data;
109
+ }
110
+ const sourceSchema = state.schemas[version - 1];
111
+ if (sourceSchema == null) {
112
+ throw new Error(
113
+ `Codec "${state.name}" missing schema for v${String(version)}; cannot migrate.`
114
+ );
115
+ }
116
+ const validated = sourceSchema.parse(data);
117
+ const migrator = state.migrators[version - 1];
118
+ if (migrator == null) {
119
+ throw new Error(
120
+ `Codec "${state.name}" missing migrator v${String(version)} \u2192 v${String(version + 1)}.`
121
+ );
122
+ }
123
+ return migrateStep(state, migrator(validated), version + 1);
124
+ }
125
+
126
+ // ../spec/src/codecs.ts
127
+ import { z as z11 } from "zod";
128
+
129
+ // ../spec/src/graph.ts
130
+ import { z as z5 } from "zod";
131
+
132
+ // ../spec/src/edge.ts
133
+ import { z as z2 } from "zod";
134
+ var edgeSchema = z2.object({
135
+ from: z2.string().min(1).describe("Key of the source state in the states record"),
136
+ requiresKeys: z2.record(z2.string(), z2.string()).optional().describe(
137
+ "Maps workflow variable namespace to precondition name. Used by the runtime to namespace batch precondition data into dot-namespaced variables (e.g. { project: 'data:project' } maps projectId \u2192 project.projectId)."
138
+ ),
139
+ to: z2.string().min(1).describe("Key of the target state in the states record"),
140
+ workflow: z2.string().min(1).describe(
141
+ "Filename (without .json) of the workflow in .ripplo/workflows/ that executes this edge"
142
+ )
143
+ }).describe("A directed edge between two states, executed by a workflow");
144
+
145
+ // ../spec/src/precondition.ts
146
+ import { z as z3 } from "zod";
147
+ var preconditionSchema = z3.object({
148
+ depends: z3.array(z3.string().min(1)).optional().describe(
149
+ "Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."
150
+ ),
151
+ description: z3.string().min(1).describe("Human-readable description of what this precondition ensures"),
152
+ returns: z3.array(z3.string().min(1)).optional().describe(
153
+ "Keys that the execute response's data field will contain. e.g. ['projectId', 'workflowId']. These are used for route param interpolation ({{projectId}}) and workflow variables. Declared here so generated types are strongly typed per precondition."
154
+ )
155
+ }).describe("A named precondition declared at the graph level. States reference these by name.");
156
+
157
+ // ../spec/src/state.ts
158
+ import { z as z4 } from "zod";
159
+ var stateNodeSchema = z4.object({
160
+ preconditions: z4.array(z4.string().min(1)).describe("Ordered list of precondition names to satisfy before entering this state"),
161
+ route: z4.string().min(1).describe(
162
+ "URL pattern with {{placeholders}} for dynamic segments, e.g. '/projects/{{projectId}}/settings'"
163
+ )
164
+ }).describe(
165
+ "A distinct application state \u2014 a unique combination of location, auth context, and data conditions"
166
+ );
167
+
168
+ // ../spec/src/graph.ts
169
+ var MAX_STATES = 1e3;
170
+ var MAX_EDGES = 5e3;
171
+ var MAX_PRECONDITIONS = 500;
172
+ var stateGraphSchema = z5.object({
173
+ edges: z5.array(edgeSchema).max(MAX_EDGES).describe("Directed edges between states, each executed by a workflow"),
174
+ preconditions: z5.record(z5.string().max(200), preconditionSchema).refine(
175
+ (obj) => Object.keys(obj).length <= MAX_PRECONDITIONS,
176
+ `Graph has more than ${String(MAX_PRECONDITIONS)} preconditions`
177
+ ).describe(
178
+ "Named preconditions keyed by name (e.g. 'auth:admin', 'data:three-projects'). States reference these by name."
179
+ ),
180
+ states: z5.record(z5.string().max(200), stateNodeSchema).refine(
181
+ (obj) => Object.keys(obj).length <= MAX_STATES,
182
+ `Graph has more than ${String(MAX_STATES)} states`
183
+ ).describe("States keyed by stable ID (kebab-case)")
184
+ }).describe("Ripplo State Graph \u2014 models application states, edges, and executable preconditions");
185
+
186
+ // ../spec/src/schema.ts
187
+ import { z as z10 } from "zod";
188
+
189
+ // ../spec/src/locators.ts
190
+ import { z as z6 } from "zod";
191
+ var testIdLocator = z6.object({
192
+ by: z6.literal("testId"),
193
+ value: z6.string().min(1)
194
+ });
195
+ var roleLocator = z6.object({
196
+ by: z6.literal("role"),
197
+ name: z6.string().optional(),
198
+ role: z6.string().min(1)
199
+ });
200
+ var locatorSchema = z6.discriminatedUnion("by", [testIdLocator, roleLocator]);
201
+
202
+ // ../spec/src/operators.ts
203
+ import { z as z7 } from "zod";
204
+ var comparisonOperator = z7.enum([
205
+ "equals",
206
+ "notEquals",
207
+ "contains",
208
+ "startsWith",
209
+ "endsWith",
210
+ "matches"
211
+ ]);
212
+ var numericOperator = z7.enum([
213
+ "equals",
214
+ "notEquals",
215
+ "greaterThan",
216
+ "greaterThanOrEqual",
217
+ "lessThan",
218
+ "lessThanOrEqual"
219
+ ]);
220
+
221
+ // ../spec/src/value-ref.ts
222
+ import { z as z8 } from "zod";
223
+ var staticValueSchema = z8.object({
224
+ type: z8.literal("static"),
225
+ value: z8.union([z8.string(), z8.number(), z8.boolean()])
226
+ });
227
+ var variableRefSchema = z8.object({
228
+ name: z8.string().min(1),
229
+ type: z8.literal("variable")
230
+ });
231
+ var valueRefSchema = z8.discriminatedUnion("type", [staticValueSchema, variableRefSchema]);
232
+ var stringValueRefSchema = z8.discriminatedUnion("type", [
233
+ z8.object({ type: z8.literal("static"), value: z8.string() }),
234
+ variableRefSchema
235
+ ]);
236
+ var numericValueRefSchema = z8.discriminatedUnion("type", [
237
+ z8.object({ type: z8.literal("static"), value: z8.number().int().nonnegative() }),
238
+ variableRefSchema
239
+ ]);
240
+
241
+ // ../spec/src/variables.ts
242
+ import { z as z9 } from "zod";
243
+ var variableDefSchema = z9.discriminatedUnion("type", [
244
+ z9.object({
245
+ default: z9.string().optional(),
246
+ type: z9.literal("string")
247
+ }),
248
+ z9.object({
249
+ default: z9.number().optional(),
250
+ type: z9.literal("number")
251
+ }),
252
+ z9.object({
253
+ default: z9.boolean().optional(),
254
+ type: z9.literal("boolean")
255
+ }),
256
+ z9.object({
257
+ key: z9.string().min(1),
258
+ type: z9.literal("env")
259
+ })
260
+ ]);
261
+
262
+ // ../spec/src/schema.ts
263
+ var nodeBase = {
264
+ comment: z10.string().max(2e3).optional(),
265
+ id: z10.string().min(1).max(200),
266
+ label: z10.string().max(500).optional(),
267
+ next: z10.string().max(200).optional(),
268
+ timeout: z10.number().int().positive().optional()
269
+ };
270
+ var MAX_NODES_PER_WORKFLOW = 500;
271
+ var gotoNode = z10.object({
272
+ ...nodeBase,
273
+ type: z10.literal("goto"),
274
+ url: stringValueRefSchema
275
+ });
276
+ var clickNode = z10.object({ ...nodeBase, locator: locatorSchema, type: z10.literal("click") });
277
+ var fillNode = z10.object({
278
+ ...nodeBase,
279
+ locator: locatorSchema,
280
+ type: z10.literal("fill"),
281
+ value: stringValueRefSchema
282
+ });
283
+ var selectNode = z10.object({
284
+ ...nodeBase,
285
+ locator: locatorSchema,
286
+ type: z10.literal("select"),
287
+ value: stringValueRefSchema
288
+ });
289
+ var hoverNode = z10.object({ ...nodeBase, locator: locatorSchema, type: z10.literal("hover") });
290
+ var pressNode = z10.object({
291
+ ...nodeBase,
292
+ key: z10.string().min(1),
293
+ locator: locatorSchema.optional(),
294
+ type: z10.literal("press")
295
+ });
296
+ var checkNode = z10.object({ ...nodeBase, locator: locatorSchema, type: z10.literal("check") });
297
+ var uncheckNode = z10.object({ ...nodeBase, locator: locatorSchema, type: z10.literal("uncheck") });
298
+ var setViewportNode = z10.object({
299
+ ...nodeBase,
300
+ height: z10.number().int().positive(),
301
+ type: z10.literal("setViewport"),
302
+ width: z10.number().int().positive()
303
+ });
304
+ var failNode = z10.object({ ...nodeBase, message: z10.string().min(1), type: z10.literal("fail") });
305
+ var setVariableNode = z10.object({
306
+ ...nodeBase,
307
+ type: z10.literal("setVariable"),
308
+ value: valueRefSchema,
309
+ variable: z10.string().min(1)
310
+ });
311
+ var extractTextNode = z10.object({
312
+ ...nodeBase,
313
+ locator: locatorSchema,
314
+ type: z10.literal("extractText"),
315
+ variable: z10.string().min(1)
316
+ });
317
+ var uploadNode = z10.object({
318
+ ...nodeBase,
319
+ files: z10.array(z10.string()).min(1),
320
+ locator: locatorSchema,
321
+ type: z10.literal("upload")
322
+ });
323
+ var dblclickNode = z10.object({
324
+ ...nodeBase,
325
+ locator: locatorSchema,
326
+ type: z10.literal("dblclick")
327
+ });
328
+ var dragNode = z10.object({
329
+ ...nodeBase,
330
+ source: locatorSchema,
331
+ target: locatorSchema,
332
+ type: z10.literal("drag")
333
+ });
334
+ var scrollIntoViewNode = z10.object({
335
+ ...nodeBase,
336
+ locator: locatorSchema,
337
+ type: z10.literal("scrollIntoView")
338
+ });
339
+ var typeNode = z10.object({
340
+ ...nodeBase,
341
+ locator: locatorSchema,
342
+ type: z10.literal("type"),
343
+ value: stringValueRefSchema
344
+ });
345
+ var focusNode = z10.object({
346
+ ...nodeBase,
347
+ locator: locatorSchema,
348
+ type: z10.literal("focus")
349
+ });
350
+ var clearNode = z10.object({ ...nodeBase, locator: locatorSchema, type: z10.literal("clear") });
351
+ var rightClickNode = z10.object({
352
+ ...nodeBase,
353
+ locator: locatorSchema,
354
+ type: z10.literal("rightClick")
355
+ });
356
+ var handleDialogNode = z10.object({
357
+ ...nodeBase,
358
+ action: z10.enum(["accept", "dismiss"]),
359
+ promptText: z10.string().optional(),
360
+ type: z10.literal("handleDialog")
361
+ });
362
+ var clipboardNode = z10.object({
363
+ ...nodeBase,
364
+ action: z10.enum(["read", "write"]),
365
+ type: z10.literal("clipboard"),
366
+ value: stringValueRefSchema.optional(),
367
+ variable: z10.string().min(1).optional()
368
+ });
369
+ var setPermissionNode = z10.object({
370
+ ...nodeBase,
371
+ permission: z10.string().min(1),
372
+ state: z10.enum(["granted", "prompt"]),
373
+ type: z10.literal("setPermission")
374
+ });
375
+ var assertVisibleNode = z10.object({
376
+ ...nodeBase,
377
+ locator: locatorSchema,
378
+ type: z10.literal("assertVisible")
379
+ });
380
+ var assertNotVisibleNode = z10.object({
381
+ ...nodeBase,
382
+ locator: locatorSchema,
383
+ type: z10.literal("assertNotVisible")
384
+ });
385
+ var assertTextNode = z10.object({
386
+ ...nodeBase,
387
+ expected: stringValueRefSchema,
388
+ locator: locatorSchema,
389
+ operator: comparisonOperator,
390
+ type: z10.literal("assertText")
391
+ });
392
+ var assertUrlNode = z10.object({
393
+ ...nodeBase,
394
+ expected: stringValueRefSchema,
395
+ operator: comparisonOperator,
396
+ type: z10.literal("assertUrl")
397
+ });
398
+ var assertCountNode = z10.object({
399
+ ...nodeBase,
400
+ expected: numericValueRefSchema,
401
+ locator: locatorSchema,
402
+ operator: numericOperator,
403
+ type: z10.literal("assertCount")
404
+ });
405
+ var assertValueNode = z10.object({
406
+ ...nodeBase,
407
+ expected: stringValueRefSchema,
408
+ locator: locatorSchema,
409
+ operator: comparisonOperator,
410
+ type: z10.literal("assertValue")
411
+ });
412
+ var assertAttributeNode = z10.object({
413
+ ...nodeBase,
414
+ attribute: z10.string().min(1),
415
+ expected: stringValueRefSchema,
416
+ locator: locatorSchema,
417
+ operator: comparisonOperator,
418
+ type: z10.literal("assertAttribute")
419
+ });
420
+ var assertEnabledNode = z10.object({
421
+ ...nodeBase,
422
+ locator: locatorSchema,
423
+ type: z10.literal("assertEnabled")
424
+ });
425
+ var assertDisabledNode = z10.object({
426
+ ...nodeBase,
427
+ locator: locatorSchema,
428
+ type: z10.literal("assertDisabled")
429
+ });
430
+ var assertTitleNode = z10.object({
431
+ ...nodeBase,
432
+ expected: stringValueRefSchema,
433
+ operator: comparisonOperator,
434
+ type: z10.literal("assertTitle")
435
+ });
436
+ var assertCheckedNode = z10.object({
437
+ ...nodeBase,
438
+ locator: locatorSchema,
439
+ type: z10.literal("assertChecked")
440
+ });
441
+ var assertNotCheckedNode = z10.object({
442
+ ...nodeBase,
443
+ locator: locatorSchema,
444
+ type: z10.literal("assertNotChecked")
445
+ });
446
+ var assertFocusedNode = z10.object({
447
+ ...nodeBase,
448
+ locator: locatorSchema,
449
+ type: z10.literal("assertFocused")
450
+ });
451
+ var assertNotFocusedNode = z10.object({
452
+ ...nodeBase,
453
+ locator: locatorSchema,
454
+ type: z10.literal("assertNotFocused")
455
+ });
456
+ var specNodeSchema = z10.discriminatedUnion("type", [
457
+ gotoNode,
458
+ clickNode,
459
+ fillNode,
460
+ selectNode,
461
+ hoverNode,
462
+ pressNode,
463
+ checkNode,
464
+ uncheckNode,
465
+ assertVisibleNode,
466
+ assertNotVisibleNode,
467
+ assertTextNode,
468
+ assertUrlNode,
469
+ assertCountNode,
470
+ assertValueNode,
471
+ assertAttributeNode,
472
+ assertEnabledNode,
473
+ assertDisabledNode,
474
+ setViewportNode,
475
+ failNode,
476
+ setVariableNode,
477
+ extractTextNode,
478
+ uploadNode,
479
+ dblclickNode,
480
+ dragNode,
481
+ scrollIntoViewNode,
482
+ typeNode,
483
+ focusNode,
484
+ clearNode,
485
+ rightClickNode,
486
+ handleDialogNode,
487
+ clipboardNode,
488
+ setPermissionNode,
489
+ assertTitleNode,
490
+ assertCheckedNode,
491
+ assertNotCheckedNode,
492
+ assertFocusedNode,
493
+ assertNotFocusedNode
494
+ ]);
495
+ var workflowSpecSchema = z10.object({
496
+ entryNode: z10.string().min(1).max(200),
497
+ nodes: z10.record(z10.string().max(200), specNodeSchema).refine(
498
+ (nodes) => Object.keys(nodes).length <= MAX_NODES_PER_WORKFLOW,
499
+ `Workflow has more than ${String(MAX_NODES_PER_WORKFLOW)} nodes`
500
+ ),
501
+ variableNamespaces: z10.record(z10.string().max(200), z10.string().max(500)).optional(),
502
+ variables: z10.record(z10.string().max(200), variableDefSchema).optional()
503
+ });
504
+
505
+ // ../spec/src/codecs.ts
506
+ var preconditionMapSchema = z11.record(z11.string().max(200), preconditionSchema);
507
+ var looksLikeWorkflowSpec = {
508
+ assumedVersion: 1,
509
+ detect: (raw) => typeof raw === "object" && raw !== null && "entryNode" in raw && "nodes" in raw
510
+ };
511
+ var looksLikeStateGraph = {
512
+ assumedVersion: 1,
513
+ detect: (raw) => typeof raw === "object" && raw !== null && "edges" in raw && "states" in raw && "preconditions" in raw
514
+ };
515
+ var looksLikePreconditionMap = {
516
+ assumedVersion: 1,
517
+ detect: (raw) => typeof raw === "object" && raw !== null && !Array.isArray(raw) && !("__codec" in raw)
518
+ };
519
+ var workflowSpecCodec = defineCodec("workflow-spec").legacy(looksLikeWorkflowSpec).initial(workflowSpecSchema).build();
520
+ var stateGraphCodec = defineCodec("state-graph").legacy(looksLikeStateGraph).initial(stateGraphSchema).build();
521
+ var preconditionMapCodec = defineCodec("precondition-map").legacy(looksLikePreconditionMap).initial(preconditionMapSchema).build();
522
+
523
+ // src/lockfile.ts
524
+ import { z as z12 } from "zod";
525
+ var LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
526
+ var MAX_TESTS = 5e3;
527
+ var compiledTestSchema = z12.object({
528
+ description: z12.string().max(2e3),
529
+ expectedOutcome: z12.string().max(2e3),
530
+ implemented: z12.boolean(),
531
+ name: z12.string().max(200),
532
+ slug: z12.string().max(200),
533
+ spec: workflowSpecSchema
534
+ });
535
+ var lockfileBodySchema = z12.object({
536
+ graph: stateGraphSchema,
537
+ tests: z12.array(compiledTestSchema).max(MAX_TESTS)
538
+ });
539
+ var lockfileCodec = defineCodec("ripplo-lockfile").initial(lockfileBodySchema).build();
540
+ function compileResultToLockfile(result) {
541
+ return {
542
+ graph: result.graph,
543
+ tests: result.tests.map((test) => ({
544
+ description: test.description,
545
+ expectedOutcome: test.expectedOutcome,
546
+ implemented: test.implemented,
547
+ name: test.name,
548
+ slug: test.slug,
549
+ spec: test.spec
550
+ }))
551
+ };
552
+ }
553
+ function serializeLockfile(lockfile) {
554
+ const envelope = lockfileCodec.encode(lockfile);
555
+ return `${JSON.stringify(envelope, sortedReplacer(envelope), 2)}
556
+ `;
557
+ }
558
+ async function readLockfile({ cwd }) {
559
+ const lockfilePath = path.join(cwd, LOCKFILE_RELATIVE_PATH);
560
+ const raw = await readFileOrNull(lockfilePath);
561
+ if (raw == null) {
562
+ return null;
563
+ }
564
+ return decodeJson(lockfileCodec, raw);
565
+ }
566
+ async function writeLockfile({ cwd, result }) {
567
+ const lockfile = compileResultToLockfile(result);
568
+ const lockfilePath = path.join(cwd, LOCKFILE_RELATIVE_PATH);
569
+ await fs.mkdir(path.dirname(lockfilePath), { recursive: true });
570
+ await fs.writeFile(lockfilePath, serializeLockfile(lockfile), "utf8");
571
+ }
572
+ function compareCompileToLockfile({
573
+ compiled,
574
+ existing
575
+ }) {
576
+ if (existing == null) {
577
+ return "missing";
578
+ }
579
+ const fresh = serializeLockfile(compileResultToLockfile(compiled));
580
+ const current = serializeLockfile(existing);
581
+ return fresh === current ? "match" : "stale";
582
+ }
583
+ async function readFileOrNull(filePath) {
584
+ try {
585
+ return await fs.readFile(filePath, "utf8");
586
+ } catch (error) {
587
+ if (isNodeError(error) && error.code === "ENOENT") {
588
+ return null;
589
+ }
590
+ throw error;
591
+ }
592
+ }
593
+ function isNodeError(value) {
594
+ return value instanceof Error && "code" in value;
595
+ }
596
+ function sortedReplacer(root) {
597
+ return function replacer(_key, value) {
598
+ if (value === root || !isPlainObject(value)) {
599
+ return value;
600
+ }
601
+ return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)));
602
+ };
603
+ }
604
+ function isPlainObject(value) {
605
+ return typeof value === "object" && value !== null && !Array.isArray(value);
606
+ }
607
+ export {
608
+ LOCKFILE_RELATIVE_PATH,
609
+ compareCompileToLockfile,
610
+ compileResultToLockfile,
611
+ lockfileCodec,
612
+ readLockfile,
613
+ serializeLockfile,
614
+ writeLockfile
615
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ripplo/testing",
3
3
  "description": "TypeScript DSL for defining and running Ripplo e2e workflow tests",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -37,6 +37,11 @@
37
37
  "import": "./dist/compiler.js",
38
38
  "default": "./dist/compiler.js"
39
39
  },
40
+ "./lockfile": {
41
+ "types": "./dist/lockfile.d.ts",
42
+ "import": "./dist/lockfile.js",
43
+ "default": "./dist/lockfile.js"
44
+ },
40
45
  "./express": {
41
46
  "types": "./dist/express.d.ts",
42
47
  "import": "./dist/express.js",
@@ -55,7 +60,7 @@
55
60
  },
56
61
  "dependencies": {
57
62
  "standardwebhooks": "^1.0.0",
58
- "zod": "catalog:"
63
+ "zod": "^4.3.6"
59
64
  },
60
65
  "devDependencies": {
61
66
  "@types/express": "^5.0.2",