@ripplo/testing 0.0.7 → 0.0.9

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.
@@ -8,9 +8,16 @@ function compile(ripplo) {
8
8
  const preconditionDefs = ripplo.getPreconditions();
9
9
  const testDefs = ripplo.getTests();
10
10
  validateUniqueIds(testDefs);
11
- const tests = testDefs.map((def) => compileTest(def));
12
- const graph = compileGraph(preconditionDefs, testDefs);
13
- return { config: ripplo.getConfig(), graph, tests };
11
+ const preconditions = {};
12
+ preconditionDefs.forEach((def) => {
13
+ preconditions[def.name] = {
14
+ depends: [...def.dependsOn],
15
+ description: def.description,
16
+ returns: [...def.returns]
17
+ };
18
+ });
19
+ const tests = testDefs.map((def) => compileTest(def, preconditionDefs));
20
+ return { config: ripplo.getConfig(), preconditions, tests };
14
21
  }
15
22
  function validateUniqueIds(defs) {
16
23
  const seen = /* @__PURE__ */ new Map();
@@ -22,7 +29,7 @@ function validateUniqueIds(defs) {
22
29
  seen.set(def.id, def.name);
23
30
  });
24
31
  }
25
- function compileTest(def) {
32
+ function compileTest(def, preconditionDefs) {
26
33
  const slug = def.id;
27
34
  const { accessedKeys, vars } = buildPlaceholderVars(def.requiresKeys);
28
35
  const startsAtUrl = def.startsAtFn == null ? void 0 : def.startsAtFn(vars);
@@ -36,12 +43,15 @@ function compileTest(def) {
36
43
  "Test requires preconditions but never references their data \u2014 destructure and use precondition data in steps()"
37
44
  );
38
45
  }
46
+ const resolvedPreconditions = resolveDependencyChain(def.requires, preconditionDefs);
39
47
  return {
40
48
  additionalChecks: [],
41
49
  description: def.description,
42
50
  expectedOutcome: def.expectedOutcome,
43
51
  implemented: def.implemented,
44
52
  name: def.name,
53
+ preconditions: resolvedPreconditions,
54
+ requiresKeys: { ...def.requiresKeys },
45
55
  slug,
46
56
  spec,
47
57
  warnings
@@ -83,41 +93,12 @@ function compileSteps(steps, accessedKeys, requiresKeys) {
83
93
  variables[key] = { default: `test-${key}`, type: "string" };
84
94
  });
85
95
  const variableNamespaces = { ...requiresKeys };
86
- return { entryNode: "step-0", nodes, variableNamespaces, variables, version: 2 };
96
+ return { entryNode: "step-0", nodes, variableNamespaces, variables };
87
97
  }
88
98
  function compileNode(step, id, next) {
89
99
  const { label, node: raw } = readStep(step);
90
100
  return { ...raw, id, label, next };
91
101
  }
92
- function compileGraph(preconditionDefs, testDefs) {
93
- const preconditions = {};
94
- preconditionDefs.forEach((def) => {
95
- preconditions[def.name] = {
96
- depends: [...def.dependsOn],
97
- description: def.description,
98
- returns: [...def.returns]
99
- };
100
- });
101
- const states = {};
102
- const edges = [];
103
- testDefs.forEach((def) => {
104
- if (!def.implemented || def.startsAtFn == null) {
105
- return;
106
- }
107
- const resolved = resolveDependencyChain(def.requires, preconditionDefs);
108
- const { vars } = buildPlaceholderVars(def.requiresKeys);
109
- const route = def.startsAtFn(vars);
110
- const stateId = deriveStateId(resolved, route);
111
- states[stateId] = { preconditions: [...resolved], route };
112
- edges.push({
113
- from: stateId,
114
- requiresKeys: def.requiresKeys,
115
- to: stateId,
116
- workflow: def.id
117
- });
118
- });
119
- return { edges, preconditions, states, version: 3 };
120
- }
121
102
  function resolveDependencyChain(requires, preconditionDefs) {
122
103
  const defsByName = new Map(preconditionDefs.map((d) => [d.name, d]));
123
104
  const resolved = [];
@@ -137,13 +118,6 @@ function resolveDependencyChain(requires, preconditionDefs) {
137
118
  });
138
119
  return resolved;
139
120
  }
140
- function deriveStateId(preconditions, route) {
141
- const sorted = preconditions.toSorted((a, b) => a.localeCompare(b));
142
- return slugify(`${sorted.join("-")}-${route}`);
143
- }
144
- function slugify(input) {
145
- return input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "");
146
- }
147
121
 
148
122
  export {
149
123
  compile
@@ -1,11 +1,11 @@
1
- import { StateGraph, WorkflowSpec } from '@ripplo/spec';
1
+ import { Precondition, WorkflowSpec } from '@ripplo/spec';
2
2
  import { D as DslConfig, R as RipploBuilder } from './builder-DTWMrbuv.js';
3
3
  import 'zod';
4
4
  import './step-DLfkKI3V.js';
5
5
 
6
6
  interface CompileResult {
7
7
  readonly config: DslConfig;
8
- readonly graph: StateGraph;
8
+ readonly preconditions: Record<string, Precondition>;
9
9
  readonly tests: ReadonlyArray<CompiledTest>;
10
10
  }
11
11
  interface CompiledTest {
@@ -14,6 +14,8 @@ interface CompiledTest {
14
14
  readonly expectedOutcome: string;
15
15
  readonly implemented: boolean;
16
16
  readonly name: string;
17
+ readonly preconditions: ReadonlyArray<string>;
18
+ readonly requiresKeys: Record<string, string>;
17
19
  readonly slug: string;
18
20
  readonly spec: WorkflowSpec;
19
21
  readonly warnings: ReadonlyArray<string>;
package/dist/compiler.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compile
3
- } from "./chunk-GTHRSFXF.js";
3
+ } from "./chunk-LEIKZ6BE.js";
4
4
  import "./chunk-MGATMMCZ.js";
5
5
  export {
6
6
  compile
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compile
3
- } from "./chunk-GTHRSFXF.js";
3
+ } from "./chunk-LEIKZ6BE.js";
4
4
  import "./chunk-MGATMMCZ.js";
5
5
  import {
6
6
  buildSetCookieHeader,
@@ -1,37 +1,21 @@
1
+ import { Codec } from '@ripplo/spec';
1
2
  import { z } from 'zod';
2
3
  import { CompileResult } from './compiler.js';
3
- import '@ripplo/spec';
4
4
  import './builder-DTWMrbuv.js';
5
5
  import './step-DLfkKI3V.js';
6
6
 
7
- declare const LOCKFILE_VERSION = 1;
8
7
  declare const LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
9
- declare const lockfileSchema: z.ZodObject<{
10
- graph: z.ZodObject<{
11
- edges: z.ZodArray<z.ZodObject<{
12
- from: z.ZodString;
13
- requiresKeys: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
14
- to: z.ZodString;
15
- workflow: z.ZodString;
16
- }, z.core.$strip>>;
17
- preconditions: z.ZodRecord<z.ZodString, z.ZodObject<{
18
- depends: z.ZodOptional<z.ZodArray<z.ZodString>>;
19
- description: z.ZodString;
20
- returns: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
- }, z.core.$strip>>;
22
- states: z.ZodRecord<z.ZodString, z.ZodObject<{
23
- preconditions: z.ZodArray<z.ZodString>;
24
- route: z.ZodString;
25
- }, z.core.$strip>>;
26
- version: z.ZodLiteral<3>;
27
- }, z.core.$strip>;
28
- lockfileVersion: z.ZodNumber;
29
- tests: z.ZodArray<z.ZodObject<{
30
- additionalChecks: z.ZodArray<z.ZodString>;
8
+ declare const lockfileBodySchema: z.ZodObject<{
9
+ preconditions: z.ZodRecord<z.ZodString, z.ZodObject<{
10
+ depends: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
11
  description: z.ZodString;
12
+ returns: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ }, z.core.$strip>>;
14
+ tests: z.ZodArray<z.ZodObject<{
32
15
  expectedOutcome: z.ZodString;
33
- implemented: z.ZodBoolean;
34
16
  name: z.ZodString;
17
+ preconditions: z.ZodArray<z.ZodString>;
18
+ requiresKeys: z.ZodRecord<z.ZodString, z.ZodString>;
35
19
  slug: z.ZodString;
36
20
  spec: z.ZodObject<{
37
21
  entryNode: z.ZodString;
@@ -696,12 +680,11 @@ declare const lockfileSchema: z.ZodObject<{
696
680
  key: z.ZodString;
697
681
  type: z.ZodLiteral<"env">;
698
682
  }, z.core.$strip>], "type">>>;
699
- version: z.ZodLiteral<2>;
700
683
  }, z.core.$strip>;
701
- warnings: z.ZodArray<z.ZodString>;
702
684
  }, z.core.$strip>>;
703
685
  }, z.core.$strip>;
704
- type Lockfile = z.infer<typeof lockfileSchema>;
686
+ type Lockfile = z.infer<typeof lockfileBodySchema>;
687
+ declare const lockfileCodec: Codec<Lockfile>;
705
688
  declare function compileResultToLockfile(result: CompileResult): Lockfile;
706
689
  declare function serializeLockfile(lockfile: Lockfile): string;
707
690
  interface ReadLockfileParams {
@@ -720,4 +703,4 @@ interface CompareLockfileParams {
720
703
  }
721
704
  declare function compareCompileToLockfile({ compiled, existing, }: CompareLockfileParams): LockfileComparison;
722
705
 
723
- export { type CompareLockfileParams, LOCKFILE_RELATIVE_PATH, LOCKFILE_VERSION, type Lockfile, type LockfileComparison, type ReadLockfileParams, type WriteLockfileParams, compareCompileToLockfile, compileResultToLockfile, lockfileSchema, readLockfile, serializeLockfile, writeLockfile };
706
+ export { type CompareLockfileParams, LOCKFILE_RELATIVE_PATH, type Lockfile, type LockfileComparison, type ReadLockfileParams, type WriteLockfileParams, compareCompileToLockfile, compileResultToLockfile, lockfileCodec, readLockfile, serializeLockfile, writeLockfile };
package/dist/lockfile.js CHANGED
@@ -2,21 +2,129 @@
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
4
 
5
- // ../spec/src/edge.ts
5
+ // ../spec/src/codec.ts
6
6
  import { z } from "zod";
7
- var edgeSchema = z.object({
8
- from: z.string().min(1).describe("Key of the source state in the states record"),
9
- requiresKeys: z.record(z.string(), z.string()).optional().describe(
10
- "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)."
11
- ),
12
- to: z.string().min(1).describe("Key of the target state in the states record"),
13
- workflow: z.string().min(1).describe(
14
- "Filename (without .json) of the workflow in .ripplo/workflows/ that executes this edge"
15
- )
16
- }).describe("A directed edge between two states, executed by a workflow");
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
+ }
17
125
 
18
- // ../spec/src/graph.ts
19
- import { z as z4 } from "zod";
126
+ // ../spec/src/codecs.ts
127
+ import { z as z8 } from "zod";
20
128
 
21
129
  // ../spec/src/precondition.ts
22
130
  import { z as z2 } from "zod";
@@ -30,48 +138,25 @@ var preconditionSchema = z2.object({
30
138
  )
31
139
  }).describe("A named precondition declared at the graph level. States reference these by name.");
32
140
 
33
- // ../spec/src/state.ts
34
- import { z as z3 } from "zod";
35
- var stateNodeSchema = z3.object({
36
- preconditions: z3.array(z3.string().min(1)).describe("Ordered list of precondition names to satisfy before entering this state"),
37
- route: z3.string().min(1).describe(
38
- "URL pattern with {{placeholders}} for dynamic segments, e.g. '/projects/{{projectId}}/settings'"
39
- )
40
- }).describe(
41
- "A distinct application state \u2014 a unique combination of location, auth context, and data conditions"
42
- );
43
-
44
- // ../spec/src/graph.ts
45
- var stateGraphSchema = z4.object({
46
- edges: z4.array(edgeSchema).describe("Directed edges between states, each executed by a workflow"),
47
- preconditions: z4.record(z4.string(), preconditionSchema).describe(
48
- "Named preconditions keyed by name (e.g. 'auth:admin', 'data:three-projects'). States reference these by name."
49
- ),
50
- states: z4.record(z4.string(), stateNodeSchema).describe("States keyed by stable ID (kebab-case)"),
51
- version: z4.literal(3).describe("Schema version, always 3")
52
- }).describe(
53
- "Ripplo State Graph v3 \u2014 models application states, edges, and executable preconditions"
54
- );
55
-
56
141
  // ../spec/src/schema.ts
57
- import { z as z9 } from "zod";
142
+ import { z as z7 } from "zod";
58
143
 
59
144
  // ../spec/src/locators.ts
60
- import { z as z5 } from "zod";
61
- var testIdLocator = z5.object({
62
- by: z5.literal("testId"),
63
- value: z5.string().min(1)
145
+ import { z as z3 } from "zod";
146
+ var testIdLocator = z3.object({
147
+ by: z3.literal("testId"),
148
+ value: z3.string().min(1)
64
149
  });
65
- var roleLocator = z5.object({
66
- by: z5.literal("role"),
67
- name: z5.string().optional(),
68
- role: z5.string().min(1)
150
+ var roleLocator = z3.object({
151
+ by: z3.literal("role"),
152
+ name: z3.string().optional(),
153
+ role: z3.string().min(1)
69
154
  });
70
- var locatorSchema = z5.discriminatedUnion("by", [testIdLocator, roleLocator]);
155
+ var locatorSchema = z3.discriminatedUnion("by", [testIdLocator, roleLocator]);
71
156
 
72
157
  // ../spec/src/operators.ts
73
- import { z as z6 } from "zod";
74
- var comparisonOperator = z6.enum([
158
+ import { z as z4 } from "zod";
159
+ var comparisonOperator = z4.enum([
75
160
  "equals",
76
161
  "notEquals",
77
162
  "contains",
@@ -79,7 +164,7 @@ var comparisonOperator = z6.enum([
79
164
  "endsWith",
80
165
  "matches"
81
166
  ]);
82
- var numericOperator = z6.enum([
167
+ var numericOperator = z4.enum([
83
168
  "equals",
84
169
  "notEquals",
85
170
  "greaterThan",
@@ -89,240 +174,241 @@ var numericOperator = z6.enum([
89
174
  ]);
90
175
 
91
176
  // ../spec/src/value-ref.ts
92
- import { z as z7 } from "zod";
93
- var staticValueSchema = z7.object({
94
- type: z7.literal("static"),
95
- value: z7.union([z7.string(), z7.number(), z7.boolean()])
177
+ import { z as z5 } from "zod";
178
+ var staticValueSchema = z5.object({
179
+ type: z5.literal("static"),
180
+ value: z5.union([z5.string(), z5.number(), z5.boolean()])
96
181
  });
97
- var variableRefSchema = z7.object({
98
- name: z7.string().min(1),
99
- type: z7.literal("variable")
182
+ var variableRefSchema = z5.object({
183
+ name: z5.string().min(1),
184
+ type: z5.literal("variable")
100
185
  });
101
- var valueRefSchema = z7.discriminatedUnion("type", [staticValueSchema, variableRefSchema]);
102
- var stringValueRefSchema = z7.discriminatedUnion("type", [
103
- z7.object({ type: z7.literal("static"), value: z7.string() }),
186
+ var valueRefSchema = z5.discriminatedUnion("type", [staticValueSchema, variableRefSchema]);
187
+ var stringValueRefSchema = z5.discriminatedUnion("type", [
188
+ z5.object({ type: z5.literal("static"), value: z5.string() }),
104
189
  variableRefSchema
105
190
  ]);
106
- var numericValueRefSchema = z7.discriminatedUnion("type", [
107
- z7.object({ type: z7.literal("static"), value: z7.number().int().nonnegative() }),
191
+ var numericValueRefSchema = z5.discriminatedUnion("type", [
192
+ z5.object({ type: z5.literal("static"), value: z5.number().int().nonnegative() }),
108
193
  variableRefSchema
109
194
  ]);
110
195
 
111
196
  // ../spec/src/variables.ts
112
- import { z as z8 } from "zod";
113
- var variableDefSchema = z8.discriminatedUnion("type", [
114
- z8.object({
115
- default: z8.string().optional(),
116
- type: z8.literal("string")
197
+ import { z as z6 } from "zod";
198
+ var variableDefSchema = z6.discriminatedUnion("type", [
199
+ z6.object({
200
+ default: z6.string().optional(),
201
+ type: z6.literal("string")
117
202
  }),
118
- z8.object({
119
- default: z8.number().optional(),
120
- type: z8.literal("number")
203
+ z6.object({
204
+ default: z6.number().optional(),
205
+ type: z6.literal("number")
121
206
  }),
122
- z8.object({
123
- default: z8.boolean().optional(),
124
- type: z8.literal("boolean")
207
+ z6.object({
208
+ default: z6.boolean().optional(),
209
+ type: z6.literal("boolean")
125
210
  }),
126
- z8.object({
127
- key: z8.string().min(1),
128
- type: z8.literal("env")
211
+ z6.object({
212
+ key: z6.string().min(1),
213
+ type: z6.literal("env")
129
214
  })
130
215
  ]);
131
216
 
132
217
  // ../spec/src/schema.ts
133
218
  var nodeBase = {
134
- comment: z9.string().optional(),
135
- id: z9.string().min(1),
136
- label: z9.string().optional(),
137
- next: z9.string().optional(),
138
- timeout: z9.number().int().positive().optional()
219
+ comment: z7.string().max(2e3).optional(),
220
+ id: z7.string().min(1).max(200),
221
+ label: z7.string().max(500).optional(),
222
+ next: z7.string().max(200).optional(),
223
+ timeout: z7.number().int().positive().optional()
139
224
  };
140
- var gotoNode = z9.object({
225
+ var MAX_NODES_PER_WORKFLOW = 500;
226
+ var gotoNode = z7.object({
141
227
  ...nodeBase,
142
- type: z9.literal("goto"),
228
+ type: z7.literal("goto"),
143
229
  url: stringValueRefSchema
144
230
  });
145
- var clickNode = z9.object({ ...nodeBase, locator: locatorSchema, type: z9.literal("click") });
146
- var fillNode = z9.object({
231
+ var clickNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("click") });
232
+ var fillNode = z7.object({
147
233
  ...nodeBase,
148
234
  locator: locatorSchema,
149
- type: z9.literal("fill"),
235
+ type: z7.literal("fill"),
150
236
  value: stringValueRefSchema
151
237
  });
152
- var selectNode = z9.object({
238
+ var selectNode = z7.object({
153
239
  ...nodeBase,
154
240
  locator: locatorSchema,
155
- type: z9.literal("select"),
241
+ type: z7.literal("select"),
156
242
  value: stringValueRefSchema
157
243
  });
158
- var hoverNode = z9.object({ ...nodeBase, locator: locatorSchema, type: z9.literal("hover") });
159
- var pressNode = z9.object({
244
+ var hoverNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("hover") });
245
+ var pressNode = z7.object({
160
246
  ...nodeBase,
161
- key: z9.string().min(1),
247
+ key: z7.string().min(1),
162
248
  locator: locatorSchema.optional(),
163
- type: z9.literal("press")
249
+ type: z7.literal("press")
164
250
  });
165
- var checkNode = z9.object({ ...nodeBase, locator: locatorSchema, type: z9.literal("check") });
166
- var uncheckNode = z9.object({ ...nodeBase, locator: locatorSchema, type: z9.literal("uncheck") });
167
- var setViewportNode = z9.object({
251
+ var checkNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("check") });
252
+ var uncheckNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("uncheck") });
253
+ var setViewportNode = z7.object({
168
254
  ...nodeBase,
169
- height: z9.number().int().positive(),
170
- type: z9.literal("setViewport"),
171
- width: z9.number().int().positive()
255
+ height: z7.number().int().positive(),
256
+ type: z7.literal("setViewport"),
257
+ width: z7.number().int().positive()
172
258
  });
173
- var failNode = z9.object({ ...nodeBase, message: z9.string().min(1), type: z9.literal("fail") });
174
- var setVariableNode = z9.object({
259
+ var failNode = z7.object({ ...nodeBase, message: z7.string().min(1), type: z7.literal("fail") });
260
+ var setVariableNode = z7.object({
175
261
  ...nodeBase,
176
- type: z9.literal("setVariable"),
262
+ type: z7.literal("setVariable"),
177
263
  value: valueRefSchema,
178
- variable: z9.string().min(1)
264
+ variable: z7.string().min(1)
179
265
  });
180
- var extractTextNode = z9.object({
266
+ var extractTextNode = z7.object({
181
267
  ...nodeBase,
182
268
  locator: locatorSchema,
183
- type: z9.literal("extractText"),
184
- variable: z9.string().min(1)
269
+ type: z7.literal("extractText"),
270
+ variable: z7.string().min(1)
185
271
  });
186
- var uploadNode = z9.object({
272
+ var uploadNode = z7.object({
187
273
  ...nodeBase,
188
- files: z9.array(z9.string()).min(1),
274
+ files: z7.array(z7.string()).min(1),
189
275
  locator: locatorSchema,
190
- type: z9.literal("upload")
276
+ type: z7.literal("upload")
191
277
  });
192
- var dblclickNode = z9.object({
278
+ var dblclickNode = z7.object({
193
279
  ...nodeBase,
194
280
  locator: locatorSchema,
195
- type: z9.literal("dblclick")
281
+ type: z7.literal("dblclick")
196
282
  });
197
- var dragNode = z9.object({
283
+ var dragNode = z7.object({
198
284
  ...nodeBase,
199
285
  source: locatorSchema,
200
286
  target: locatorSchema,
201
- type: z9.literal("drag")
287
+ type: z7.literal("drag")
202
288
  });
203
- var scrollIntoViewNode = z9.object({
289
+ var scrollIntoViewNode = z7.object({
204
290
  ...nodeBase,
205
291
  locator: locatorSchema,
206
- type: z9.literal("scrollIntoView")
292
+ type: z7.literal("scrollIntoView")
207
293
  });
208
- var typeNode = z9.object({
294
+ var typeNode = z7.object({
209
295
  ...nodeBase,
210
296
  locator: locatorSchema,
211
- type: z9.literal("type"),
297
+ type: z7.literal("type"),
212
298
  value: stringValueRefSchema
213
299
  });
214
- var focusNode = z9.object({
300
+ var focusNode = z7.object({
215
301
  ...nodeBase,
216
302
  locator: locatorSchema,
217
- type: z9.literal("focus")
303
+ type: z7.literal("focus")
218
304
  });
219
- var clearNode = z9.object({ ...nodeBase, locator: locatorSchema, type: z9.literal("clear") });
220
- var rightClickNode = z9.object({
305
+ var clearNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("clear") });
306
+ var rightClickNode = z7.object({
221
307
  ...nodeBase,
222
308
  locator: locatorSchema,
223
- type: z9.literal("rightClick")
309
+ type: z7.literal("rightClick")
224
310
  });
225
- var handleDialogNode = z9.object({
311
+ var handleDialogNode = z7.object({
226
312
  ...nodeBase,
227
- action: z9.enum(["accept", "dismiss"]),
228
- promptText: z9.string().optional(),
229
- type: z9.literal("handleDialog")
313
+ action: z7.enum(["accept", "dismiss"]),
314
+ promptText: z7.string().optional(),
315
+ type: z7.literal("handleDialog")
230
316
  });
231
- var clipboardNode = z9.object({
317
+ var clipboardNode = z7.object({
232
318
  ...nodeBase,
233
- action: z9.enum(["read", "write"]),
234
- type: z9.literal("clipboard"),
319
+ action: z7.enum(["read", "write"]),
320
+ type: z7.literal("clipboard"),
235
321
  value: stringValueRefSchema.optional(),
236
- variable: z9.string().min(1).optional()
322
+ variable: z7.string().min(1).optional()
237
323
  });
238
- var setPermissionNode = z9.object({
324
+ var setPermissionNode = z7.object({
239
325
  ...nodeBase,
240
- permission: z9.string().min(1),
241
- state: z9.enum(["granted", "prompt"]),
242
- type: z9.literal("setPermission")
326
+ permission: z7.string().min(1),
327
+ state: z7.enum(["granted", "prompt"]),
328
+ type: z7.literal("setPermission")
243
329
  });
244
- var assertVisibleNode = z9.object({
330
+ var assertVisibleNode = z7.object({
245
331
  ...nodeBase,
246
332
  locator: locatorSchema,
247
- type: z9.literal("assertVisible")
333
+ type: z7.literal("assertVisible")
248
334
  });
249
- var assertNotVisibleNode = z9.object({
335
+ var assertNotVisibleNode = z7.object({
250
336
  ...nodeBase,
251
337
  locator: locatorSchema,
252
- type: z9.literal("assertNotVisible")
338
+ type: z7.literal("assertNotVisible")
253
339
  });
254
- var assertTextNode = z9.object({
340
+ var assertTextNode = z7.object({
255
341
  ...nodeBase,
256
342
  expected: stringValueRefSchema,
257
343
  locator: locatorSchema,
258
344
  operator: comparisonOperator,
259
- type: z9.literal("assertText")
345
+ type: z7.literal("assertText")
260
346
  });
261
- var assertUrlNode = z9.object({
347
+ var assertUrlNode = z7.object({
262
348
  ...nodeBase,
263
349
  expected: stringValueRefSchema,
264
350
  operator: comparisonOperator,
265
- type: z9.literal("assertUrl")
351
+ type: z7.literal("assertUrl")
266
352
  });
267
- var assertCountNode = z9.object({
353
+ var assertCountNode = z7.object({
268
354
  ...nodeBase,
269
355
  expected: numericValueRefSchema,
270
356
  locator: locatorSchema,
271
357
  operator: numericOperator,
272
- type: z9.literal("assertCount")
358
+ type: z7.literal("assertCount")
273
359
  });
274
- var assertValueNode = z9.object({
360
+ var assertValueNode = z7.object({
275
361
  ...nodeBase,
276
362
  expected: stringValueRefSchema,
277
363
  locator: locatorSchema,
278
364
  operator: comparisonOperator,
279
- type: z9.literal("assertValue")
365
+ type: z7.literal("assertValue")
280
366
  });
281
- var assertAttributeNode = z9.object({
367
+ var assertAttributeNode = z7.object({
282
368
  ...nodeBase,
283
- attribute: z9.string().min(1),
369
+ attribute: z7.string().min(1),
284
370
  expected: stringValueRefSchema,
285
371
  locator: locatorSchema,
286
372
  operator: comparisonOperator,
287
- type: z9.literal("assertAttribute")
373
+ type: z7.literal("assertAttribute")
288
374
  });
289
- var assertEnabledNode = z9.object({
375
+ var assertEnabledNode = z7.object({
290
376
  ...nodeBase,
291
377
  locator: locatorSchema,
292
- type: z9.literal("assertEnabled")
378
+ type: z7.literal("assertEnabled")
293
379
  });
294
- var assertDisabledNode = z9.object({
380
+ var assertDisabledNode = z7.object({
295
381
  ...nodeBase,
296
382
  locator: locatorSchema,
297
- type: z9.literal("assertDisabled")
383
+ type: z7.literal("assertDisabled")
298
384
  });
299
- var assertTitleNode = z9.object({
385
+ var assertTitleNode = z7.object({
300
386
  ...nodeBase,
301
387
  expected: stringValueRefSchema,
302
388
  operator: comparisonOperator,
303
- type: z9.literal("assertTitle")
389
+ type: z7.literal("assertTitle")
304
390
  });
305
- var assertCheckedNode = z9.object({
391
+ var assertCheckedNode = z7.object({
306
392
  ...nodeBase,
307
393
  locator: locatorSchema,
308
- type: z9.literal("assertChecked")
394
+ type: z7.literal("assertChecked")
309
395
  });
310
- var assertNotCheckedNode = z9.object({
396
+ var assertNotCheckedNode = z7.object({
311
397
  ...nodeBase,
312
398
  locator: locatorSchema,
313
- type: z9.literal("assertNotChecked")
399
+ type: z7.literal("assertNotChecked")
314
400
  });
315
- var assertFocusedNode = z9.object({
401
+ var assertFocusedNode = z7.object({
316
402
  ...nodeBase,
317
403
  locator: locatorSchema,
318
- type: z9.literal("assertFocused")
404
+ type: z7.literal("assertFocused")
319
405
  });
320
- var assertNotFocusedNode = z9.object({
406
+ var assertNotFocusedNode = z7.object({
321
407
  ...nodeBase,
322
408
  locator: locatorSchema,
323
- type: z9.literal("assertNotFocused")
409
+ type: z7.literal("assertNotFocused")
324
410
  });
325
- var specNodeSchema = z9.discriminatedUnion("type", [
411
+ var specNodeSchema = z7.discriminatedUnion("type", [
326
412
  gotoNode,
327
413
  clickNode,
328
414
  fillNode,
@@ -361,51 +447,63 @@ var specNodeSchema = z9.discriminatedUnion("type", [
361
447
  assertFocusedNode,
362
448
  assertNotFocusedNode
363
449
  ]);
364
- var workflowSpecSchema = z9.object({
365
- entryNode: z9.string().min(1),
366
- nodes: z9.record(z9.string(), specNodeSchema),
367
- variableNamespaces: z9.record(z9.string(), z9.string()).optional(),
368
- variables: z9.record(z9.string(), variableDefSchema).optional(),
369
- version: z9.literal(2)
450
+ var workflowSpecSchema = z7.object({
451
+ entryNode: z7.string().min(1).max(200),
452
+ nodes: z7.record(z7.string().max(200), specNodeSchema).refine(
453
+ (nodes) => Object.keys(nodes).length <= MAX_NODES_PER_WORKFLOW,
454
+ `Workflow has more than ${String(MAX_NODES_PER_WORKFLOW)} nodes`
455
+ ),
456
+ variableNamespaces: z7.record(z7.string().max(200), z7.string().max(500)).optional(),
457
+ variables: z7.record(z7.string().max(200), variableDefSchema).optional()
370
458
  });
371
459
 
460
+ // ../spec/src/codecs.ts
461
+ var preconditionMapSchema = z8.record(z8.string().max(200), preconditionSchema);
462
+ var looksLikeWorkflowSpec = {
463
+ assumedVersion: 1,
464
+ detect: (raw) => typeof raw === "object" && raw !== null && "entryNode" in raw && "nodes" in raw
465
+ };
466
+ var looksLikePreconditionMap = {
467
+ assumedVersion: 1,
468
+ detect: (raw) => typeof raw === "object" && raw !== null && !Array.isArray(raw) && !("__codec" in raw)
469
+ };
470
+ var workflowSpecCodec = defineCodec("workflow-spec").legacy(looksLikeWorkflowSpec).initial(workflowSpecSchema).build();
471
+ var preconditionMapCodec = defineCodec("precondition-map").legacy(looksLikePreconditionMap).initial(preconditionMapSchema).build();
472
+
372
473
  // src/lockfile.ts
373
- import { z as z10 } from "zod";
374
- var LOCKFILE_VERSION = 1;
474
+ import { z as z9 } from "zod";
375
475
  var LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
376
- var compiledTestSchema = z10.object({
377
- additionalChecks: z10.array(z10.string()),
378
- description: z10.string(),
379
- expectedOutcome: z10.string(),
380
- implemented: z10.boolean(),
381
- name: z10.string(),
382
- slug: z10.string(),
383
- spec: workflowSpecSchema,
384
- warnings: z10.array(z10.string())
385
- });
386
- var lockfileSchema = z10.object({
387
- graph: stateGraphSchema,
388
- lockfileVersion: z10.number().int().positive(),
389
- tests: z10.array(compiledTestSchema)
390
- });
476
+ var MAX_TESTS = 5e3;
477
+ var requiresKeysSchema = z9.record(z9.string().max(200), z9.string().max(200));
478
+ var compiledTestSchema = z9.object({
479
+ expectedOutcome: z9.string().max(2e3),
480
+ name: z9.string().max(200),
481
+ preconditions: z9.array(z9.string().max(200)).max(1e3),
482
+ requiresKeys: requiresKeysSchema,
483
+ slug: z9.string().max(200),
484
+ spec: workflowSpecSchema
485
+ });
486
+ var lockfileBodySchema = z9.object({
487
+ preconditions: z9.record(z9.string().max(200), preconditionSchema),
488
+ tests: z9.array(compiledTestSchema).max(MAX_TESTS)
489
+ });
490
+ var lockfileCodec = defineCodec("ripplo-lockfile").initial(lockfileBodySchema).build();
391
491
  function compileResultToLockfile(result) {
392
492
  return {
393
- graph: result.graph,
394
- lockfileVersion: LOCKFILE_VERSION,
395
- tests: result.tests.map((test) => ({
396
- additionalChecks: [...test.additionalChecks],
397
- description: test.description,
493
+ preconditions: result.preconditions,
494
+ tests: result.tests.filter((test) => test.implemented).map((test) => ({
398
495
  expectedOutcome: test.expectedOutcome,
399
- implemented: test.implemented,
400
496
  name: test.name,
497
+ preconditions: [...test.preconditions],
498
+ requiresKeys: { ...test.requiresKeys },
401
499
  slug: test.slug,
402
- spec: test.spec,
403
- warnings: [...test.warnings]
500
+ spec: test.spec
404
501
  }))
405
502
  };
406
503
  }
407
504
  function serializeLockfile(lockfile) {
408
- return `${JSON.stringify(lockfile, sortedReplacer(lockfile), 2)}
505
+ const envelope = lockfileCodec.encode(lockfile);
506
+ return `${JSON.stringify(envelope, sortedReplacer(envelope), 2)}
409
507
  `;
410
508
  }
411
509
  async function readLockfile({ cwd }) {
@@ -414,8 +512,7 @@ async function readLockfile({ cwd }) {
414
512
  if (raw == null) {
415
513
  return null;
416
514
  }
417
- const parsed = JSON.parse(raw);
418
- return lockfileSchema.parse(parsed);
515
+ return decodeJson(lockfileCodec, raw);
419
516
  }
420
517
  async function writeLockfile({ cwd, result }) {
421
518
  const lockfile = compileResultToLockfile(result);
@@ -460,10 +557,9 @@ function isPlainObject(value) {
460
557
  }
461
558
  export {
462
559
  LOCKFILE_RELATIVE_PATH,
463
- LOCKFILE_VERSION,
464
560
  compareCompileToLockfile,
465
561
  compileResultToLockfile,
466
- lockfileSchema,
562
+ lockfileCodec,
467
563
  readLockfile,
468
564
  serializeLockfile,
469
565
  writeLockfile
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.7",
4
+ "version": "0.0.9",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "standardwebhooks": "^1.0.0",
63
- "zod": "catalog:"
63
+ "zod": "^4.3.6"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/express": "^5.0.2",