@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.
- package/README.md +11 -0
- package/dist/{chunk-GTHRSFXF.js → chunk-AQ52MYXE.js} +2 -2
- package/dist/compiler.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lockfile.d.ts +718 -0
- package/dist/lockfile.js +615 -0
- package/package.json +7 -2
package/dist/lockfile.js
ADDED
|
@@ -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.
|
|
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": "
|
|
63
|
+
"zod": "^4.3.6"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
66
|
"@types/express": "^5.0.2",
|