guardrail-ship 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-implementation.d.ts +1 -0
- package/dist/mock-implementation.d.ts.map +1 -0
- package/dist/mock-implementation.js +2 -0
- package/dist/mock-implementation.js.map +1 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts +5 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts.map +1 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.js +92 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.js.map +1 -0
- package/dist/mockproof/import-graph-scanner.d.ts +93 -0
- package/dist/mockproof/import-graph-scanner.d.ts.map +1 -0
- package/dist/mockproof/import-graph-scanner.js +411 -0
- package/dist/mockproof/import-graph-scanner.js.map +1 -0
- package/dist/mockproof/index.d.ts +10 -0
- package/dist/mockproof/index.d.ts.map +1 -0
- package/dist/mockproof/index.js +10 -0
- package/dist/mockproof/index.js.map +1 -0
- package/dist/reality-mode/auth-enforcer.d.ts +13 -0
- package/dist/reality-mode/auth-enforcer.d.ts.map +1 -0
- package/dist/reality-mode/auth-enforcer.js +90 -0
- package/dist/reality-mode/auth-enforcer.js.map +1 -0
- package/dist/reality-mode/explorer/critical-flows.d.ts +71 -0
- package/dist/reality-mode/explorer/critical-flows.d.ts.map +1 -0
- package/dist/reality-mode/explorer/critical-flows.js +463 -0
- package/dist/reality-mode/explorer/critical-flows.js.map +1 -0
- package/dist/reality-mode/explorer/flow-parser.d.ts +52 -0
- package/dist/reality-mode/explorer/flow-parser.d.ts.map +1 -0
- package/dist/reality-mode/explorer/flow-parser.js +250 -0
- package/dist/reality-mode/explorer/flow-parser.js.map +1 -0
- package/dist/reality-mode/explorer/index.d.ts +11 -0
- package/dist/reality-mode/explorer/index.d.ts.map +1 -0
- package/dist/reality-mode/explorer/index.js +11 -0
- package/dist/reality-mode/explorer/index.js.map +1 -0
- package/dist/reality-mode/explorer/runtime-explorer.d.ts +35 -0
- package/dist/reality-mode/explorer/runtime-explorer.d.ts.map +1 -0
- package/dist/reality-mode/explorer/runtime-explorer.js +688 -0
- package/dist/reality-mode/explorer/runtime-explorer.js.map +1 -0
- package/dist/reality-mode/explorer/surface-discovery.d.ts +60 -0
- package/dist/reality-mode/explorer/surface-discovery.d.ts.map +1 -0
- package/dist/reality-mode/explorer/surface-discovery.js +357 -0
- package/dist/reality-mode/explorer/surface-discovery.js.map +1 -0
- package/dist/reality-mode/explorer/types.d.ts +275 -0
- package/dist/reality-mode/explorer/types.d.ts.map +1 -0
- package/dist/reality-mode/explorer/types.js +8 -0
- package/dist/reality-mode/explorer/types.js.map +1 -0
- package/dist/reality-mode/fake-success-detector.d.ts +10 -0
- package/dist/reality-mode/fake-success-detector.d.ts.map +1 -0
- package/dist/reality-mode/fake-success-detector.js +76 -0
- package/dist/reality-mode/fake-success-detector.js.map +1 -0
- package/dist/reality-mode/index.d.ts +14 -0
- package/dist/reality-mode/index.d.ts.map +1 -0
- package/dist/reality-mode/index.js +14 -0
- package/dist/reality-mode/index.js.map +1 -0
- package/dist/reality-mode/reality-scanner.d.ts +48 -0
- package/dist/reality-mode/reality-scanner.d.ts.map +1 -0
- package/dist/reality-mode/reality-scanner.js +516 -0
- package/dist/reality-mode/reality-scanner.js.map +1 -0
- package/dist/reality-mode/report-generator.d.ts +11 -0
- package/dist/reality-mode/report-generator.d.ts.map +1 -0
- package/dist/reality-mode/report-generator.js +233 -0
- package/dist/reality-mode/report-generator.js.map +1 -0
- package/dist/reality-mode/traffic-classifier.d.ts +14 -0
- package/dist/reality-mode/traffic-classifier.d.ts.map +1 -0
- package/dist/reality-mode/traffic-classifier.js +131 -0
- package/dist/reality-mode/traffic-classifier.js.map +1 -0
- package/dist/reality-mode/types.d.ts +90 -0
- package/dist/reality-mode/types.d.ts.map +1 -0
- package/dist/reality-mode/types.js +2 -0
- package/dist/reality-mode/types.js.map +1 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts +5 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts.map +1 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.js +146 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.js.map +1 -0
- package/dist/ship-badge/index.d.ts +9 -0
- package/dist/ship-badge/index.d.ts.map +1 -0
- package/dist/ship-badge/index.js +9 -0
- package/dist/ship-badge/index.js.map +1 -0
- package/dist/ship-badge/ship-badge-generator.d.ts +136 -0
- package/dist/ship-badge/ship-badge-generator.d.ts.map +1 -0
- package/dist/ship-badge/ship-badge-generator.js +681 -0
- package/dist/ship-badge/ship-badge-generator.js.map +1 -0
- package/package.json +20 -0
- package/src/index.ts +7 -0
- package/src/mock-implementation.ts +0 -0
- package/src/mockproof/__tests__/import-graph-scanner.test.ts +115 -0
- package/src/mockproof/import-graph-scanner.d.ts +93 -0
- package/src/mockproof/import-graph-scanner.d.ts.map +1 -0
- package/src/mockproof/import-graph-scanner.js +482 -0
- package/src/mockproof/import-graph-scanner.ts +540 -0
- package/src/mockproof/index.ts +18 -0
- package/src/reality-mode/auth-enforcer.ts +97 -0
- package/src/reality-mode/explorer/critical-flows.ts +504 -0
- package/src/reality-mode/explorer/flow-parser.ts +293 -0
- package/src/reality-mode/explorer/index.ts +22 -0
- package/src/reality-mode/explorer/runtime-explorer.ts +715 -0
- package/src/reality-mode/explorer/surface-discovery.ts +498 -0
- package/src/reality-mode/explorer/templates/example-flows/auth-flow.yaml +41 -0
- package/src/reality-mode/explorer/templates/example-flows/checkout-flow.yaml +66 -0
- package/src/reality-mode/explorer/templates/example-flows/contact-form.yaml +43 -0
- package/src/reality-mode/explorer/templates/github-action.yml +132 -0
- package/src/reality-mode/explorer/types.ts +356 -0
- package/src/reality-mode/fake-success-detector.ts +89 -0
- package/src/reality-mode/index.ts +19 -0
- package/src/reality-mode/reality-scanner.d.ts +123 -0
- package/src/reality-mode/reality-scanner.d.ts.map +1 -0
- package/src/reality-mode/reality-scanner.js +526 -0
- package/src/reality-mode/reality-scanner.ts +576 -0
- package/src/reality-mode/report-generator.ts +253 -0
- package/src/reality-mode/traffic-classifier.ts +169 -0
- package/src/reality-mode/types.ts +95 -0
- package/src/ship-badge/__tests__/ship-badge-generator.test.ts +162 -0
- package/src/ship-badge/index.ts +16 -0
- package/src/ship-badge/ship-badge-generator.d.ts +136 -0
- package/src/ship-badge/ship-badge-generator.d.ts.map +1 -0
- package/src/ship-badge/ship-badge-generator.js +779 -0
- package/src/ship-badge/ship-badge-generator.ts +873 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow Pack YAML Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses custom flow definitions from YAML files so users can
|
|
5
|
+
* define their own critical paths to test.
|
|
6
|
+
*
|
|
7
|
+
* Example YAML:
|
|
8
|
+
* ```yaml
|
|
9
|
+
* id: custom-checkout
|
|
10
|
+
* name: Custom Checkout Flow
|
|
11
|
+
* description: Tests our specific checkout process
|
|
12
|
+
* steps:
|
|
13
|
+
* - action: navigate
|
|
14
|
+
* target: /products
|
|
15
|
+
* - action: click
|
|
16
|
+
* target: button:has-text("Add to Cart")
|
|
17
|
+
* - action: navigate
|
|
18
|
+
* target: /checkout
|
|
19
|
+
* - action: fill
|
|
20
|
+
* target: "#email"
|
|
21
|
+
* value: "{{email}}"
|
|
22
|
+
* assertions:
|
|
23
|
+
* - type: url-contains
|
|
24
|
+
* value: /confirmation
|
|
25
|
+
* critical: true
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type { CriticalFlow, FlowStep, FlowAssertion } from "./types";
|
|
30
|
+
|
|
31
|
+
interface RawFlowYAML {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
steps: Array<{
|
|
37
|
+
action: string;
|
|
38
|
+
target?: string;
|
|
39
|
+
value?: string;
|
|
40
|
+
timeout?: number;
|
|
41
|
+
}>;
|
|
42
|
+
assertions?: Array<{
|
|
43
|
+
type: string;
|
|
44
|
+
value: string;
|
|
45
|
+
critical?: boolean;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse a YAML flow definition into a CriticalFlow object
|
|
51
|
+
*/
|
|
52
|
+
export function parseFlowYAML(yaml: string): CriticalFlow {
|
|
53
|
+
// Simple YAML parser for flow definitions
|
|
54
|
+
// In production, would use a proper YAML library
|
|
55
|
+
const lines = yaml.split("\n");
|
|
56
|
+
const flow: Partial<RawFlowYAML> = {
|
|
57
|
+
steps: [],
|
|
58
|
+
assertions: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
let currentSection: "root" | "steps" | "assertions" = "root";
|
|
62
|
+
let currentItem: Record<string, any> | null = null;
|
|
63
|
+
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
67
|
+
|
|
68
|
+
// Detect section changes
|
|
69
|
+
if (trimmed === "steps:") {
|
|
70
|
+
currentSection = "steps";
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (trimmed === "assertions:") {
|
|
74
|
+
currentSection = "assertions";
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle list items
|
|
79
|
+
if (trimmed.startsWith("- ")) {
|
|
80
|
+
if (currentItem && currentSection === "steps") {
|
|
81
|
+
flow.steps!.push(currentItem as any);
|
|
82
|
+
} else if (currentItem && currentSection === "assertions") {
|
|
83
|
+
flow.assertions!.push(currentItem as any);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
currentItem = {};
|
|
87
|
+
const content = trimmed.slice(2);
|
|
88
|
+
if (content.includes(":")) {
|
|
89
|
+
const [keyPart, ...valueParts] = content.split(":");
|
|
90
|
+
const key = keyPart?.trim();
|
|
91
|
+
const value = valueParts
|
|
92
|
+
.join(":")
|
|
93
|
+
.trim()
|
|
94
|
+
.replace(/^["']|["']$/g, "");
|
|
95
|
+
if (key) currentItem[key] = parseValue(value);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle properties
|
|
101
|
+
if (trimmed.includes(":")) {
|
|
102
|
+
const [keyPart, ...valueParts] = trimmed.split(":");
|
|
103
|
+
const key = keyPart?.trim();
|
|
104
|
+
const value = valueParts
|
|
105
|
+
.join(":")
|
|
106
|
+
.trim()
|
|
107
|
+
.replace(/^["']|["']$/g, "");
|
|
108
|
+
|
|
109
|
+
if (key && currentSection === "root") {
|
|
110
|
+
(flow as any)[key] = parseValue(value);
|
|
111
|
+
} else if (key && currentItem) {
|
|
112
|
+
currentItem[key] = parseValue(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Don't forget the last item
|
|
118
|
+
if (currentItem) {
|
|
119
|
+
if (currentSection === "steps") {
|
|
120
|
+
flow.steps!.push(currentItem as any);
|
|
121
|
+
} else if (currentSection === "assertions") {
|
|
122
|
+
flow.assertions!.push(currentItem as any);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return convertToFlow(flow as RawFlowYAML);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse a value, handling booleans and numbers
|
|
131
|
+
*/
|
|
132
|
+
function parseValue(value: string): string | number | boolean {
|
|
133
|
+
if (value === "true") return true;
|
|
134
|
+
if (value === "false") return false;
|
|
135
|
+
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Convert raw YAML to CriticalFlow
|
|
141
|
+
*/
|
|
142
|
+
function convertToFlow(raw: RawFlowYAML): CriticalFlow {
|
|
143
|
+
return {
|
|
144
|
+
id: raw.id || "custom-flow",
|
|
145
|
+
name: raw.name || "Custom Flow",
|
|
146
|
+
description: raw.description || "",
|
|
147
|
+
required: raw.required ?? false,
|
|
148
|
+
steps: raw.steps.map(
|
|
149
|
+
(s): FlowStep => ({
|
|
150
|
+
action: s.action as FlowStep["action"],
|
|
151
|
+
target: s.target,
|
|
152
|
+
value: s.value,
|
|
153
|
+
timeout: s.timeout,
|
|
154
|
+
}),
|
|
155
|
+
),
|
|
156
|
+
assertions: (raw.assertions || []).map(
|
|
157
|
+
(a): FlowAssertion => ({
|
|
158
|
+
type: a.type as FlowAssertion["type"],
|
|
159
|
+
value: a.value,
|
|
160
|
+
critical: a.critical ?? true,
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Load flow from file path
|
|
168
|
+
*/
|
|
169
|
+
export async function loadFlowFromFile(
|
|
170
|
+
filePath: string,
|
|
171
|
+
): Promise<CriticalFlow> {
|
|
172
|
+
const fs = await import("fs").then((m) => m.promises);
|
|
173
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
174
|
+
return parseFlowYAML(content);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Load all flows from a directory
|
|
179
|
+
*/
|
|
180
|
+
export async function loadFlowsFromDirectory(
|
|
181
|
+
dirPath: string,
|
|
182
|
+
): Promise<CriticalFlow[]> {
|
|
183
|
+
const fs = await import("fs").then((m) => m.promises);
|
|
184
|
+
const path = await import("path");
|
|
185
|
+
|
|
186
|
+
const flows: CriticalFlow[] = [];
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const files = await fs.readdir(dirPath);
|
|
190
|
+
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
if (file.endsWith(".yaml") || file.endsWith(".yml")) {
|
|
193
|
+
const filePath = path.join(dirPath, file);
|
|
194
|
+
const flow = await loadFlowFromFile(filePath);
|
|
195
|
+
flows.push(flow);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Directory doesn't exist or can't be read
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return flows;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validate a flow definition
|
|
207
|
+
*/
|
|
208
|
+
export function validateFlow(flow: CriticalFlow): {
|
|
209
|
+
valid: boolean;
|
|
210
|
+
errors: string[];
|
|
211
|
+
} {
|
|
212
|
+
const errors: string[] = [];
|
|
213
|
+
|
|
214
|
+
if (!flow.id) errors.push("Flow must have an id");
|
|
215
|
+
if (!flow.name) errors.push("Flow must have a name");
|
|
216
|
+
if (!flow.steps || flow.steps.length === 0)
|
|
217
|
+
errors.push("Flow must have at least one step");
|
|
218
|
+
|
|
219
|
+
const validActions = ["navigate", "click", "fill", "wait", "assert"];
|
|
220
|
+
for (const step of flow.steps || []) {
|
|
221
|
+
if (!validActions.includes(step.action)) {
|
|
222
|
+
errors.push(`Invalid action: ${step.action}`);
|
|
223
|
+
}
|
|
224
|
+
if (step.action !== "wait" && !step.target) {
|
|
225
|
+
errors.push(`Step "${step.action}" requires a target`);
|
|
226
|
+
}
|
|
227
|
+
if (step.action === "fill" && !step.value) {
|
|
228
|
+
errors.push("Fill action requires a value");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const validAssertionTypes = [
|
|
233
|
+
"url-contains",
|
|
234
|
+
"element-visible",
|
|
235
|
+
"element-hidden",
|
|
236
|
+
"cookie-exists",
|
|
237
|
+
"localstorage-has",
|
|
238
|
+
"network-success",
|
|
239
|
+
"no-errors",
|
|
240
|
+
];
|
|
241
|
+
for (const assertion of flow.assertions || []) {
|
|
242
|
+
if (!validAssertionTypes.includes(assertion.type)) {
|
|
243
|
+
errors.push(`Invalid assertion type: ${assertion.type}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { valid: errors.length === 0, errors };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Generate example flow YAML
|
|
252
|
+
*/
|
|
253
|
+
export function generateExampleFlowYAML(): string {
|
|
254
|
+
return `# Example Critical Flow Definition
|
|
255
|
+
# Save this as .guardrail/flows/my-flow.yaml
|
|
256
|
+
|
|
257
|
+
id: my-custom-flow
|
|
258
|
+
name: My Custom Flow
|
|
259
|
+
description: Tests my specific user journey
|
|
260
|
+
required: false
|
|
261
|
+
|
|
262
|
+
steps:
|
|
263
|
+
- action: navigate
|
|
264
|
+
target: /my-page
|
|
265
|
+
|
|
266
|
+
- action: fill
|
|
267
|
+
target: input[name="email"]
|
|
268
|
+
value: "{{email}}"
|
|
269
|
+
|
|
270
|
+
- action: fill
|
|
271
|
+
target: input[name="password"]
|
|
272
|
+
value: "{{password}}"
|
|
273
|
+
|
|
274
|
+
- action: click
|
|
275
|
+
target: button[type="submit"]
|
|
276
|
+
|
|
277
|
+
- action: wait
|
|
278
|
+
timeout: 3000
|
|
279
|
+
|
|
280
|
+
assertions:
|
|
281
|
+
- type: url-contains
|
|
282
|
+
value: /success
|
|
283
|
+
critical: true
|
|
284
|
+
|
|
285
|
+
- type: element-visible
|
|
286
|
+
value: .welcome-message
|
|
287
|
+
critical: false
|
|
288
|
+
|
|
289
|
+
- type: no-errors
|
|
290
|
+
value: ""
|
|
291
|
+
critical: true
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reality Explorer - Main Export
|
|
3
|
+
*
|
|
4
|
+
* The "legit" Reality Mode that actually tests everything in your app.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./types";
|
|
8
|
+
export { SurfaceDiscovery } from "./surface-discovery";
|
|
9
|
+
export { RuntimeExplorer, createDefaultConfig } from "./runtime-explorer";
|
|
10
|
+
export {
|
|
11
|
+
FLOW_PACKS,
|
|
12
|
+
getAllFlows,
|
|
13
|
+
getFlowPack,
|
|
14
|
+
generateFlowTest,
|
|
15
|
+
} from "./critical-flows";
|
|
16
|
+
export {
|
|
17
|
+
parseFlowYAML,
|
|
18
|
+
loadFlowFromFile,
|
|
19
|
+
loadFlowsFromDirectory,
|
|
20
|
+
validateFlow,
|
|
21
|
+
generateExampleFlowYAML,
|
|
22
|
+
} from "./flow-parser";
|