@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.
- package/dist/{chunk-GTHRSFXF.js → chunk-LEIKZ6BE.js} +15 -41
- package/dist/compiler.d.ts +4 -2
- package/dist/compiler.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lockfile.d.ts +12 -29
- package/dist/lockfile.js +294 -198
- package/package.json +2 -2
|
@@ -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
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
package/dist/compiler.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
package/dist/index.js
CHANGED
package/dist/lockfile.d.ts
CHANGED
|
@@ -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
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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,
|
|
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/
|
|
5
|
+
// ../spec/src/codec.ts
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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/
|
|
19
|
-
import { z as
|
|
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
|
|
142
|
+
import { z as z7 } from "zod";
|
|
58
143
|
|
|
59
144
|
// ../spec/src/locators.ts
|
|
60
|
-
import { z as
|
|
61
|
-
var testIdLocator =
|
|
62
|
-
by:
|
|
63
|
-
value:
|
|
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 =
|
|
66
|
-
by:
|
|
67
|
-
name:
|
|
68
|
-
role:
|
|
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 =
|
|
155
|
+
var locatorSchema = z3.discriminatedUnion("by", [testIdLocator, roleLocator]);
|
|
71
156
|
|
|
72
157
|
// ../spec/src/operators.ts
|
|
73
|
-
import { z as
|
|
74
|
-
var comparisonOperator =
|
|
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 =
|
|
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
|
|
93
|
-
var staticValueSchema =
|
|
94
|
-
type:
|
|
95
|
-
value:
|
|
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 =
|
|
98
|
-
name:
|
|
99
|
-
type:
|
|
182
|
+
var variableRefSchema = z5.object({
|
|
183
|
+
name: z5.string().min(1),
|
|
184
|
+
type: z5.literal("variable")
|
|
100
185
|
});
|
|
101
|
-
var valueRefSchema =
|
|
102
|
-
var stringValueRefSchema =
|
|
103
|
-
|
|
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 =
|
|
107
|
-
|
|
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
|
|
113
|
-
var variableDefSchema =
|
|
114
|
-
|
|
115
|
-
default:
|
|
116
|
-
type:
|
|
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
|
-
|
|
119
|
-
default:
|
|
120
|
-
type:
|
|
203
|
+
z6.object({
|
|
204
|
+
default: z6.number().optional(),
|
|
205
|
+
type: z6.literal("number")
|
|
121
206
|
}),
|
|
122
|
-
|
|
123
|
-
default:
|
|
124
|
-
type:
|
|
207
|
+
z6.object({
|
|
208
|
+
default: z6.boolean().optional(),
|
|
209
|
+
type: z6.literal("boolean")
|
|
125
210
|
}),
|
|
126
|
-
|
|
127
|
-
key:
|
|
128
|
-
type:
|
|
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:
|
|
135
|
-
id:
|
|
136
|
-
label:
|
|
137
|
-
next:
|
|
138
|
-
timeout:
|
|
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
|
|
225
|
+
var MAX_NODES_PER_WORKFLOW = 500;
|
|
226
|
+
var gotoNode = z7.object({
|
|
141
227
|
...nodeBase,
|
|
142
|
-
type:
|
|
228
|
+
type: z7.literal("goto"),
|
|
143
229
|
url: stringValueRefSchema
|
|
144
230
|
});
|
|
145
|
-
var clickNode =
|
|
146
|
-
var fillNode =
|
|
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:
|
|
235
|
+
type: z7.literal("fill"),
|
|
150
236
|
value: stringValueRefSchema
|
|
151
237
|
});
|
|
152
|
-
var selectNode =
|
|
238
|
+
var selectNode = z7.object({
|
|
153
239
|
...nodeBase,
|
|
154
240
|
locator: locatorSchema,
|
|
155
|
-
type:
|
|
241
|
+
type: z7.literal("select"),
|
|
156
242
|
value: stringValueRefSchema
|
|
157
243
|
});
|
|
158
|
-
var hoverNode =
|
|
159
|
-
var pressNode =
|
|
244
|
+
var hoverNode = z7.object({ ...nodeBase, locator: locatorSchema, type: z7.literal("hover") });
|
|
245
|
+
var pressNode = z7.object({
|
|
160
246
|
...nodeBase,
|
|
161
|
-
key:
|
|
247
|
+
key: z7.string().min(1),
|
|
162
248
|
locator: locatorSchema.optional(),
|
|
163
|
-
type:
|
|
249
|
+
type: z7.literal("press")
|
|
164
250
|
});
|
|
165
|
-
var checkNode =
|
|
166
|
-
var uncheckNode =
|
|
167
|
-
var setViewportNode =
|
|
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:
|
|
170
|
-
type:
|
|
171
|
-
width:
|
|
255
|
+
height: z7.number().int().positive(),
|
|
256
|
+
type: z7.literal("setViewport"),
|
|
257
|
+
width: z7.number().int().positive()
|
|
172
258
|
});
|
|
173
|
-
var failNode =
|
|
174
|
-
var setVariableNode =
|
|
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:
|
|
262
|
+
type: z7.literal("setVariable"),
|
|
177
263
|
value: valueRefSchema,
|
|
178
|
-
variable:
|
|
264
|
+
variable: z7.string().min(1)
|
|
179
265
|
});
|
|
180
|
-
var extractTextNode =
|
|
266
|
+
var extractTextNode = z7.object({
|
|
181
267
|
...nodeBase,
|
|
182
268
|
locator: locatorSchema,
|
|
183
|
-
type:
|
|
184
|
-
variable:
|
|
269
|
+
type: z7.literal("extractText"),
|
|
270
|
+
variable: z7.string().min(1)
|
|
185
271
|
});
|
|
186
|
-
var uploadNode =
|
|
272
|
+
var uploadNode = z7.object({
|
|
187
273
|
...nodeBase,
|
|
188
|
-
files:
|
|
274
|
+
files: z7.array(z7.string()).min(1),
|
|
189
275
|
locator: locatorSchema,
|
|
190
|
-
type:
|
|
276
|
+
type: z7.literal("upload")
|
|
191
277
|
});
|
|
192
|
-
var dblclickNode =
|
|
278
|
+
var dblclickNode = z7.object({
|
|
193
279
|
...nodeBase,
|
|
194
280
|
locator: locatorSchema,
|
|
195
|
-
type:
|
|
281
|
+
type: z7.literal("dblclick")
|
|
196
282
|
});
|
|
197
|
-
var dragNode =
|
|
283
|
+
var dragNode = z7.object({
|
|
198
284
|
...nodeBase,
|
|
199
285
|
source: locatorSchema,
|
|
200
286
|
target: locatorSchema,
|
|
201
|
-
type:
|
|
287
|
+
type: z7.literal("drag")
|
|
202
288
|
});
|
|
203
|
-
var scrollIntoViewNode =
|
|
289
|
+
var scrollIntoViewNode = z7.object({
|
|
204
290
|
...nodeBase,
|
|
205
291
|
locator: locatorSchema,
|
|
206
|
-
type:
|
|
292
|
+
type: z7.literal("scrollIntoView")
|
|
207
293
|
});
|
|
208
|
-
var typeNode =
|
|
294
|
+
var typeNode = z7.object({
|
|
209
295
|
...nodeBase,
|
|
210
296
|
locator: locatorSchema,
|
|
211
|
-
type:
|
|
297
|
+
type: z7.literal("type"),
|
|
212
298
|
value: stringValueRefSchema
|
|
213
299
|
});
|
|
214
|
-
var focusNode =
|
|
300
|
+
var focusNode = z7.object({
|
|
215
301
|
...nodeBase,
|
|
216
302
|
locator: locatorSchema,
|
|
217
|
-
type:
|
|
303
|
+
type: z7.literal("focus")
|
|
218
304
|
});
|
|
219
|
-
var clearNode =
|
|
220
|
-
var rightClickNode =
|
|
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:
|
|
309
|
+
type: z7.literal("rightClick")
|
|
224
310
|
});
|
|
225
|
-
var handleDialogNode =
|
|
311
|
+
var handleDialogNode = z7.object({
|
|
226
312
|
...nodeBase,
|
|
227
|
-
action:
|
|
228
|
-
promptText:
|
|
229
|
-
type:
|
|
313
|
+
action: z7.enum(["accept", "dismiss"]),
|
|
314
|
+
promptText: z7.string().optional(),
|
|
315
|
+
type: z7.literal("handleDialog")
|
|
230
316
|
});
|
|
231
|
-
var clipboardNode =
|
|
317
|
+
var clipboardNode = z7.object({
|
|
232
318
|
...nodeBase,
|
|
233
|
-
action:
|
|
234
|
-
type:
|
|
319
|
+
action: z7.enum(["read", "write"]),
|
|
320
|
+
type: z7.literal("clipboard"),
|
|
235
321
|
value: stringValueRefSchema.optional(),
|
|
236
|
-
variable:
|
|
322
|
+
variable: z7.string().min(1).optional()
|
|
237
323
|
});
|
|
238
|
-
var setPermissionNode =
|
|
324
|
+
var setPermissionNode = z7.object({
|
|
239
325
|
...nodeBase,
|
|
240
|
-
permission:
|
|
241
|
-
state:
|
|
242
|
-
type:
|
|
326
|
+
permission: z7.string().min(1),
|
|
327
|
+
state: z7.enum(["granted", "prompt"]),
|
|
328
|
+
type: z7.literal("setPermission")
|
|
243
329
|
});
|
|
244
|
-
var assertVisibleNode =
|
|
330
|
+
var assertVisibleNode = z7.object({
|
|
245
331
|
...nodeBase,
|
|
246
332
|
locator: locatorSchema,
|
|
247
|
-
type:
|
|
333
|
+
type: z7.literal("assertVisible")
|
|
248
334
|
});
|
|
249
|
-
var assertNotVisibleNode =
|
|
335
|
+
var assertNotVisibleNode = z7.object({
|
|
250
336
|
...nodeBase,
|
|
251
337
|
locator: locatorSchema,
|
|
252
|
-
type:
|
|
338
|
+
type: z7.literal("assertNotVisible")
|
|
253
339
|
});
|
|
254
|
-
var assertTextNode =
|
|
340
|
+
var assertTextNode = z7.object({
|
|
255
341
|
...nodeBase,
|
|
256
342
|
expected: stringValueRefSchema,
|
|
257
343
|
locator: locatorSchema,
|
|
258
344
|
operator: comparisonOperator,
|
|
259
|
-
type:
|
|
345
|
+
type: z7.literal("assertText")
|
|
260
346
|
});
|
|
261
|
-
var assertUrlNode =
|
|
347
|
+
var assertUrlNode = z7.object({
|
|
262
348
|
...nodeBase,
|
|
263
349
|
expected: stringValueRefSchema,
|
|
264
350
|
operator: comparisonOperator,
|
|
265
|
-
type:
|
|
351
|
+
type: z7.literal("assertUrl")
|
|
266
352
|
});
|
|
267
|
-
var assertCountNode =
|
|
353
|
+
var assertCountNode = z7.object({
|
|
268
354
|
...nodeBase,
|
|
269
355
|
expected: numericValueRefSchema,
|
|
270
356
|
locator: locatorSchema,
|
|
271
357
|
operator: numericOperator,
|
|
272
|
-
type:
|
|
358
|
+
type: z7.literal("assertCount")
|
|
273
359
|
});
|
|
274
|
-
var assertValueNode =
|
|
360
|
+
var assertValueNode = z7.object({
|
|
275
361
|
...nodeBase,
|
|
276
362
|
expected: stringValueRefSchema,
|
|
277
363
|
locator: locatorSchema,
|
|
278
364
|
operator: comparisonOperator,
|
|
279
|
-
type:
|
|
365
|
+
type: z7.literal("assertValue")
|
|
280
366
|
});
|
|
281
|
-
var assertAttributeNode =
|
|
367
|
+
var assertAttributeNode = z7.object({
|
|
282
368
|
...nodeBase,
|
|
283
|
-
attribute:
|
|
369
|
+
attribute: z7.string().min(1),
|
|
284
370
|
expected: stringValueRefSchema,
|
|
285
371
|
locator: locatorSchema,
|
|
286
372
|
operator: comparisonOperator,
|
|
287
|
-
type:
|
|
373
|
+
type: z7.literal("assertAttribute")
|
|
288
374
|
});
|
|
289
|
-
var assertEnabledNode =
|
|
375
|
+
var assertEnabledNode = z7.object({
|
|
290
376
|
...nodeBase,
|
|
291
377
|
locator: locatorSchema,
|
|
292
|
-
type:
|
|
378
|
+
type: z7.literal("assertEnabled")
|
|
293
379
|
});
|
|
294
|
-
var assertDisabledNode =
|
|
380
|
+
var assertDisabledNode = z7.object({
|
|
295
381
|
...nodeBase,
|
|
296
382
|
locator: locatorSchema,
|
|
297
|
-
type:
|
|
383
|
+
type: z7.literal("assertDisabled")
|
|
298
384
|
});
|
|
299
|
-
var assertTitleNode =
|
|
385
|
+
var assertTitleNode = z7.object({
|
|
300
386
|
...nodeBase,
|
|
301
387
|
expected: stringValueRefSchema,
|
|
302
388
|
operator: comparisonOperator,
|
|
303
|
-
type:
|
|
389
|
+
type: z7.literal("assertTitle")
|
|
304
390
|
});
|
|
305
|
-
var assertCheckedNode =
|
|
391
|
+
var assertCheckedNode = z7.object({
|
|
306
392
|
...nodeBase,
|
|
307
393
|
locator: locatorSchema,
|
|
308
|
-
type:
|
|
394
|
+
type: z7.literal("assertChecked")
|
|
309
395
|
});
|
|
310
|
-
var assertNotCheckedNode =
|
|
396
|
+
var assertNotCheckedNode = z7.object({
|
|
311
397
|
...nodeBase,
|
|
312
398
|
locator: locatorSchema,
|
|
313
|
-
type:
|
|
399
|
+
type: z7.literal("assertNotChecked")
|
|
314
400
|
});
|
|
315
|
-
var assertFocusedNode =
|
|
401
|
+
var assertFocusedNode = z7.object({
|
|
316
402
|
...nodeBase,
|
|
317
403
|
locator: locatorSchema,
|
|
318
|
-
type:
|
|
404
|
+
type: z7.literal("assertFocused")
|
|
319
405
|
});
|
|
320
|
-
var assertNotFocusedNode =
|
|
406
|
+
var assertNotFocusedNode = z7.object({
|
|
321
407
|
...nodeBase,
|
|
322
408
|
locator: locatorSchema,
|
|
323
|
-
type:
|
|
409
|
+
type: z7.literal("assertNotFocused")
|
|
324
410
|
});
|
|
325
|
-
var specNodeSchema =
|
|
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 =
|
|
365
|
-
entryNode:
|
|
366
|
-
nodes:
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
374
|
-
var LOCKFILE_VERSION = 1;
|
|
474
|
+
import { z as z9 } from "zod";
|
|
375
475
|
var LOCKFILE_RELATIVE_PATH = ".ripplo/ripplo.lock";
|
|
376
|
-
var
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
expectedOutcome:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
});
|
|
386
|
-
var
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
63
|
+
"zod": "^4.3.6"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/express": "^5.0.2",
|