@ripplo/testing 0.6.1 → 0.7.1

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.
Files changed (44) hide show
  1. package/DSL.md +357 -0
  2. package/LICENSE.md +1 -0
  3. package/README.md +47 -273
  4. package/dist/engine-BfvzXgLg.d.ts +1091 -0
  5. package/dist/express.d.ts +7 -9
  6. package/dist/express.js +422 -48
  7. package/dist/index.d.ts +134 -59
  8. package/dist/index.js +1654 -1126
  9. package/package.json +31 -113
  10. package/dist/actions.d.ts +0 -260
  11. package/dist/actions.js +0 -177
  12. package/dist/assert.d.ts +0 -188
  13. package/dist/assert.js +0 -111
  14. package/dist/builder-SsgqYqSC.d.ts +0 -156
  15. package/dist/chunk-2YLI7VD4.js +0 -65
  16. package/dist/chunk-4MGIQFAJ.js +0 -16
  17. package/dist/chunk-DCJBLS2U.js +0 -26
  18. package/dist/chunk-MGATMMCZ.js +0 -16
  19. package/dist/chunk-XO36IU66.js +0 -88
  20. package/dist/chunk-YFOTJIVF.js +0 -134
  21. package/dist/chunk-YQAEOH5W.js +0 -111
  22. package/dist/compiler.d.ts +0 -32
  23. package/dist/compiler.js +0 -8
  24. package/dist/control.d.ts +0 -45
  25. package/dist/control.js +0 -17
  26. package/dist/elysia.d.ts +0 -78
  27. package/dist/elysia.js +0 -114
  28. package/dist/engine-BOqzK_go.d.ts +0 -61
  29. package/dist/fastify.d.ts +0 -14
  30. package/dist/fastify.js +0 -79
  31. package/dist/hono.d.ts +0 -19
  32. package/dist/hono.js +0 -89
  33. package/dist/koa.d.ts +0 -14
  34. package/dist/koa.js +0 -135
  35. package/dist/locators.d.ts +0 -40
  36. package/dist/locators.js +0 -11
  37. package/dist/lockfile.d.ts +0 -722
  38. package/dist/lockfile.js +0 -707
  39. package/dist/nestjs.d.ts +0 -17
  40. package/dist/nestjs.js +0 -139
  41. package/dist/nextjs.d.ts +0 -14
  42. package/dist/nextjs.js +0 -137
  43. package/dist/step-De52hTLd.d.ts +0 -19
  44. package/dist/types-BzZrl65Z.d.ts +0 -115
package/dist/lockfile.js DELETED
@@ -1,707 +0,0 @@
1
- import "./chunk-4MGIQFAJ.js";
2
-
3
- // src/lockfile.ts
4
- import { createHash } from "crypto";
5
- import fs from "fs/promises";
6
- import path from "path";
7
-
8
- // ../spec/src/codec.ts
9
- import { z } from "zod";
10
- var envelopeSchema = z.object({
11
- __codec: z.string().min(1),
12
- data: z.unknown(),
13
- version: z.number().int().positive()
14
- });
15
- var CodecVersionError = class extends Error {
16
- codec;
17
- currentVersion;
18
- gotVersion;
19
- constructor(params) {
20
- super(
21
- `Unsupported ${params.codec} version ${String(params.gotVersion)} (current ${String(params.currentVersion)}). Upgrade Ripplo or rebuild with a compatible CLI.`
22
- );
23
- this.name = "CodecVersionError";
24
- this.codec = params.codec;
25
- this.currentVersion = params.currentVersion;
26
- this.gotVersion = params.gotVersion;
27
- }
28
- };
29
- var CodecMismatchError = class extends Error {
30
- constructor(params) {
31
- super(`Codec mismatch: expected "${params.expected}", got "${params.got}"`);
32
- this.name = "CodecMismatchError";
33
- }
34
- };
35
- function defineCodec(name) {
36
- return makeInitial({ legacy: void 0, migrators: [], name, schemas: [] });
37
- }
38
- function decodeJson(codec, raw) {
39
- const parsed = JSON.parse(raw);
40
- return codec.decode(parsed);
41
- }
42
- function makeInitial(state) {
43
- return {
44
- initial: (schema) => makeBuilder(schema, { ...state, schemas: [schema] }),
45
- legacy: (adapter) => makeInitial({ ...state, legacy: adapter })
46
- };
47
- }
48
- function makeBuilder(latestSchema, state) {
49
- return {
50
- build: () => buildCodec(latestSchema, state),
51
- legacy: (adapter) => makeBuilder(latestSchema, { ...state, legacy: adapter }),
52
- upgrade: (schema, up) => {
53
- const erased = up;
54
- return makeBuilder(schema, {
55
- ...state,
56
- migrators: [...state.migrators, erased],
57
- schemas: [...state.schemas, schema]
58
- });
59
- }
60
- };
61
- }
62
- function buildCodec(latestSchema, state) {
63
- const currentVersion = state.schemas.length;
64
- return {
65
- currentVersion,
66
- name: state.name,
67
- decode: (raw) => decodeWith(latestSchema, state, raw),
68
- encode: (value) => ({
69
- __codec: state.name,
70
- data: value,
71
- version: currentVersion
72
- })
73
- };
74
- }
75
- function decodeWith(latestSchema, state, raw) {
76
- const { data, version } = unwrapEnvelope(state, raw);
77
- const migrated = migrateStep(state, data, version);
78
- return latestSchema.parse(migrated);
79
- }
80
- function unwrapEnvelope(state, raw) {
81
- const envelopeResult = envelopeSchema.safeParse(raw);
82
- if (envelopeResult.success) {
83
- if (envelopeResult.data.__codec !== state.name) {
84
- throw new CodecMismatchError({
85
- expected: state.name,
86
- got: envelopeResult.data.__codec
87
- });
88
- }
89
- return {
90
- data: envelopeResult.data.data,
91
- version: envelopeResult.data.version
92
- };
93
- }
94
- if (state.legacy != null && state.legacy.detect(raw)) {
95
- return { data: raw, version: state.legacy.assumedVersion };
96
- }
97
- throw new Error(
98
- `Cannot decode "${state.name}": value is not a codec envelope and no legacy detector matched.`
99
- );
100
- }
101
- function migrateStep(state, data, version) {
102
- const currentVersion = state.schemas.length;
103
- if (version > currentVersion) {
104
- throw new CodecVersionError({
105
- codec: state.name,
106
- currentVersion,
107
- gotVersion: version
108
- });
109
- }
110
- if (version === currentVersion) {
111
- return data;
112
- }
113
- const sourceSchema = state.schemas[version - 1];
114
- if (sourceSchema == null) {
115
- throw new Error(
116
- `Codec "${state.name}" missing schema for v${String(version)}; cannot migrate.`
117
- );
118
- }
119
- const validated = sourceSchema.parse(data);
120
- const migrator = state.migrators[version - 1];
121
- if (migrator == null) {
122
- throw new Error(
123
- `Codec "${state.name}" missing migrator v${String(version)} \u2192 v${String(version + 1)}.`
124
- );
125
- }
126
- return migrateStep(state, migrator(validated), version + 1);
127
- }
128
-
129
- // ../spec/src/codecs.ts
130
- import { z as z8 } from "zod";
131
-
132
- // ../spec/src/precondition.ts
133
- import { z as z2 } from "zod";
134
- var preconditionSchema = z2.object({
135
- depends: z2.array(z2.string().min(1)).optional().describe(
136
- "Names of other preconditions that must be satisfied first. Resolved via topological sort; cycles are rejected at validation time."
137
- ),
138
- description: z2.string().min(1).describe("Human-readable description of what this precondition ensures"),
139
- returns: z2.array(z2.string().min(1)).optional().describe(
140
- "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."
141
- )
142
- }).describe("A named precondition declared at the graph level. States reference these by name.");
143
-
144
- // ../spec/src/schema.ts
145
- import { z as z7 } from "zod";
146
-
147
- // ../spec/src/locators.ts
148
- import { z as z3 } from "zod";
149
- var testIdLocator = z3.object({
150
- by: z3.literal("testId"),
151
- value: z3.string().min(1)
152
- });
153
- var roleLocator = z3.object({
154
- by: z3.literal("role"),
155
- name: z3.string().optional(),
156
- role: z3.string().min(1)
157
- });
158
- var locatorSchema = z3.discriminatedUnion("by", [testIdLocator, roleLocator]);
159
-
160
- // ../spec/src/operators.ts
161
- import { z as z4 } from "zod";
162
- var comparisonOperator = z4.enum([
163
- "equals",
164
- "notEquals",
165
- "contains",
166
- "startsWith",
167
- "endsWith",
168
- "matches"
169
- ]);
170
- var numericOperator = z4.enum([
171
- "equals",
172
- "notEquals",
173
- "greaterThan",
174
- "greaterThanOrEqual",
175
- "lessThan",
176
- "lessThanOrEqual"
177
- ]);
178
-
179
- // ../spec/src/value-ref.ts
180
- import { z as z5 } from "zod";
181
- var staticValueSchema = z5.object({
182
- type: z5.literal("static"),
183
- value: z5.union([z5.string(), z5.number(), z5.boolean()])
184
- });
185
- var variableRefSchema = z5.object({
186
- name: z5.string().min(1),
187
- type: z5.literal("variable")
188
- });
189
- var valueRefSchema = z5.discriminatedUnion("type", [staticValueSchema, variableRefSchema]);
190
- var stringValueRefSchema = z5.discriminatedUnion("type", [
191
- z5.object({ type: z5.literal("static"), value: z5.string() }),
192
- variableRefSchema
193
- ]);
194
- var numericValueRefSchema = z5.discriminatedUnion("type", [
195
- z5.object({ type: z5.literal("static"), value: z5.number().int().nonnegative() }),
196
- variableRefSchema
197
- ]);
198
- var primitiveValueRefSchema = z5.discriminatedUnion("type", [
199
- z5.object({
200
- type: z5.literal("static"),
201
- value: z5.union([z5.string(), z5.number(), z5.boolean()])
202
- }),
203
- variableRefSchema
204
- ]);
205
-
206
- // ../spec/src/variables.ts
207
- import { z as z6 } from "zod";
208
- var variableDefSchema = z6.discriminatedUnion("type", [
209
- z6.object({
210
- default: z6.string().optional(),
211
- type: z6.literal("string")
212
- }),
213
- z6.object({
214
- default: z6.number().optional(),
215
- type: z6.literal("number")
216
- }),
217
- z6.object({
218
- default: z6.boolean().optional(),
219
- type: z6.literal("boolean")
220
- }),
221
- z6.object({
222
- key: z6.string().min(1),
223
- type: z6.literal("env")
224
- })
225
- ]);
226
-
227
- // ../spec/src/schema.ts
228
- var nodeBase = {
229
- id: z7.string().min(1).max(200),
230
- label: z7.string().max(500).optional(),
231
- next: z7.string().max(200).optional(),
232
- uiOnly: z7.boolean().optional()
233
- };
234
- var MAX_NODES_PER_WORKFLOW = 500;
235
- var gotoNode = z7.object({
236
- ...nodeBase,
237
- type: z7.literal("goto"),
238
- url: stringValueRefSchema
239
- });
240
- var clickNode = z7.object({
241
- ...nodeBase,
242
- locator: locatorSchema,
243
- modifier: z7.enum(["Alt", "Control", "Meta", "Shift"]).optional(),
244
- type: z7.literal("click")
245
- });
246
- var fillNode = z7.object({
247
- ...nodeBase,
248
- locator: locatorSchema,
249
- type: z7.literal("fill"),
250
- value: stringValueRefSchema
251
- });
252
- var selectNode = z7.object({
253
- ...nodeBase,
254
- locator: locatorSchema,
255
- type: z7.literal("select"),
256
- value: stringValueRefSchema
257
- });
258
- var hoverNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("hover") });
259
- var pressNode = z7.object({
260
- ...nodeBase,
261
- key: z7.string().min(1),
262
- locator: locatorSchema.optional(),
263
- type: z7.literal("press")
264
- });
265
- var checkNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("check") });
266
- var uncheckNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("uncheck") });
267
- var setViewportNode = z7.object({
268
- ...nodeBase,
269
- height: z7.number().int().positive(),
270
- type: z7.literal("setViewport"),
271
- width: z7.number().int().positive()
272
- });
273
- var failNode = z7.object({ ...nodeBase, message: z7.string().min(1), type: z7.literal("fail") });
274
- var setVariableNode = z7.object({
275
- ...nodeBase,
276
- type: z7.literal("setVariable"),
277
- value: valueRefSchema,
278
- variable: z7.string().min(1)
279
- });
280
- var extractTextNode = z7.object({
281
- ...nodeBase,
282
- locator: locatorSchema,
283
- type: z7.literal("extractText"),
284
- variable: z7.string().min(1)
285
- });
286
- var uploadNode = z7.object({
287
- ...nodeBase,
288
- files: z7.array(z7.string()).min(1),
289
- locator: locatorSchema,
290
- type: z7.literal("upload")
291
- });
292
- var dblclickNode = z7.object({
293
- ...nodeBase,
294
- locator: locatorSchema,
295
- type: z7.literal("dblclick")
296
- });
297
- var dragNode = z7.object({
298
- ...nodeBase,
299
- source: locatorSchema,
300
- target: locatorSchema,
301
- type: z7.literal("drag")
302
- });
303
- var scrollIntoViewNode = z7.object({
304
- ...nodeBase,
305
- locator: locatorSchema,
306
- type: z7.literal("scrollIntoView")
307
- });
308
- var typeNode = z7.object({
309
- ...nodeBase,
310
- locator: locatorSchema,
311
- type: z7.literal("type"),
312
- value: stringValueRefSchema
313
- });
314
- var focusNode = z7.object({
315
- ...nodeBase,
316
- locator: locatorSchema,
317
- type: z7.literal("focus")
318
- });
319
- var clearNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("clear") });
320
- var rightClickNode = z7.object({
321
- ...nodeBase,
322
- locator: locatorSchema,
323
- type: z7.literal("rightClick")
324
- });
325
- var handleDialogNode = z7.object({
326
- ...nodeBase,
327
- action: z7.enum(["accept", "dismiss"]),
328
- promptText: z7.string().optional(),
329
- type: z7.literal("handleDialog")
330
- });
331
- var clipboardNode = z7.object({
332
- ...nodeBase,
333
- action: z7.enum(["read", "write"]),
334
- type: z7.literal("clipboard"),
335
- value: stringValueRefSchema.optional(),
336
- variable: z7.string().min(1).optional()
337
- });
338
- var setPermissionNode = z7.object({
339
- ...nodeBase,
340
- permission: z7.string().min(1),
341
- state: z7.enum(["granted", "prompt"]),
342
- type: z7.literal("setPermission")
343
- });
344
- var assertVisibleNode = z7.object({
345
- ...nodeBase,
346
- locator: locatorSchema,
347
- type: z7.literal("assertVisible")
348
- });
349
- var assertNotVisibleNode = z7.object({
350
- ...nodeBase,
351
- locator: locatorSchema,
352
- type: z7.literal("assertNotVisible")
353
- });
354
- var assertTextNode = z7.object({
355
- ...nodeBase,
356
- expected: stringValueRefSchema,
357
- locator: locatorSchema,
358
- operator: comparisonOperator,
359
- type: z7.literal("assertText")
360
- });
361
- var assertUrlNode = z7.object({
362
- ...nodeBase,
363
- expected: stringValueRefSchema,
364
- operator: comparisonOperator,
365
- type: z7.literal("assertUrl")
366
- });
367
- var assertCountNode = z7.object({
368
- ...nodeBase,
369
- expected: numericValueRefSchema,
370
- locator: locatorSchema,
371
- operator: numericOperator,
372
- type: z7.literal("assertCount")
373
- });
374
- var assertValueNode = z7.object({
375
- ...nodeBase,
376
- expected: stringValueRefSchema,
377
- locator: locatorSchema,
378
- operator: comparisonOperator,
379
- type: z7.literal("assertValue")
380
- });
381
- var assertAttributeNode = z7.object({
382
- ...nodeBase,
383
- attribute: z7.string().min(1),
384
- expected: stringValueRefSchema,
385
- locator: locatorSchema,
386
- operator: comparisonOperator,
387
- type: z7.literal("assertAttribute")
388
- });
389
- var assertEnabledNode = z7.object({
390
- ...nodeBase,
391
- locator: locatorSchema,
392
- type: z7.literal("assertEnabled")
393
- });
394
- var assertDisabledNode = z7.object({
395
- ...nodeBase,
396
- locator: locatorSchema,
397
- type: z7.literal("assertDisabled")
398
- });
399
- var assertTitleNode = z7.object({
400
- ...nodeBase,
401
- expected: stringValueRefSchema,
402
- operator: comparisonOperator,
403
- type: z7.literal("assertTitle")
404
- });
405
- var assertCheckedNode = z7.object({
406
- ...nodeBase,
407
- locator: locatorSchema,
408
- type: z7.literal("assertChecked")
409
- });
410
- var assertNotCheckedNode = z7.object({
411
- ...nodeBase,
412
- locator: locatorSchema,
413
- type: z7.literal("assertNotChecked")
414
- });
415
- var assertFocusedNode = z7.object({
416
- ...nodeBase,
417
- locator: locatorSchema,
418
- type: z7.literal("assertFocused")
419
- });
420
- var assertNotFocusedNode = z7.object({
421
- ...nodeBase,
422
- locator: locatorSchema,
423
- type: z7.literal("assertNotFocused")
424
- });
425
- var assertObserverNode = z7.object({
426
- ...nodeBase,
427
- budget: z7.enum(["fast", "slow", "async"]),
428
- observer: z7.string().min(1).max(200),
429
- params: z7.record(z7.string().max(200), primitiveValueRefSchema),
430
- type: z7.literal("assertObserver")
431
- });
432
- var specNodeSchema = z7.discriminatedUnion("type", [
433
- gotoNode,
434
- clickNode,
435
- fillNode,
436
- selectNode,
437
- hoverNode,
438
- pressNode,
439
- checkNode,
440
- uncheckNode,
441
- assertVisibleNode,
442
- assertNotVisibleNode,
443
- assertTextNode,
444
- assertUrlNode,
445
- assertCountNode,
446
- assertValueNode,
447
- assertAttributeNode,
448
- assertEnabledNode,
449
- assertDisabledNode,
450
- setViewportNode,
451
- failNode,
452
- setVariableNode,
453
- extractTextNode,
454
- uploadNode,
455
- dblclickNode,
456
- dragNode,
457
- scrollIntoViewNode,
458
- typeNode,
459
- focusNode,
460
- clearNode,
461
- rightClickNode,
462
- handleDialogNode,
463
- clipboardNode,
464
- setPermissionNode,
465
- assertTitleNode,
466
- assertCheckedNode,
467
- assertNotCheckedNode,
468
- assertFocusedNode,
469
- assertNotFocusedNode,
470
- assertObserverNode
471
- ]);
472
- var workflowSpecSchema = z7.object({
473
- entryNode: z7.string().min(1).max(200),
474
- nodes: z7.record(z7.string().max(200), specNodeSchema).refine(
475
- (nodes) => Object.keys(nodes).length <= MAX_NODES_PER_WORKFLOW,
476
- `Workflow has more than ${String(MAX_NODES_PER_WORKFLOW)} nodes`
477
- ),
478
- uiOnly: z7.boolean().optional(),
479
- variableNamespaces: z7.record(z7.string().max(200), z7.string().max(500)).optional(),
480
- variables: z7.record(z7.string().max(200), variableDefSchema).optional()
481
- });
482
-
483
- // ../spec/src/codecs.ts
484
- var preconditionMapSchema = z8.record(z8.string().max(200), preconditionSchema);
485
- var looksLikeWorkflowSpec = {
486
- assumedVersion: 1,
487
- detect: (raw) => typeof raw === "object" && raw !== null && "entryNode" in raw && "nodes" in raw
488
- };
489
- var looksLikePreconditionMap = {
490
- assumedVersion: 1,
491
- detect: (raw) => typeof raw === "object" && raw !== null && !Array.isArray(raw) && !("__codec" in raw)
492
- };
493
- var workflowSpecCodec = defineCodec("workflow-spec").legacy(looksLikeWorkflowSpec).initial(workflowSpecSchema).build();
494
- var preconditionMapCodec = defineCodec("precondition-map").legacy(looksLikePreconditionMap).initial(preconditionMapSchema).build();
495
-
496
- // ../spec/src/observer.ts
497
- import { z as z9 } from "zod";
498
- var OBSERVER_BUDGETS = ["fast", "slow", "async"];
499
- var observerSchema = z9.object({
500
- budget: z9.enum(OBSERVER_BUDGETS).describe("Polling budget tier: fast | slow | async"),
501
- description: z9.string().min(1).describe("Human-readable description of what this observer checks")
502
- }).describe(
503
- "A named backend state observer. Tests assert against it with assert.backend(observer, params). Implementation lives on the user's server."
504
- );
505
-
506
- // src/lockfile.ts
507
- import { z as z10 } from "zod";
508
- var LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
509
- var FIXTURES_RELATIVE_PATH = ".ripplo/fixtures";
510
- var MAX_TESTS = 5e3;
511
- var requiresKeysSchema = z10.record(z10.string().max(200), z10.string().max(200));
512
- var compiledTestSchema = z10.object({
513
- coverage: z10.array(z10.string().max(500)).max(2e3).default([]),
514
- expectedOutcome: z10.string().max(2e3),
515
- name: z10.string().max(200),
516
- preconditions: z10.array(z10.string().max(200)).max(1e3),
517
- requiresKeys: requiresKeysSchema,
518
- slug: z10.string().max(200),
519
- sourcePath: z10.string().max(500).optional(),
520
- spec: workflowSpecSchema
521
- });
522
- var fixtureEntrySchema = z10.object({
523
- sha256: z10.string().regex(/^[0-9a-f]{64}$/u),
524
- size: z10.number().int().nonnegative()
525
- });
526
- var fixturesMapSchema = z10.record(z10.string().min(1).max(500), fixtureEntrySchema);
527
- var lockfileBodyV1Schema = z10.object({
528
- observers: z10.record(z10.string().max(200), observerSchema),
529
- preconditions: z10.record(z10.string().max(200), preconditionSchema),
530
- tests: z10.array(compiledTestSchema).max(MAX_TESTS)
531
- });
532
- var lockfileBodyV2Schema = z10.object({
533
- fixtures: fixturesMapSchema,
534
- observers: z10.record(z10.string().max(200), observerSchema),
535
- preconditions: z10.record(z10.string().max(200), preconditionSchema),
536
- tests: z10.array(compiledTestSchema).max(MAX_TESTS)
537
- });
538
- var lockfileCodec = defineCodec("ripplo-lockfile").initial(lockfileBodyV1Schema).upgrade(
539
- lockfileBodyV2Schema,
540
- (prev) => ({
541
- ...prev,
542
- fixtures: {}
543
- })
544
- ).build();
545
- function compileResultToLockfile(result) {
546
- return {
547
- fixtures: { ...result.fixtures },
548
- observers: result.observers,
549
- preconditions: result.preconditions,
550
- tests: result.tests.filter((test) => test.implemented).map((test) => ({
551
- coverage: [...test.coverage],
552
- expectedOutcome: test.expectedOutcome,
553
- name: test.name,
554
- preconditions: [...test.preconditions],
555
- requiresKeys: { ...test.requiresKeys },
556
- slug: test.slug,
557
- sourcePath: test.sourcePath,
558
- spec: test.spec
559
- }))
560
- };
561
- }
562
- function serializeLockfile(lockfile) {
563
- const envelope = lockfileCodec.encode(lockfile);
564
- return `${JSON.stringify(envelope, sortedReplacer(envelope), 2)}
565
- `;
566
- }
567
- async function readLockfile({ cwd }) {
568
- const lockfilePath = path.join(cwd, LOCKFILE_RELATIVE_PATH);
569
- const raw = await readFileOrNull(lockfilePath);
570
- if (raw == null) {
571
- return null;
572
- }
573
- return decodeJson(lockfileCodec, raw);
574
- }
575
- var MAX_FIXTURE_BYTES = 10 * 1024 * 1024;
576
- var MAX_TOTAL_FIXTURE_BYTES = 50 * 1024 * 1024;
577
- async function writeLockfile({ cwd, result }) {
578
- const hydrated = await hashFixturesIntoCompileResult({ cwd, result });
579
- const lockfile = compileResultToLockfile(hydrated);
580
- const lockfilePath = path.join(cwd, LOCKFILE_RELATIVE_PATH);
581
- await fs.mkdir(path.dirname(lockfilePath), { recursive: true });
582
- await fs.writeFile(lockfilePath, serializeLockfile(lockfile), "utf8");
583
- }
584
- async function hashFixturesIntoCompileResult({
585
- cwd,
586
- result
587
- }) {
588
- const referenced = collectFixtureReferences(result);
589
- if (referenced.size === 0) {
590
- return { ...result, fixtures: {} };
591
- }
592
- const fixturesRoot = path.join(cwd, FIXTURES_RELATIVE_PATH);
593
- const sortedNames = [...referenced].toSorted((a, b) => a.localeCompare(b));
594
- const hashed = await Promise.all(
595
- sortedNames.map(async (name) => {
596
- const entry = await hashOneFixture({ fixturesRoot, name });
597
- if (entry.size > MAX_FIXTURE_BYTES) {
598
- throw new Error(
599
- `Fixture "${name}" is ${String(entry.size)} bytes; exceeds per-file limit of ${String(MAX_FIXTURE_BYTES)} bytes`
600
- );
601
- }
602
- return [name, entry];
603
- })
604
- );
605
- const total = hashed.reduce((sum, [, entry]) => sum + entry.size, 0);
606
- if (total > MAX_TOTAL_FIXTURE_BYTES) {
607
- throw new Error(
608
- `Total fixtures size exceeds limit of ${String(MAX_TOTAL_FIXTURE_BYTES)} bytes`
609
- );
610
- }
611
- return { ...result, fixtures: Object.fromEntries(hashed) };
612
- }
613
- async function hashOneFixture({ fixturesRoot, name }) {
614
- const rawName = name;
615
- if (typeof rawName !== "string" || rawName.length === 0) {
616
- throw new Error(
617
- `Internal error: upload step produced a non-string fixture name (got ${rawName === null ? "null" : typeof rawName}). This usually means a test passed a non-Fixture value to upload() \u2014 wrap the filename with fixture("file.png").`
618
- );
619
- }
620
- if (name.includes("..") || path.isAbsolute(name)) {
621
- throw new Error(`Invalid fixture name "${name}": must be a path under .ripplo/fixtures/`);
622
- }
623
- const abs = path.join(fixturesRoot, name);
624
- const stat = await fs.lstat(abs).catch((error) => {
625
- if (isNodeError(error) && error.code === "ENOENT") {
626
- throw new Error(`Fixture "${name}" not found at ${abs}`);
627
- }
628
- throw error;
629
- });
630
- if (stat.isSymbolicLink()) {
631
- throw new Error(`Fixture "${name}" is a symlink; symlinks are not allowed`);
632
- }
633
- if (!stat.isFile()) {
634
- throw new Error(`Fixture "${name}" is not a regular file`);
635
- }
636
- const bytes = await fs.readFile(abs);
637
- const sha256 = createHash("sha256").update(bytes).digest("hex");
638
- return { sha256, size: bytes.byteLength };
639
- }
640
- function collectFixtureReferences(result) {
641
- const names = /* @__PURE__ */ new Set();
642
- result.tests.forEach((test) => {
643
- Object.entries(test.spec.nodes).forEach(([stepId, node]) => {
644
- if (node.type !== "upload") {
645
- return;
646
- }
647
- node.files.forEach((name, i) => {
648
- const raw = name;
649
- if (typeof raw !== "string" || raw.length === 0) {
650
- throw new Error(
651
- `Test "${test.slug}" step "${stepId}" upload files[${String(i)}] is not a non-empty string (got ${raw === null ? "null" : typeof raw}). Wrap filenames with fixture(): upload(loc, fixture("file.png")).`
652
- );
653
- }
654
- names.add(raw);
655
- });
656
- });
657
- });
658
- return names;
659
- }
660
- async function compareCompileToLockfile({
661
- compiled,
662
- cwd,
663
- existing
664
- }) {
665
- if (existing == null) {
666
- return "missing";
667
- }
668
- const hydrated = await hashFixturesIntoCompileResult({ cwd, result: compiled });
669
- const fresh = serializeLockfile(compileResultToLockfile(hydrated));
670
- const current = serializeLockfile(existing);
671
- return fresh === current ? "match" : "stale";
672
- }
673
- async function readFileOrNull(filePath) {
674
- try {
675
- return await fs.readFile(filePath, "utf8");
676
- } catch (error) {
677
- if (isNodeError(error) && error.code === "ENOENT") {
678
- return null;
679
- }
680
- throw error;
681
- }
682
- }
683
- function isNodeError(value) {
684
- return value instanceof Error && "code" in value;
685
- }
686
- function sortedReplacer(root) {
687
- return function replacer(_key, value) {
688
- if (value === root || !isPlainObject(value)) {
689
- return value;
690
- }
691
- return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)));
692
- };
693
- }
694
- function isPlainObject(value) {
695
- return typeof value === "object" && value !== null && !Array.isArray(value);
696
- }
697
- export {
698
- FIXTURES_RELATIVE_PATH,
699
- LOCKFILE_RELATIVE_PATH,
700
- compareCompileToLockfile,
701
- compileResultToLockfile,
702
- hashFixturesIntoCompileResult,
703
- lockfileCodec,
704
- readLockfile,
705
- serializeLockfile,
706
- writeLockfile
707
- };