@ripplo/testing 0.6.1 → 0.7.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/DSL.md +355 -0
- package/LICENSE.md +1 -0
- package/README.md +47 -273
- package/dist/engine-BT7hUouB.d.ts +1095 -0
- package/dist/express.d.ts +7 -9
- package/dist/express.js +422 -48
- package/dist/index.d.ts +122 -59
- package/dist/index.js +1630 -1126
- package/package.json +31 -113
- package/dist/actions.d.ts +0 -260
- package/dist/actions.js +0 -177
- package/dist/assert.d.ts +0 -188
- package/dist/assert.js +0 -111
- package/dist/builder-SsgqYqSC.d.ts +0 -156
- package/dist/chunk-2YLI7VD4.js +0 -65
- package/dist/chunk-4MGIQFAJ.js +0 -16
- package/dist/chunk-DCJBLS2U.js +0 -26
- package/dist/chunk-MGATMMCZ.js +0 -16
- package/dist/chunk-XO36IU66.js +0 -88
- package/dist/chunk-YFOTJIVF.js +0 -134
- package/dist/chunk-YQAEOH5W.js +0 -111
- package/dist/compiler.d.ts +0 -32
- package/dist/compiler.js +0 -8
- package/dist/control.d.ts +0 -45
- package/dist/control.js +0 -17
- package/dist/elysia.d.ts +0 -78
- package/dist/elysia.js +0 -114
- package/dist/engine-BOqzK_go.d.ts +0 -61
- package/dist/fastify.d.ts +0 -14
- package/dist/fastify.js +0 -79
- package/dist/hono.d.ts +0 -19
- package/dist/hono.js +0 -89
- package/dist/koa.d.ts +0 -14
- package/dist/koa.js +0 -135
- package/dist/locators.d.ts +0 -40
- package/dist/locators.js +0 -11
- package/dist/lockfile.d.ts +0 -722
- package/dist/lockfile.js +0 -707
- package/dist/nestjs.d.ts +0 -17
- package/dist/nestjs.js +0 -139
- package/dist/nextjs.d.ts +0 -14
- package/dist/nextjs.js +0 -137
- package/dist/step-De52hTLd.d.ts +0 -19
- 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
|
-
};
|