getseatbelt 0.0.1
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 +122 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +4735 -0
- package/dist/index.d.ts +1592 -0
- package/dist/index.js +4615 -0
- package/package.json +72 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1592 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Page } from 'playwright';
|
|
3
|
+
|
|
4
|
+
interface InitOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
/** `--yes`: accept the run + reset offers without prompting. */
|
|
7
|
+
yes?: boolean;
|
|
8
|
+
/** `--run` / `--no-run`: force running (or not) after setup, skipping the prompt. */
|
|
9
|
+
run?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `seatbelt init` (alias `seatbelt on`): set up Seatbelt in this repo.
|
|
13
|
+
* Never overwrites an existing config or state file.
|
|
14
|
+
*/
|
|
15
|
+
declare function initCommand(opts?: InitOptions): Promise<number>;
|
|
16
|
+
|
|
17
|
+
interface RunOptions {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
/** `undefined`/"all" runs all flows; a flow id runs just that one (and its prerequisites). */
|
|
20
|
+
target?: string;
|
|
21
|
+
/** When false, do not auto-open the report (commander's --no-open sets open:false). */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
/** Override the visible-browser default (--headed / --headless). */
|
|
24
|
+
headed?: boolean;
|
|
25
|
+
/** Accept prompts non-interactively (reserved; currently a no-op accepted flag). */
|
|
26
|
+
yes?: boolean;
|
|
27
|
+
/** "certify" records certification + freshness baselines and labels the report. */
|
|
28
|
+
mode?: "run" | "certify";
|
|
29
|
+
}
|
|
30
|
+
declare function runCommand(opts?: RunOptions): Promise<number>;
|
|
31
|
+
|
|
32
|
+
interface CertifyOptions {
|
|
33
|
+
cwd?: string;
|
|
34
|
+
/** Run all flows as part of certification (default: don't run — just sync + show the map). */
|
|
35
|
+
run?: boolean;
|
|
36
|
+
open?: boolean;
|
|
37
|
+
headed?: boolean;
|
|
38
|
+
yes?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `seatbelt certify` — the QA-certified flow-map command. Syncs the local flow map with the
|
|
42
|
+
* YAML flows, shows the map, and (with `--run`) verifies every eligible flow in a real browser
|
|
43
|
+
* and records certification + a freshness baseline. Local-first and deterministic — no AI.
|
|
44
|
+
*/
|
|
45
|
+
declare function certifyCommand(opts?: CertifyOptions): Promise<number>;
|
|
46
|
+
|
|
47
|
+
interface RefreshFlowsOptions {
|
|
48
|
+
cwd?: string;
|
|
49
|
+
/** No backend/AI — only regenerate missing local starter templates. */
|
|
50
|
+
localOnly?: boolean;
|
|
51
|
+
ai?: boolean;
|
|
52
|
+
flow?: string;
|
|
53
|
+
apply?: boolean;
|
|
54
|
+
dryRun?: boolean;
|
|
55
|
+
provider?: "seatbelt" | "openai";
|
|
56
|
+
model?: string;
|
|
57
|
+
/** Accept safe actions non-interactively. */
|
|
58
|
+
yes?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `seatbelt refresh-flows` stays local by default and uses hosted AI only with --ai.
|
|
62
|
+
*
|
|
63
|
+
* Without --ai this command:
|
|
64
|
+
* - scaffolds the starter set if you have no flows,
|
|
65
|
+
* - with --local-only, regenerates any MISSING starter templates (never overwrites your files),
|
|
66
|
+
* - reports which flows look stale (dependency files changed since last certified),
|
|
67
|
+
* - and prints the exact next steps to refresh them.
|
|
68
|
+
*
|
|
69
|
+
* With --ai it routes through the same validated suggestion path as generate-flows.
|
|
70
|
+
*/
|
|
71
|
+
declare function refreshFlowsCommand(opts?: RefreshFlowsOptions): Promise<number>;
|
|
72
|
+
|
|
73
|
+
declare const configSchema: z.ZodDefault<z.ZodObject<{
|
|
74
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
75
|
+
app: z.ZodDefault<z.ZodObject<{
|
|
76
|
+
name: z.ZodDefault<z.ZodString>;
|
|
77
|
+
type: z.ZodOptional<z.ZodEnum<["saas", "ecommerce", "web", "api"]>>;
|
|
78
|
+
framework: z.ZodOptional<z.ZodString>;
|
|
79
|
+
packageManager: z.ZodOptional<z.ZodString>;
|
|
80
|
+
}, "strip", z.ZodTypeAny, {
|
|
81
|
+
name: string;
|
|
82
|
+
type?: "saas" | "ecommerce" | "web" | "api" | undefined;
|
|
83
|
+
framework?: string | undefined;
|
|
84
|
+
packageManager?: string | undefined;
|
|
85
|
+
}, {
|
|
86
|
+
type?: "saas" | "ecommerce" | "web" | "api" | undefined;
|
|
87
|
+
name?: string | undefined;
|
|
88
|
+
framework?: string | undefined;
|
|
89
|
+
packageManager?: string | undefined;
|
|
90
|
+
}>>;
|
|
91
|
+
server: z.ZodDefault<z.ZodObject<{
|
|
92
|
+
baseUrl: z.ZodDefault<z.ZodString>;
|
|
93
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
devCommand: z.ZodOptional<z.ZodString>;
|
|
95
|
+
startTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
96
|
+
reuseExistingServer: z.ZodDefault<z.ZodBoolean>;
|
|
97
|
+
}, "strip", z.ZodTypeAny, {
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
startTimeoutMs: number;
|
|
100
|
+
reuseExistingServer: boolean;
|
|
101
|
+
port?: number | undefined;
|
|
102
|
+
devCommand?: string | undefined;
|
|
103
|
+
}, {
|
|
104
|
+
baseUrl?: string | undefined;
|
|
105
|
+
port?: number | undefined;
|
|
106
|
+
devCommand?: string | undefined;
|
|
107
|
+
startTimeoutMs?: number | undefined;
|
|
108
|
+
reuseExistingServer?: boolean | undefined;
|
|
109
|
+
}>>;
|
|
110
|
+
services: z.ZodDefault<z.ZodObject<{
|
|
111
|
+
startCommand: z.ZodOptional<z.ZodString>;
|
|
112
|
+
waitFor: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
113
|
+
startTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
114
|
+
reuseExisting: z.ZodDefault<z.ZodBoolean>;
|
|
115
|
+
stopOnFinish: z.ZodDefault<z.ZodBoolean>;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
startTimeoutMs: number;
|
|
118
|
+
waitFor: string[];
|
|
119
|
+
reuseExisting: boolean;
|
|
120
|
+
stopOnFinish: boolean;
|
|
121
|
+
startCommand?: string | undefined;
|
|
122
|
+
}, {
|
|
123
|
+
startTimeoutMs?: number | undefined;
|
|
124
|
+
startCommand?: string | undefined;
|
|
125
|
+
waitFor?: string[] | undefined;
|
|
126
|
+
reuseExisting?: boolean | undefined;
|
|
127
|
+
stopOnFinish?: boolean | undefined;
|
|
128
|
+
}>>;
|
|
129
|
+
checks: z.ZodDefault<z.ZodObject<{
|
|
130
|
+
lint: z.ZodOptional<z.ZodString>;
|
|
131
|
+
typecheck: z.ZodOptional<z.ZodString>;
|
|
132
|
+
build: z.ZodOptional<z.ZodString>;
|
|
133
|
+
test: z.ZodOptional<z.ZodString>;
|
|
134
|
+
}, "strip", z.ZodTypeAny, {
|
|
135
|
+
lint?: string | undefined;
|
|
136
|
+
typecheck?: string | undefined;
|
|
137
|
+
build?: string | undefined;
|
|
138
|
+
test?: string | undefined;
|
|
139
|
+
}, {
|
|
140
|
+
lint?: string | undefined;
|
|
141
|
+
typecheck?: string | undefined;
|
|
142
|
+
build?: string | undefined;
|
|
143
|
+
test?: string | undefined;
|
|
144
|
+
}>>;
|
|
145
|
+
reset: z.ZodDefault<z.ZodObject<{
|
|
146
|
+
command: z.ZodOptional<z.ZodString>;
|
|
147
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
148
|
+
allowNonLocal: z.ZodDefault<z.ZodBoolean>;
|
|
149
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
150
|
+
verify: z.ZodOptional<z.ZodObject<{
|
|
151
|
+
expectStdoutContains: z.ZodOptional<z.ZodString>;
|
|
152
|
+
httpOk: z.ZodOptional<z.ZodString>;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
expectStdoutContains?: string | undefined;
|
|
155
|
+
httpOk?: string | undefined;
|
|
156
|
+
}, {
|
|
157
|
+
expectStdoutContains?: string | undefined;
|
|
158
|
+
httpOk?: string | undefined;
|
|
159
|
+
}>>;
|
|
160
|
+
}, "strip", z.ZodTypeAny, {
|
|
161
|
+
required: boolean;
|
|
162
|
+
allowNonLocal: boolean;
|
|
163
|
+
timeoutMs: number;
|
|
164
|
+
command?: string | undefined;
|
|
165
|
+
verify?: {
|
|
166
|
+
expectStdoutContains?: string | undefined;
|
|
167
|
+
httpOk?: string | undefined;
|
|
168
|
+
} | undefined;
|
|
169
|
+
}, {
|
|
170
|
+
command?: string | undefined;
|
|
171
|
+
required?: boolean | undefined;
|
|
172
|
+
allowNonLocal?: boolean | undefined;
|
|
173
|
+
timeoutMs?: number | undefined;
|
|
174
|
+
verify?: {
|
|
175
|
+
expectStdoutContains?: string | undefined;
|
|
176
|
+
httpOk?: string | undefined;
|
|
177
|
+
} | undefined;
|
|
178
|
+
}>>;
|
|
179
|
+
browser: z.ZodDefault<z.ZodObject<{
|
|
180
|
+
headed: z.ZodDefault<z.ZodBoolean>;
|
|
181
|
+
freshSession: z.ZodDefault<z.ZodBoolean>;
|
|
182
|
+
slowMo: z.ZodDefault<z.ZodNumber>;
|
|
183
|
+
viewport: z.ZodDefault<z.ZodObject<{
|
|
184
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
185
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
186
|
+
}, "strip", z.ZodTypeAny, {
|
|
187
|
+
width: number;
|
|
188
|
+
height: number;
|
|
189
|
+
}, {
|
|
190
|
+
width?: number | undefined;
|
|
191
|
+
height?: number | undefined;
|
|
192
|
+
}>>;
|
|
193
|
+
video: z.ZodDefault<z.ZodBoolean>;
|
|
194
|
+
trace: z.ZodDefault<z.ZodBoolean>;
|
|
195
|
+
}, "strip", z.ZodTypeAny, {
|
|
196
|
+
headed: boolean;
|
|
197
|
+
freshSession: boolean;
|
|
198
|
+
slowMo: number;
|
|
199
|
+
viewport: {
|
|
200
|
+
width: number;
|
|
201
|
+
height: number;
|
|
202
|
+
};
|
|
203
|
+
video: boolean;
|
|
204
|
+
trace: boolean;
|
|
205
|
+
}, {
|
|
206
|
+
headed?: boolean | undefined;
|
|
207
|
+
freshSession?: boolean | undefined;
|
|
208
|
+
slowMo?: number | undefined;
|
|
209
|
+
viewport?: {
|
|
210
|
+
width?: number | undefined;
|
|
211
|
+
height?: number | undefined;
|
|
212
|
+
} | undefined;
|
|
213
|
+
video?: boolean | undefined;
|
|
214
|
+
trace?: boolean | undefined;
|
|
215
|
+
}>>;
|
|
216
|
+
ai: z.ZodDefault<z.ZodObject<{
|
|
217
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
218
|
+
backend: z.ZodDefault<z.ZodString>;
|
|
219
|
+
}, "strip", z.ZodTypeAny, {
|
|
220
|
+
enabled: boolean;
|
|
221
|
+
backend: string;
|
|
222
|
+
}, {
|
|
223
|
+
enabled?: boolean | undefined;
|
|
224
|
+
backend?: string | undefined;
|
|
225
|
+
}>>;
|
|
226
|
+
flows: z.ZodDefault<z.ZodObject<{
|
|
227
|
+
dir: z.ZodDefault<z.ZodString>;
|
|
228
|
+
run: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
229
|
+
autoRefresh: z.ZodDefault<z.ZodEnum<["prompt", "off", "always"]>>;
|
|
230
|
+
}, "strip", z.ZodTypeAny, {
|
|
231
|
+
dir: string;
|
|
232
|
+
run: string[];
|
|
233
|
+
autoRefresh: "prompt" | "off" | "always";
|
|
234
|
+
}, {
|
|
235
|
+
dir?: string | undefined;
|
|
236
|
+
run?: string[] | undefined;
|
|
237
|
+
autoRefresh?: "prompt" | "off" | "always" | undefined;
|
|
238
|
+
}>>;
|
|
239
|
+
report: z.ZodDefault<z.ZodObject<{
|
|
240
|
+
formats: z.ZodDefault<z.ZodArray<z.ZodEnum<["html", "md"]>, "many">>;
|
|
241
|
+
openOnFinish: z.ZodDefault<z.ZodBoolean>;
|
|
242
|
+
}, "strip", z.ZodTypeAny, {
|
|
243
|
+
formats: ("html" | "md")[];
|
|
244
|
+
openOnFinish: boolean;
|
|
245
|
+
}, {
|
|
246
|
+
formats?: ("html" | "md")[] | undefined;
|
|
247
|
+
openOnFinish?: boolean | undefined;
|
|
248
|
+
}>>;
|
|
249
|
+
env: z.ZodDefault<z.ZodObject<{
|
|
250
|
+
file: z.ZodDefault<z.ZodString>;
|
|
251
|
+
}, "strip", z.ZodTypeAny, {
|
|
252
|
+
file: string;
|
|
253
|
+
}, {
|
|
254
|
+
file?: string | undefined;
|
|
255
|
+
}>>;
|
|
256
|
+
}, "strip", z.ZodTypeAny, {
|
|
257
|
+
version: number;
|
|
258
|
+
app: {
|
|
259
|
+
name: string;
|
|
260
|
+
type?: "saas" | "ecommerce" | "web" | "api" | undefined;
|
|
261
|
+
framework?: string | undefined;
|
|
262
|
+
packageManager?: string | undefined;
|
|
263
|
+
};
|
|
264
|
+
server: {
|
|
265
|
+
baseUrl: string;
|
|
266
|
+
startTimeoutMs: number;
|
|
267
|
+
reuseExistingServer: boolean;
|
|
268
|
+
port?: number | undefined;
|
|
269
|
+
devCommand?: string | undefined;
|
|
270
|
+
};
|
|
271
|
+
services: {
|
|
272
|
+
startTimeoutMs: number;
|
|
273
|
+
waitFor: string[];
|
|
274
|
+
reuseExisting: boolean;
|
|
275
|
+
stopOnFinish: boolean;
|
|
276
|
+
startCommand?: string | undefined;
|
|
277
|
+
};
|
|
278
|
+
checks: {
|
|
279
|
+
lint?: string | undefined;
|
|
280
|
+
typecheck?: string | undefined;
|
|
281
|
+
build?: string | undefined;
|
|
282
|
+
test?: string | undefined;
|
|
283
|
+
};
|
|
284
|
+
reset: {
|
|
285
|
+
required: boolean;
|
|
286
|
+
allowNonLocal: boolean;
|
|
287
|
+
timeoutMs: number;
|
|
288
|
+
command?: string | undefined;
|
|
289
|
+
verify?: {
|
|
290
|
+
expectStdoutContains?: string | undefined;
|
|
291
|
+
httpOk?: string | undefined;
|
|
292
|
+
} | undefined;
|
|
293
|
+
};
|
|
294
|
+
browser: {
|
|
295
|
+
headed: boolean;
|
|
296
|
+
freshSession: boolean;
|
|
297
|
+
slowMo: number;
|
|
298
|
+
viewport: {
|
|
299
|
+
width: number;
|
|
300
|
+
height: number;
|
|
301
|
+
};
|
|
302
|
+
video: boolean;
|
|
303
|
+
trace: boolean;
|
|
304
|
+
};
|
|
305
|
+
ai: {
|
|
306
|
+
enabled: boolean;
|
|
307
|
+
backend: string;
|
|
308
|
+
};
|
|
309
|
+
flows: {
|
|
310
|
+
dir: string;
|
|
311
|
+
run: string[];
|
|
312
|
+
autoRefresh: "prompt" | "off" | "always";
|
|
313
|
+
};
|
|
314
|
+
report: {
|
|
315
|
+
formats: ("html" | "md")[];
|
|
316
|
+
openOnFinish: boolean;
|
|
317
|
+
};
|
|
318
|
+
env: {
|
|
319
|
+
file: string;
|
|
320
|
+
};
|
|
321
|
+
}, {
|
|
322
|
+
version?: number | undefined;
|
|
323
|
+
app?: {
|
|
324
|
+
type?: "saas" | "ecommerce" | "web" | "api" | undefined;
|
|
325
|
+
name?: string | undefined;
|
|
326
|
+
framework?: string | undefined;
|
|
327
|
+
packageManager?: string | undefined;
|
|
328
|
+
} | undefined;
|
|
329
|
+
server?: {
|
|
330
|
+
baseUrl?: string | undefined;
|
|
331
|
+
port?: number | undefined;
|
|
332
|
+
devCommand?: string | undefined;
|
|
333
|
+
startTimeoutMs?: number | undefined;
|
|
334
|
+
reuseExistingServer?: boolean | undefined;
|
|
335
|
+
} | undefined;
|
|
336
|
+
services?: {
|
|
337
|
+
startTimeoutMs?: number | undefined;
|
|
338
|
+
startCommand?: string | undefined;
|
|
339
|
+
waitFor?: string[] | undefined;
|
|
340
|
+
reuseExisting?: boolean | undefined;
|
|
341
|
+
stopOnFinish?: boolean | undefined;
|
|
342
|
+
} | undefined;
|
|
343
|
+
checks?: {
|
|
344
|
+
lint?: string | undefined;
|
|
345
|
+
typecheck?: string | undefined;
|
|
346
|
+
build?: string | undefined;
|
|
347
|
+
test?: string | undefined;
|
|
348
|
+
} | undefined;
|
|
349
|
+
reset?: {
|
|
350
|
+
command?: string | undefined;
|
|
351
|
+
required?: boolean | undefined;
|
|
352
|
+
allowNonLocal?: boolean | undefined;
|
|
353
|
+
timeoutMs?: number | undefined;
|
|
354
|
+
verify?: {
|
|
355
|
+
expectStdoutContains?: string | undefined;
|
|
356
|
+
httpOk?: string | undefined;
|
|
357
|
+
} | undefined;
|
|
358
|
+
} | undefined;
|
|
359
|
+
browser?: {
|
|
360
|
+
headed?: boolean | undefined;
|
|
361
|
+
freshSession?: boolean | undefined;
|
|
362
|
+
slowMo?: number | undefined;
|
|
363
|
+
viewport?: {
|
|
364
|
+
width?: number | undefined;
|
|
365
|
+
height?: number | undefined;
|
|
366
|
+
} | undefined;
|
|
367
|
+
video?: boolean | undefined;
|
|
368
|
+
trace?: boolean | undefined;
|
|
369
|
+
} | undefined;
|
|
370
|
+
ai?: {
|
|
371
|
+
enabled?: boolean | undefined;
|
|
372
|
+
backend?: string | undefined;
|
|
373
|
+
} | undefined;
|
|
374
|
+
flows?: {
|
|
375
|
+
dir?: string | undefined;
|
|
376
|
+
run?: string[] | undefined;
|
|
377
|
+
autoRefresh?: "prompt" | "off" | "always" | undefined;
|
|
378
|
+
} | undefined;
|
|
379
|
+
report?: {
|
|
380
|
+
formats?: ("html" | "md")[] | undefined;
|
|
381
|
+
openOnFinish?: boolean | undefined;
|
|
382
|
+
} | undefined;
|
|
383
|
+
env?: {
|
|
384
|
+
file?: string | undefined;
|
|
385
|
+
} | undefined;
|
|
386
|
+
}>>;
|
|
387
|
+
type SeatbeltConfig = z.infer<typeof configSchema>;
|
|
388
|
+
|
|
389
|
+
interface ProjectPaths {
|
|
390
|
+
/** Absolute project root (cwd). */
|
|
391
|
+
cwd: string;
|
|
392
|
+
/** Absolute path to seatbelt.config.yaml. */
|
|
393
|
+
configFile: string;
|
|
394
|
+
/** Absolute path to the .seatbelt directory. */
|
|
395
|
+
seatbeltDir: string;
|
|
396
|
+
/** Absolute path to .seatbelt/state.json. */
|
|
397
|
+
stateFile: string;
|
|
398
|
+
/** Absolute path to .seatbelt/artifacts. */
|
|
399
|
+
artifactsDir: string;
|
|
400
|
+
/** Absolute path to the project .gitignore. */
|
|
401
|
+
gitignore: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
interface DetectedRoute {
|
|
405
|
+
route: string;
|
|
406
|
+
file: string;
|
|
407
|
+
source: "next-app" | "next-pages" | "nuxt" | "vite-react" | "vue-router" | "generic";
|
|
408
|
+
}
|
|
409
|
+
declare function detectRoutes(cwd: string): DetectedRoute[];
|
|
410
|
+
|
|
411
|
+
interface PrivacyDecision {
|
|
412
|
+
allowed: boolean;
|
|
413
|
+
reason: string;
|
|
414
|
+
}
|
|
415
|
+
interface SourceSnippet {
|
|
416
|
+
path: string;
|
|
417
|
+
content: string;
|
|
418
|
+
}
|
|
419
|
+
interface SourceCollection {
|
|
420
|
+
included: SourceSnippet[];
|
|
421
|
+
excluded: Array<{
|
|
422
|
+
path: string;
|
|
423
|
+
reason: string;
|
|
424
|
+
}>;
|
|
425
|
+
}
|
|
426
|
+
declare function privacyDecision(relPath: string, sizeBytes?: number, maxBytes?: number): PrivacyDecision;
|
|
427
|
+
declare function redactSecrets(input: string): string;
|
|
428
|
+
declare function collectEnvVarNames(cwd: string): string[];
|
|
429
|
+
declare function collectSourceFiles(cwd: string, relPaths: string[], maxFiles?: number): SourceCollection;
|
|
430
|
+
|
|
431
|
+
interface PackageSummary {
|
|
432
|
+
name: string | null;
|
|
433
|
+
scripts: Record<string, string>;
|
|
434
|
+
dependencies: string[];
|
|
435
|
+
devDependencies: string[];
|
|
436
|
+
}
|
|
437
|
+
interface ExistingFlowSummary {
|
|
438
|
+
id: string;
|
|
439
|
+
name: string;
|
|
440
|
+
file: string;
|
|
441
|
+
status: string | null;
|
|
442
|
+
yaml: string;
|
|
443
|
+
}
|
|
444
|
+
interface StaleFlowSummary {
|
|
445
|
+
id: string;
|
|
446
|
+
changedFiles: string[];
|
|
447
|
+
}
|
|
448
|
+
interface AppInspection {
|
|
449
|
+
cwd: string;
|
|
450
|
+
packageManager: string;
|
|
451
|
+
framework: string;
|
|
452
|
+
packageJson: PackageSummary | null;
|
|
453
|
+
routes: DetectedRoute[];
|
|
454
|
+
existingFlows: ExistingFlowSummary[];
|
|
455
|
+
stateFlowIds: string[];
|
|
456
|
+
staleFlows: StaleFlowSummary[];
|
|
457
|
+
candidateFlows: string[];
|
|
458
|
+
envVarNames: string[];
|
|
459
|
+
source: SourceCollection;
|
|
460
|
+
}
|
|
461
|
+
declare function inferCandidateFlows(routes: DetectedRoute[], pkg: PackageSummary | null): string[];
|
|
462
|
+
declare function inspectApp(paths: ProjectPaths, config: SeatbeltConfig): Promise<AppInspection>;
|
|
463
|
+
|
|
464
|
+
type FlowGenerationMode = "generate" | "refresh";
|
|
465
|
+
declare function formatInspectionForPrompt(inspection: AppInspection): string;
|
|
466
|
+
declare function buildFlowPrompt(inspection: AppInspection, mode: FlowGenerationMode, targetFlows?: string[]): string;
|
|
467
|
+
declare function buildCopyPasteFallbackPrompt(inspection: AppInspection, targetFlows?: string[]): string;
|
|
468
|
+
|
|
469
|
+
type AiProviderName = "seatbelt" | "openai";
|
|
470
|
+
interface FlowAiRequest {
|
|
471
|
+
prompt: string;
|
|
472
|
+
model: string;
|
|
473
|
+
mode?: "generate-flows" | "refresh-flows";
|
|
474
|
+
cliVersion?: string;
|
|
475
|
+
project?: unknown;
|
|
476
|
+
}
|
|
477
|
+
interface FlowAiProvider {
|
|
478
|
+
name: AiProviderName;
|
|
479
|
+
available(): boolean;
|
|
480
|
+
generate(request: FlowAiRequest): Promise<string>;
|
|
481
|
+
}
|
|
482
|
+
declare function defaultModel(provider: AiProviderName): string;
|
|
483
|
+
|
|
484
|
+
interface GenerateFlowsOptions {
|
|
485
|
+
cwd?: string;
|
|
486
|
+
localOnly?: boolean;
|
|
487
|
+
dryRun?: boolean;
|
|
488
|
+
yes?: boolean;
|
|
489
|
+
provider?: AiProviderName;
|
|
490
|
+
model?: string;
|
|
491
|
+
}
|
|
492
|
+
interface FlowGenerationRunOptions extends GenerateFlowsOptions {
|
|
493
|
+
mode: FlowGenerationMode;
|
|
494
|
+
targetFlows?: string[];
|
|
495
|
+
apply?: boolean;
|
|
496
|
+
}
|
|
497
|
+
declare function runFlowGeneration(opts: FlowGenerationRunOptions): Promise<number>;
|
|
498
|
+
declare function generateFlowsCommand(opts?: GenerateFlowsOptions): Promise<number>;
|
|
499
|
+
|
|
500
|
+
interface SetupResetOptions {
|
|
501
|
+
cwd?: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* `seatbelt setup-reset` / `seatbelt reset-prompt` — generate (never execute) a paste-ready
|
|
505
|
+
* prompt that asks the user's coding agent to add a safe clean-test-user reset script.
|
|
506
|
+
*/
|
|
507
|
+
declare function setupResetCommand(opts?: SetupResetOptions): Promise<number>;
|
|
508
|
+
|
|
509
|
+
interface LintOptions {
|
|
510
|
+
cwd?: string;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* `seatbelt lint` — a fast, non-browser safety pass. Runs the detected lint / typecheck /
|
|
514
|
+
* build / test scripts sequentially. Free and local; never touches a backend.
|
|
515
|
+
*/
|
|
516
|
+
declare function lintCommand(opts?: LintOptions): Promise<number>;
|
|
517
|
+
|
|
518
|
+
interface DoctorOptions {
|
|
519
|
+
cwd?: string;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* `seatbelt doctor` — plain-language diagnosis of the project. Never a stack trace,
|
|
523
|
+
* never requires a backend. Exit 0 if the project looks runnable, 1 if there's a blocker.
|
|
524
|
+
*/
|
|
525
|
+
declare function doctorCommand(opts?: DoctorOptions): Promise<number>;
|
|
526
|
+
|
|
527
|
+
interface ReportOptions {
|
|
528
|
+
cwd?: string;
|
|
529
|
+
/** Print the path instead of opening the report. */
|
|
530
|
+
path?: boolean;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* `seatbelt report` — open the latest report without re-running QA.
|
|
534
|
+
*/
|
|
535
|
+
declare function reportCommand(opts?: ReportOptions): Promise<number>;
|
|
536
|
+
|
|
537
|
+
interface LoadOptions {
|
|
538
|
+
cwd?: string;
|
|
539
|
+
/** CLI flags that override the config file (highest precedence). */
|
|
540
|
+
overrides?: Record<string, unknown>;
|
|
541
|
+
}
|
|
542
|
+
interface LoadedConfig {
|
|
543
|
+
config: SeatbeltConfig;
|
|
544
|
+
configFileExists: boolean;
|
|
545
|
+
paths: ProjectPaths;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Load and validate config. Merge order (highest wins):
|
|
549
|
+
* defaults < detected < seatbelt.config.yaml < CLI overrides
|
|
550
|
+
*
|
|
551
|
+
* Validation errors are returned in plain language — raw zod output is only shown
|
|
552
|
+
* when SEATBELT_DEBUG=1 (via the top-level error printer).
|
|
553
|
+
*/
|
|
554
|
+
declare function loadConfig(opts?: LoadOptions): Promise<LoadedConfig>;
|
|
555
|
+
|
|
556
|
+
/** The fully-defaulted config (what you get with an empty file). */
|
|
557
|
+
declare function defaultConfig(): SeatbeltConfig;
|
|
558
|
+
/**
|
|
559
|
+
* The minimal starter config `seatbelt on` writes. Deliberately tiny — the point
|
|
560
|
+
* is near-zero config. Everything omitted here falls back to schema defaults.
|
|
561
|
+
*/
|
|
562
|
+
declare const STARTER_CONFIG_YAML = "version: 1\napp:\n name: My app\n\nserver:\n baseUrl: http://localhost:3000\n reuseExistingServer: true\n\nbrowser:\n headed: true\n freshSession: true\n video: true\n trace: false\n\nreport:\n formats:\n - html\n openOnFinish: true\n\nflows:\n dir: qa-flows\n autoRefresh: prompt\n\n# Clean test user (optional but recommended). Run `seatbelt setup-reset` to generate\n# a prompt that adds a safe reset script to your app, then uncomment and set required: true.\n# reset:\n# command: npm run qa:reset-user\n# required: true\n# timeoutMs: 30000\n# allowNonLocal: false\n";
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Conservative project detection. Suggests defaults for framework / package manager /
|
|
566
|
+
* scripts / baseUrl. The config file always overrides these (see config/load.ts precedence).
|
|
567
|
+
*/
|
|
568
|
+
type DetectedFramework = "nextjs" | "nuxt" | "quasar" | "vite" | "node";
|
|
569
|
+
type DetectedPackageManager = "pnpm" | "yarn" | "npm";
|
|
570
|
+
interface DetectedConfig {
|
|
571
|
+
app?: {
|
|
572
|
+
framework?: string;
|
|
573
|
+
packageManager?: string;
|
|
574
|
+
};
|
|
575
|
+
server?: {
|
|
576
|
+
devCommand?: string;
|
|
577
|
+
baseUrl?: string;
|
|
578
|
+
port?: number;
|
|
579
|
+
};
|
|
580
|
+
checks?: {
|
|
581
|
+
build?: string;
|
|
582
|
+
lint?: string;
|
|
583
|
+
typecheck?: string;
|
|
584
|
+
test?: string;
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
interface DetectionSummary extends DetectedConfig {
|
|
588
|
+
framework: DetectedFramework;
|
|
589
|
+
packageManager: DetectedPackageManager;
|
|
590
|
+
livePortFound: boolean;
|
|
591
|
+
hasPackageJson: boolean;
|
|
592
|
+
/** All package.json script names (for `seatbelt doctor`). */
|
|
593
|
+
scripts: string[];
|
|
594
|
+
/** Detected Docker Compose file name, if any (we do NOT orchestrate it — just report it). */
|
|
595
|
+
composeFile: string | null;
|
|
596
|
+
/** Names (never values) of .env* files present. */
|
|
597
|
+
envFiles: string[];
|
|
598
|
+
}
|
|
599
|
+
declare function detect(paths: ProjectPaths): Promise<DetectionSummary>;
|
|
600
|
+
/** The plain object merged into config (only the config-shaped subset). */
|
|
601
|
+
declare function detectConfig(paths: ProjectPaths): Promise<DetectedConfig>;
|
|
602
|
+
|
|
603
|
+
interface ServerHandle {
|
|
604
|
+
/** True only if Seatbelt itself started this dev server (and is therefore allowed to stop it). */
|
|
605
|
+
startedBySeatbelt: boolean;
|
|
606
|
+
ready: boolean;
|
|
607
|
+
/** Absolute path to the captured server log, if Seatbelt started the server. */
|
|
608
|
+
logPath: string | null;
|
|
609
|
+
/** Friendly message when `ready` is false. */
|
|
610
|
+
error?: string;
|
|
611
|
+
stop(): Promise<void>;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Ensure the app is serving at `config.server.baseUrl` before browser QA.
|
|
615
|
+
* - Reuse it if already reachable.
|
|
616
|
+
* - Otherwise auto-start the detected/configured dev command, but only for a localhost URL.
|
|
617
|
+
* - Stream logs to `<runDir>/server.log`. Wait up to `server.startTimeoutMs`.
|
|
618
|
+
* - Never stop a server it did not start.
|
|
619
|
+
*/
|
|
620
|
+
declare function ensureServer(config: SeatbeltConfig, cwd: string, runDir: string): Promise<ServerHandle>;
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* The "clean test user" reset. Seatbelt runs the user's own project-specific reset command
|
|
624
|
+
* before browser flows so every run starts from a known state. It is NOT universal magic —
|
|
625
|
+
* the command is written by the user's coding agent (see `seatbelt setup-reset`).
|
|
626
|
+
*/
|
|
627
|
+
type ResetStatus = "passed" | "failed" | "skipped" | "missing" | "refused" | "not-run";
|
|
628
|
+
interface ResetReport {
|
|
629
|
+
configured: boolean;
|
|
630
|
+
required: boolean;
|
|
631
|
+
command: string | null;
|
|
632
|
+
status: ResetStatus;
|
|
633
|
+
exitCode: number | null;
|
|
634
|
+
durationMs: number;
|
|
635
|
+
logFile: string | null;
|
|
636
|
+
/** HTTP health verification (verify.httpOk), when configured. */
|
|
637
|
+
verifyHttpUrl: string | null;
|
|
638
|
+
verifyHttpOk: boolean | null;
|
|
639
|
+
summary: string;
|
|
640
|
+
}
|
|
641
|
+
/** A reset problem that must stop browser QA (verdict DO NOT MERGE). */
|
|
642
|
+
declare function resetBlocksRun(r: ResetReport): boolean;
|
|
643
|
+
/** A reset problem that is only a caution (results may be contaminated), but doesn't block. */
|
|
644
|
+
declare function resetIsCaution(r: ResetReport): boolean;
|
|
645
|
+
/** Resolve a verify.httpOk value (absolute URL, or a path against baseUrl) to an absolute URL. */
|
|
646
|
+
declare function resolveVerifyUrl(httpOk: string, baseUrl: string): string;
|
|
647
|
+
/** A reset report used when reset wasn't attempted (e.g. app unreachable). */
|
|
648
|
+
declare function resetNotRun(config: SeatbeltConfig, reason: string): ResetReport;
|
|
649
|
+
declare function runReset(config: SeatbeltConfig, cwd: string, runDir: string, baseUrl: string): Promise<ResetReport>;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Generates the paste-ready "make a clean test user" prompt for the user's coding agent,
|
|
653
|
+
* plus the config snippet to wire it up. Framing stays plain-language (no "hook"/"fixture").
|
|
654
|
+
*/
|
|
655
|
+
interface ResetPromptContext {
|
|
656
|
+
framework?: string | null;
|
|
657
|
+
packageManager?: string | null;
|
|
658
|
+
}
|
|
659
|
+
/** The command the user will end up with, respecting their package manager. */
|
|
660
|
+
declare function resetRunCommand(context: ResetPromptContext): string;
|
|
661
|
+
/** The suggested seatbelt.config.yaml snippet. */
|
|
662
|
+
declare function resetConfigSnippet(context: ResetPromptContext): string;
|
|
663
|
+
/** The full paste-ready agent prompt. Generic across Next.js / Vite / Quasar / other web apps. */
|
|
664
|
+
declare function buildResetPrompt(context: ResetPromptContext): string;
|
|
665
|
+
|
|
666
|
+
declare const flowStepSchema: z.ZodUnion<[z.ZodObject<{
|
|
667
|
+
goto: z.ZodString;
|
|
668
|
+
}, "strict", z.ZodTypeAny, {
|
|
669
|
+
goto: string;
|
|
670
|
+
}, {
|
|
671
|
+
goto: string;
|
|
672
|
+
}>, z.ZodObject<{
|
|
673
|
+
click: z.ZodString;
|
|
674
|
+
}, "strict", z.ZodTypeAny, {
|
|
675
|
+
click: string;
|
|
676
|
+
}, {
|
|
677
|
+
click: string;
|
|
678
|
+
}>, z.ZodObject<{
|
|
679
|
+
fill: z.ZodEffects<z.ZodObject<{
|
|
680
|
+
selector: z.ZodString;
|
|
681
|
+
value: z.ZodOptional<z.ZodString>;
|
|
682
|
+
valueFromEnv: z.ZodOptional<z.ZodString>;
|
|
683
|
+
}, "strict", z.ZodTypeAny, {
|
|
684
|
+
selector: string;
|
|
685
|
+
value?: string | undefined;
|
|
686
|
+
valueFromEnv?: string | undefined;
|
|
687
|
+
}, {
|
|
688
|
+
selector: string;
|
|
689
|
+
value?: string | undefined;
|
|
690
|
+
valueFromEnv?: string | undefined;
|
|
691
|
+
}>, {
|
|
692
|
+
selector: string;
|
|
693
|
+
value?: string | undefined;
|
|
694
|
+
valueFromEnv?: string | undefined;
|
|
695
|
+
}, {
|
|
696
|
+
selector: string;
|
|
697
|
+
value?: string | undefined;
|
|
698
|
+
valueFromEnv?: string | undefined;
|
|
699
|
+
}>;
|
|
700
|
+
}, "strict", z.ZodTypeAny, {
|
|
701
|
+
fill: {
|
|
702
|
+
selector: string;
|
|
703
|
+
value?: string | undefined;
|
|
704
|
+
valueFromEnv?: string | undefined;
|
|
705
|
+
};
|
|
706
|
+
}, {
|
|
707
|
+
fill: {
|
|
708
|
+
selector: string;
|
|
709
|
+
value?: string | undefined;
|
|
710
|
+
valueFromEnv?: string | undefined;
|
|
711
|
+
};
|
|
712
|
+
}>, z.ZodObject<{
|
|
713
|
+
press: z.ZodUnion<[z.ZodString, z.ZodObject<{
|
|
714
|
+
key: z.ZodString;
|
|
715
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
716
|
+
}, "strict", z.ZodTypeAny, {
|
|
717
|
+
key: string;
|
|
718
|
+
selector?: string | undefined;
|
|
719
|
+
}, {
|
|
720
|
+
key: string;
|
|
721
|
+
selector?: string | undefined;
|
|
722
|
+
}>]>;
|
|
723
|
+
}, "strict", z.ZodTypeAny, {
|
|
724
|
+
press: string | {
|
|
725
|
+
key: string;
|
|
726
|
+
selector?: string | undefined;
|
|
727
|
+
};
|
|
728
|
+
}, {
|
|
729
|
+
press: string | {
|
|
730
|
+
key: string;
|
|
731
|
+
selector?: string | undefined;
|
|
732
|
+
};
|
|
733
|
+
}>, z.ZodObject<{
|
|
734
|
+
selectOption: z.ZodObject<{
|
|
735
|
+
selector: z.ZodString;
|
|
736
|
+
value: z.ZodString;
|
|
737
|
+
}, "strict", z.ZodTypeAny, {
|
|
738
|
+
value: string;
|
|
739
|
+
selector: string;
|
|
740
|
+
}, {
|
|
741
|
+
value: string;
|
|
742
|
+
selector: string;
|
|
743
|
+
}>;
|
|
744
|
+
}, "strict", z.ZodTypeAny, {
|
|
745
|
+
selectOption: {
|
|
746
|
+
value: string;
|
|
747
|
+
selector: string;
|
|
748
|
+
};
|
|
749
|
+
}, {
|
|
750
|
+
selectOption: {
|
|
751
|
+
value: string;
|
|
752
|
+
selector: string;
|
|
753
|
+
};
|
|
754
|
+
}>, z.ZodObject<{
|
|
755
|
+
upload: z.ZodObject<{
|
|
756
|
+
selector: z.ZodString;
|
|
757
|
+
path: z.ZodString;
|
|
758
|
+
}, "strict", z.ZodTypeAny, {
|
|
759
|
+
path: string;
|
|
760
|
+
selector: string;
|
|
761
|
+
}, {
|
|
762
|
+
path: string;
|
|
763
|
+
selector: string;
|
|
764
|
+
}>;
|
|
765
|
+
}, "strict", z.ZodTypeAny, {
|
|
766
|
+
upload: {
|
|
767
|
+
path: string;
|
|
768
|
+
selector: string;
|
|
769
|
+
};
|
|
770
|
+
}, {
|
|
771
|
+
upload: {
|
|
772
|
+
path: string;
|
|
773
|
+
selector: string;
|
|
774
|
+
};
|
|
775
|
+
}>, z.ZodObject<{
|
|
776
|
+
waitForUrlContains: z.ZodString;
|
|
777
|
+
}, "strict", z.ZodTypeAny, {
|
|
778
|
+
waitForUrlContains: string;
|
|
779
|
+
}, {
|
|
780
|
+
waitForUrlContains: string;
|
|
781
|
+
}>, z.ZodObject<{
|
|
782
|
+
waitForText: z.ZodString;
|
|
783
|
+
}, "strict", z.ZodTypeAny, {
|
|
784
|
+
waitForText: string;
|
|
785
|
+
}, {
|
|
786
|
+
waitForText: string;
|
|
787
|
+
}>, z.ZodObject<{
|
|
788
|
+
waitForTimeoutMs: z.ZodNumber;
|
|
789
|
+
}, "strict", z.ZodTypeAny, {
|
|
790
|
+
waitForTimeoutMs: number;
|
|
791
|
+
}, {
|
|
792
|
+
waitForTimeoutMs: number;
|
|
793
|
+
}>, z.ZodObject<{
|
|
794
|
+
expectUrlContains: z.ZodString;
|
|
795
|
+
}, "strict", z.ZodTypeAny, {
|
|
796
|
+
expectUrlContains: string;
|
|
797
|
+
}, {
|
|
798
|
+
expectUrlContains: string;
|
|
799
|
+
}>, z.ZodObject<{
|
|
800
|
+
expectText: z.ZodString;
|
|
801
|
+
}, "strict", z.ZodTypeAny, {
|
|
802
|
+
expectText: string;
|
|
803
|
+
}, {
|
|
804
|
+
expectText: string;
|
|
805
|
+
}>, z.ZodObject<{
|
|
806
|
+
expectVisible: z.ZodString;
|
|
807
|
+
}, "strict", z.ZodTypeAny, {
|
|
808
|
+
expectVisible: string;
|
|
809
|
+
}, {
|
|
810
|
+
expectVisible: string;
|
|
811
|
+
}>, z.ZodObject<{
|
|
812
|
+
expectNotVisible: z.ZodString;
|
|
813
|
+
}, "strict", z.ZodTypeAny, {
|
|
814
|
+
expectNotVisible: string;
|
|
815
|
+
}, {
|
|
816
|
+
expectNotVisible: string;
|
|
817
|
+
}>, z.ZodObject<{
|
|
818
|
+
expectTitleContains: z.ZodString;
|
|
819
|
+
}, "strict", z.ZodTypeAny, {
|
|
820
|
+
expectTitleContains: string;
|
|
821
|
+
}, {
|
|
822
|
+
expectTitleContains: string;
|
|
823
|
+
}>, z.ZodObject<{
|
|
824
|
+
screenshot: z.ZodString;
|
|
825
|
+
}, "strict", z.ZodTypeAny, {
|
|
826
|
+
screenshot: string;
|
|
827
|
+
}, {
|
|
828
|
+
screenshot: string;
|
|
829
|
+
}>]>;
|
|
830
|
+
declare const flowDependenciesSchema: z.ZodDefault<z.ZodObject<{
|
|
831
|
+
files: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
832
|
+
routes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
833
|
+
services: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
834
|
+
}, "strip", z.ZodTypeAny, {
|
|
835
|
+
services: string[];
|
|
836
|
+
files: string[];
|
|
837
|
+
routes: string[];
|
|
838
|
+
}, {
|
|
839
|
+
services?: string[] | undefined;
|
|
840
|
+
files?: string[] | undefined;
|
|
841
|
+
routes?: string[] | undefined;
|
|
842
|
+
}>>;
|
|
843
|
+
declare const flowSchema: z.ZodObject<{
|
|
844
|
+
id: z.ZodString;
|
|
845
|
+
name: z.ZodString;
|
|
846
|
+
description: z.ZodDefault<z.ZodString>;
|
|
847
|
+
critical: z.ZodDefault<z.ZodBoolean>;
|
|
848
|
+
/**
|
|
849
|
+
* Declared status. Optional. `yellow` means "template — needs setup" and is skipped in
|
|
850
|
+
* bulk `seatbelt run` / `seatbelt certify --run` (still shown in the map). Run it explicitly
|
|
851
|
+
* (`seatbelt run <id>`) or remove the line once it's configured to include it in bulk runs.
|
|
852
|
+
*/
|
|
853
|
+
status: z.ZodOptional<z.ZodEnum<["grey", "green", "yellow", "red", "blocked"]>>;
|
|
854
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
855
|
+
dependencies: z.ZodDefault<z.ZodObject<{
|
|
856
|
+
files: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
857
|
+
routes: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
858
|
+
services: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
859
|
+
}, "strip", z.ZodTypeAny, {
|
|
860
|
+
services: string[];
|
|
861
|
+
files: string[];
|
|
862
|
+
routes: string[];
|
|
863
|
+
}, {
|
|
864
|
+
services?: string[] | undefined;
|
|
865
|
+
files?: string[] | undefined;
|
|
866
|
+
routes?: string[] | undefined;
|
|
867
|
+
}>>;
|
|
868
|
+
requires: z.ZodDefault<z.ZodObject<{
|
|
869
|
+
env: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
870
|
+
}, "strip", z.ZodTypeAny, {
|
|
871
|
+
env: string[];
|
|
872
|
+
}, {
|
|
873
|
+
env?: string[] | undefined;
|
|
874
|
+
}>>;
|
|
875
|
+
/**
|
|
876
|
+
* Prerequisite flow ids that must pass first (e.g. a dashboard flow `needs: [login]`).
|
|
877
|
+
* Prereqs run before the dependent, once per run, and share its browser context so login
|
|
878
|
+
* carries into the dependent. A failed prereq blocks the dependent.
|
|
879
|
+
*/
|
|
880
|
+
needs: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
881
|
+
startUrl: z.ZodOptional<z.ZodString>;
|
|
882
|
+
steps: z.ZodArray<z.ZodUnion<[z.ZodObject<{
|
|
883
|
+
goto: z.ZodString;
|
|
884
|
+
}, "strict", z.ZodTypeAny, {
|
|
885
|
+
goto: string;
|
|
886
|
+
}, {
|
|
887
|
+
goto: string;
|
|
888
|
+
}>, z.ZodObject<{
|
|
889
|
+
click: z.ZodString;
|
|
890
|
+
}, "strict", z.ZodTypeAny, {
|
|
891
|
+
click: string;
|
|
892
|
+
}, {
|
|
893
|
+
click: string;
|
|
894
|
+
}>, z.ZodObject<{
|
|
895
|
+
fill: z.ZodEffects<z.ZodObject<{
|
|
896
|
+
selector: z.ZodString;
|
|
897
|
+
value: z.ZodOptional<z.ZodString>;
|
|
898
|
+
valueFromEnv: z.ZodOptional<z.ZodString>;
|
|
899
|
+
}, "strict", z.ZodTypeAny, {
|
|
900
|
+
selector: string;
|
|
901
|
+
value?: string | undefined;
|
|
902
|
+
valueFromEnv?: string | undefined;
|
|
903
|
+
}, {
|
|
904
|
+
selector: string;
|
|
905
|
+
value?: string | undefined;
|
|
906
|
+
valueFromEnv?: string | undefined;
|
|
907
|
+
}>, {
|
|
908
|
+
selector: string;
|
|
909
|
+
value?: string | undefined;
|
|
910
|
+
valueFromEnv?: string | undefined;
|
|
911
|
+
}, {
|
|
912
|
+
selector: string;
|
|
913
|
+
value?: string | undefined;
|
|
914
|
+
valueFromEnv?: string | undefined;
|
|
915
|
+
}>;
|
|
916
|
+
}, "strict", z.ZodTypeAny, {
|
|
917
|
+
fill: {
|
|
918
|
+
selector: string;
|
|
919
|
+
value?: string | undefined;
|
|
920
|
+
valueFromEnv?: string | undefined;
|
|
921
|
+
};
|
|
922
|
+
}, {
|
|
923
|
+
fill: {
|
|
924
|
+
selector: string;
|
|
925
|
+
value?: string | undefined;
|
|
926
|
+
valueFromEnv?: string | undefined;
|
|
927
|
+
};
|
|
928
|
+
}>, z.ZodObject<{
|
|
929
|
+
press: z.ZodUnion<[z.ZodString, z.ZodObject<{
|
|
930
|
+
key: z.ZodString;
|
|
931
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
932
|
+
}, "strict", z.ZodTypeAny, {
|
|
933
|
+
key: string;
|
|
934
|
+
selector?: string | undefined;
|
|
935
|
+
}, {
|
|
936
|
+
key: string;
|
|
937
|
+
selector?: string | undefined;
|
|
938
|
+
}>]>;
|
|
939
|
+
}, "strict", z.ZodTypeAny, {
|
|
940
|
+
press: string | {
|
|
941
|
+
key: string;
|
|
942
|
+
selector?: string | undefined;
|
|
943
|
+
};
|
|
944
|
+
}, {
|
|
945
|
+
press: string | {
|
|
946
|
+
key: string;
|
|
947
|
+
selector?: string | undefined;
|
|
948
|
+
};
|
|
949
|
+
}>, z.ZodObject<{
|
|
950
|
+
selectOption: z.ZodObject<{
|
|
951
|
+
selector: z.ZodString;
|
|
952
|
+
value: z.ZodString;
|
|
953
|
+
}, "strict", z.ZodTypeAny, {
|
|
954
|
+
value: string;
|
|
955
|
+
selector: string;
|
|
956
|
+
}, {
|
|
957
|
+
value: string;
|
|
958
|
+
selector: string;
|
|
959
|
+
}>;
|
|
960
|
+
}, "strict", z.ZodTypeAny, {
|
|
961
|
+
selectOption: {
|
|
962
|
+
value: string;
|
|
963
|
+
selector: string;
|
|
964
|
+
};
|
|
965
|
+
}, {
|
|
966
|
+
selectOption: {
|
|
967
|
+
value: string;
|
|
968
|
+
selector: string;
|
|
969
|
+
};
|
|
970
|
+
}>, z.ZodObject<{
|
|
971
|
+
upload: z.ZodObject<{
|
|
972
|
+
selector: z.ZodString;
|
|
973
|
+
path: z.ZodString;
|
|
974
|
+
}, "strict", z.ZodTypeAny, {
|
|
975
|
+
path: string;
|
|
976
|
+
selector: string;
|
|
977
|
+
}, {
|
|
978
|
+
path: string;
|
|
979
|
+
selector: string;
|
|
980
|
+
}>;
|
|
981
|
+
}, "strict", z.ZodTypeAny, {
|
|
982
|
+
upload: {
|
|
983
|
+
path: string;
|
|
984
|
+
selector: string;
|
|
985
|
+
};
|
|
986
|
+
}, {
|
|
987
|
+
upload: {
|
|
988
|
+
path: string;
|
|
989
|
+
selector: string;
|
|
990
|
+
};
|
|
991
|
+
}>, z.ZodObject<{
|
|
992
|
+
waitForUrlContains: z.ZodString;
|
|
993
|
+
}, "strict", z.ZodTypeAny, {
|
|
994
|
+
waitForUrlContains: string;
|
|
995
|
+
}, {
|
|
996
|
+
waitForUrlContains: string;
|
|
997
|
+
}>, z.ZodObject<{
|
|
998
|
+
waitForText: z.ZodString;
|
|
999
|
+
}, "strict", z.ZodTypeAny, {
|
|
1000
|
+
waitForText: string;
|
|
1001
|
+
}, {
|
|
1002
|
+
waitForText: string;
|
|
1003
|
+
}>, z.ZodObject<{
|
|
1004
|
+
waitForTimeoutMs: z.ZodNumber;
|
|
1005
|
+
}, "strict", z.ZodTypeAny, {
|
|
1006
|
+
waitForTimeoutMs: number;
|
|
1007
|
+
}, {
|
|
1008
|
+
waitForTimeoutMs: number;
|
|
1009
|
+
}>, z.ZodObject<{
|
|
1010
|
+
expectUrlContains: z.ZodString;
|
|
1011
|
+
}, "strict", z.ZodTypeAny, {
|
|
1012
|
+
expectUrlContains: string;
|
|
1013
|
+
}, {
|
|
1014
|
+
expectUrlContains: string;
|
|
1015
|
+
}>, z.ZodObject<{
|
|
1016
|
+
expectText: z.ZodString;
|
|
1017
|
+
}, "strict", z.ZodTypeAny, {
|
|
1018
|
+
expectText: string;
|
|
1019
|
+
}, {
|
|
1020
|
+
expectText: string;
|
|
1021
|
+
}>, z.ZodObject<{
|
|
1022
|
+
expectVisible: z.ZodString;
|
|
1023
|
+
}, "strict", z.ZodTypeAny, {
|
|
1024
|
+
expectVisible: string;
|
|
1025
|
+
}, {
|
|
1026
|
+
expectVisible: string;
|
|
1027
|
+
}>, z.ZodObject<{
|
|
1028
|
+
expectNotVisible: z.ZodString;
|
|
1029
|
+
}, "strict", z.ZodTypeAny, {
|
|
1030
|
+
expectNotVisible: string;
|
|
1031
|
+
}, {
|
|
1032
|
+
expectNotVisible: string;
|
|
1033
|
+
}>, z.ZodObject<{
|
|
1034
|
+
expectTitleContains: z.ZodString;
|
|
1035
|
+
}, "strict", z.ZodTypeAny, {
|
|
1036
|
+
expectTitleContains: string;
|
|
1037
|
+
}, {
|
|
1038
|
+
expectTitleContains: string;
|
|
1039
|
+
}>, z.ZodObject<{
|
|
1040
|
+
screenshot: z.ZodString;
|
|
1041
|
+
}, "strict", z.ZodTypeAny, {
|
|
1042
|
+
screenshot: string;
|
|
1043
|
+
}, {
|
|
1044
|
+
screenshot: string;
|
|
1045
|
+
}>]>, "many">;
|
|
1046
|
+
}, "strict", z.ZodTypeAny, {
|
|
1047
|
+
name: string;
|
|
1048
|
+
id: string;
|
|
1049
|
+
description: string;
|
|
1050
|
+
critical: boolean;
|
|
1051
|
+
tags: string[];
|
|
1052
|
+
dependencies: {
|
|
1053
|
+
services: string[];
|
|
1054
|
+
files: string[];
|
|
1055
|
+
routes: string[];
|
|
1056
|
+
};
|
|
1057
|
+
requires: {
|
|
1058
|
+
env: string[];
|
|
1059
|
+
};
|
|
1060
|
+
needs: string[];
|
|
1061
|
+
steps: ({
|
|
1062
|
+
goto: string;
|
|
1063
|
+
} | {
|
|
1064
|
+
click: string;
|
|
1065
|
+
} | {
|
|
1066
|
+
fill: {
|
|
1067
|
+
selector: string;
|
|
1068
|
+
value?: string | undefined;
|
|
1069
|
+
valueFromEnv?: string | undefined;
|
|
1070
|
+
};
|
|
1071
|
+
} | {
|
|
1072
|
+
press: string | {
|
|
1073
|
+
key: string;
|
|
1074
|
+
selector?: string | undefined;
|
|
1075
|
+
};
|
|
1076
|
+
} | {
|
|
1077
|
+
selectOption: {
|
|
1078
|
+
value: string;
|
|
1079
|
+
selector: string;
|
|
1080
|
+
};
|
|
1081
|
+
} | {
|
|
1082
|
+
upload: {
|
|
1083
|
+
path: string;
|
|
1084
|
+
selector: string;
|
|
1085
|
+
};
|
|
1086
|
+
} | {
|
|
1087
|
+
waitForUrlContains: string;
|
|
1088
|
+
} | {
|
|
1089
|
+
waitForText: string;
|
|
1090
|
+
} | {
|
|
1091
|
+
waitForTimeoutMs: number;
|
|
1092
|
+
} | {
|
|
1093
|
+
expectUrlContains: string;
|
|
1094
|
+
} | {
|
|
1095
|
+
expectText: string;
|
|
1096
|
+
} | {
|
|
1097
|
+
expectVisible: string;
|
|
1098
|
+
} | {
|
|
1099
|
+
expectNotVisible: string;
|
|
1100
|
+
} | {
|
|
1101
|
+
expectTitleContains: string;
|
|
1102
|
+
} | {
|
|
1103
|
+
screenshot: string;
|
|
1104
|
+
})[];
|
|
1105
|
+
status?: "grey" | "green" | "yellow" | "red" | "blocked" | undefined;
|
|
1106
|
+
startUrl?: string | undefined;
|
|
1107
|
+
}, {
|
|
1108
|
+
name: string;
|
|
1109
|
+
id: string;
|
|
1110
|
+
steps: ({
|
|
1111
|
+
goto: string;
|
|
1112
|
+
} | {
|
|
1113
|
+
click: string;
|
|
1114
|
+
} | {
|
|
1115
|
+
fill: {
|
|
1116
|
+
selector: string;
|
|
1117
|
+
value?: string | undefined;
|
|
1118
|
+
valueFromEnv?: string | undefined;
|
|
1119
|
+
};
|
|
1120
|
+
} | {
|
|
1121
|
+
press: string | {
|
|
1122
|
+
key: string;
|
|
1123
|
+
selector?: string | undefined;
|
|
1124
|
+
};
|
|
1125
|
+
} | {
|
|
1126
|
+
selectOption: {
|
|
1127
|
+
value: string;
|
|
1128
|
+
selector: string;
|
|
1129
|
+
};
|
|
1130
|
+
} | {
|
|
1131
|
+
upload: {
|
|
1132
|
+
path: string;
|
|
1133
|
+
selector: string;
|
|
1134
|
+
};
|
|
1135
|
+
} | {
|
|
1136
|
+
waitForUrlContains: string;
|
|
1137
|
+
} | {
|
|
1138
|
+
waitForText: string;
|
|
1139
|
+
} | {
|
|
1140
|
+
waitForTimeoutMs: number;
|
|
1141
|
+
} | {
|
|
1142
|
+
expectUrlContains: string;
|
|
1143
|
+
} | {
|
|
1144
|
+
expectText: string;
|
|
1145
|
+
} | {
|
|
1146
|
+
expectVisible: string;
|
|
1147
|
+
} | {
|
|
1148
|
+
expectNotVisible: string;
|
|
1149
|
+
} | {
|
|
1150
|
+
expectTitleContains: string;
|
|
1151
|
+
} | {
|
|
1152
|
+
screenshot: string;
|
|
1153
|
+
})[];
|
|
1154
|
+
status?: "grey" | "green" | "yellow" | "red" | "blocked" | undefined;
|
|
1155
|
+
description?: string | undefined;
|
|
1156
|
+
critical?: boolean | undefined;
|
|
1157
|
+
tags?: string[] | undefined;
|
|
1158
|
+
dependencies?: {
|
|
1159
|
+
services?: string[] | undefined;
|
|
1160
|
+
files?: string[] | undefined;
|
|
1161
|
+
routes?: string[] | undefined;
|
|
1162
|
+
} | undefined;
|
|
1163
|
+
requires?: {
|
|
1164
|
+
env?: string[] | undefined;
|
|
1165
|
+
} | undefined;
|
|
1166
|
+
needs?: string[] | undefined;
|
|
1167
|
+
startUrl?: string | undefined;
|
|
1168
|
+
}>;
|
|
1169
|
+
type Flow = z.infer<typeof flowSchema>;
|
|
1170
|
+
type FlowStep = z.infer<typeof flowStepSchema>;
|
|
1171
|
+
type FlowDependencies$1 = z.infer<typeof flowDependenciesSchema>;
|
|
1172
|
+
/** True when a flow is a template that still needs setup (declared `status: yellow`). */
|
|
1173
|
+
declare function isNeedsSetup(flow: Flow): boolean;
|
|
1174
|
+
|
|
1175
|
+
declare const STARTER_FLOW_ID = "homepage-smoke";
|
|
1176
|
+
interface FlowLoadError {
|
|
1177
|
+
file: string;
|
|
1178
|
+
message: string;
|
|
1179
|
+
}
|
|
1180
|
+
interface LoadFlowsResult {
|
|
1181
|
+
flowsDir: string;
|
|
1182
|
+
flows: Flow[];
|
|
1183
|
+
createdStarter: boolean;
|
|
1184
|
+
/** ids of any starter templates created because the dir was empty. */
|
|
1185
|
+
createdFlowIds: string[];
|
|
1186
|
+
errors: FlowLoadError[];
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Ensure the flows dir exists and, when empty, scaffold the starter flow set.
|
|
1190
|
+
* Returns the ids of any created templates (empty if flows already existed).
|
|
1191
|
+
*/
|
|
1192
|
+
declare function ensureFlowsDir(flowsDir: string): Promise<string[]>;
|
|
1193
|
+
interface LoadFlowsOptions {
|
|
1194
|
+
/** Create a starter flow when the directory is empty (default true). Set false for read-only inspection. */
|
|
1195
|
+
create?: boolean;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Load and validate all flows in `<cwd>/<flowsDirName>`. By default creates a starter flow if
|
|
1199
|
+
* the directory is empty. Invalid flows are skipped and reported (never crash the run).
|
|
1200
|
+
*/
|
|
1201
|
+
declare function loadFlows(cwd: string, flowsDirName: string, options?: LoadFlowsOptions): Promise<LoadFlowsResult>;
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Starter flow templates. Written when `qa-flows/` has no flows yet (by `seatbelt on`,
|
|
1205
|
+
* `seatbelt run`, or `seatbelt certify`).
|
|
1206
|
+
*
|
|
1207
|
+
* Design rules (kept generic on purpose — validated against Syllawise's Next.js + Quasar
|
|
1208
|
+
* apps, but NOT overfit to them):
|
|
1209
|
+
* - Homepage runs immediately (no setup).
|
|
1210
|
+
* - login / dashboard / signup / upload / checkout ship as `status: yellow` = "needs setup":
|
|
1211
|
+
* they appear in the QA map but are SKIPPED in bulk runs until you configure them and
|
|
1212
|
+
* remove the `status: yellow` line (or run them explicitly with `seatbelt run <id>`).
|
|
1213
|
+
* - Selectors prefer stable attributes (type / placeholder / role / text) over brittle ones,
|
|
1214
|
+
* and every template says, in a comment, to confirm routes + selectors for YOUR app.
|
|
1215
|
+
*/
|
|
1216
|
+
interface FlowTemplate {
|
|
1217
|
+
id: string;
|
|
1218
|
+
yaml: string;
|
|
1219
|
+
}
|
|
1220
|
+
/** The full starter set, in a sensible display order. */
|
|
1221
|
+
declare const STARTER_FLOW_TEMPLATES: FlowTemplate[];
|
|
1222
|
+
/**
|
|
1223
|
+
* Ensure the flows dir exists and, if it has NO flow files yet, write the full starter set.
|
|
1224
|
+
* Never adds files to an existing set (so we don't surprise users who already have flows).
|
|
1225
|
+
* Returns the ids of any templates created (empty if flows already existed).
|
|
1226
|
+
*/
|
|
1227
|
+
declare function ensureStarterFlows(flowsDir: string): Promise<string[]>;
|
|
1228
|
+
|
|
1229
|
+
/** Whether the configured env file exists (for a friendly "add your creds" warning). */
|
|
1230
|
+
declare function envFileExists(cwd: string, envFile: string): boolean;
|
|
1231
|
+
/**
|
|
1232
|
+
* Resolve env values from the project env file, with `process.env` taking precedence
|
|
1233
|
+
* (so CI / shell overrides win, matching dotenv's default of not clobbering existing vars).
|
|
1234
|
+
*/
|
|
1235
|
+
declare function loadEnvValues(cwd: string, envFile: string): Record<string, string>;
|
|
1236
|
+
/** All env var names a flow needs: `requires.env` plus any referenced by `valueFromEnv`. */
|
|
1237
|
+
declare function requiredEnvForFlow(flow: Flow): string[];
|
|
1238
|
+
/** Which required env vars are missing/empty in the resolved values. */
|
|
1239
|
+
declare function missingEnvForFlow(flow: Flow, values: Record<string, string>): string[];
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Flow prerequisite graph (`needs`). Deterministic, small, no magic:
|
|
1243
|
+
* - validate that every `needs` id exists,
|
|
1244
|
+
* - detect cycles (with a friendly loop path),
|
|
1245
|
+
* - expand a set of "root" flows to the closure of their prerequisites,
|
|
1246
|
+
* - group flows into "journeys" (weakly-connected components) so a prereq like login runs once
|
|
1247
|
+
* and its browser context (auth) carries into every dependent, while unrelated flows stay
|
|
1248
|
+
* in fresh, isolated contexts.
|
|
1249
|
+
*/
|
|
1250
|
+
interface GraphError {
|
|
1251
|
+
message: string;
|
|
1252
|
+
}
|
|
1253
|
+
/** Report any `needs` that reference a non-existent flow id. */
|
|
1254
|
+
declare function validateNeeds(flows: Flow[]): GraphError[];
|
|
1255
|
+
/**
|
|
1256
|
+
* Find one dependency cycle, if any, and return its path (e.g. ["login","dash","login"]).
|
|
1257
|
+
* Returns null when the graph is acyclic. Missing `needs` ids are ignored here (validateNeeds
|
|
1258
|
+
* reports those separately).
|
|
1259
|
+
*/
|
|
1260
|
+
declare function findCycle(flows: Flow[]): string[] | null;
|
|
1261
|
+
/**
|
|
1262
|
+
* Expand `roots` to include all transitive prerequisites. Order is not guaranteed here
|
|
1263
|
+
* (use `topoSort` for ordering). Unknown ids are skipped.
|
|
1264
|
+
*/
|
|
1265
|
+
declare function expandWithPrereqs(rootIds: string[], byId: Map<string, Flow>): Set<string>;
|
|
1266
|
+
/** Topologically sort a subset of flow ids so every prerequisite comes before its dependents. */
|
|
1267
|
+
declare function topoSort(ids: Set<string>, byId: Map<string, Flow>): string[];
|
|
1268
|
+
/**
|
|
1269
|
+
* Group a set of flow ids into journeys: weakly-connected components over `needs` edges.
|
|
1270
|
+
* Flows linked by prerequisites end up in the same journey (one shared browser context);
|
|
1271
|
+
* independent flows are their own singleton journeys (fresh context each).
|
|
1272
|
+
* Each returned journey is topologically ordered (prereqs first).
|
|
1273
|
+
*/
|
|
1274
|
+
declare function journeys(ids: Set<string>, byId: Map<string, Flow>): string[][];
|
|
1275
|
+
|
|
1276
|
+
interface FlowMetaEntry {
|
|
1277
|
+
dependencies: FlowDependencies$1;
|
|
1278
|
+
/** path -> sha1 of file contents, captured at the last certification. */
|
|
1279
|
+
fingerprint: Record<string, string>;
|
|
1280
|
+
lastCertifiedRunId: string | null;
|
|
1281
|
+
lastCertifiedAt: string | null;
|
|
1282
|
+
}
|
|
1283
|
+
interface FlowMeta {
|
|
1284
|
+
version: number;
|
|
1285
|
+
flows: Record<string, FlowMetaEntry>;
|
|
1286
|
+
}
|
|
1287
|
+
interface StaleResult {
|
|
1288
|
+
stale: boolean;
|
|
1289
|
+
changedFiles: string[];
|
|
1290
|
+
}
|
|
1291
|
+
declare function readFlowMeta(paths: ProjectPaths): FlowMeta;
|
|
1292
|
+
declare function writeFlowMeta(paths: ProjectPaths, meta: FlowMeta): Promise<void>;
|
|
1293
|
+
/**
|
|
1294
|
+
* Fingerprint a flow's declared dependency files (literal paths only). Missing files and
|
|
1295
|
+
* glob patterns are skipped in the Day-4 seed — the fingerprint is a best-effort signal.
|
|
1296
|
+
*/
|
|
1297
|
+
declare function fingerprintDependencies(cwd: string, deps: FlowDependencies$1): Record<string, string>;
|
|
1298
|
+
/** Compare a current fingerprint against a stored baseline. Stale only if a baseline existed. */
|
|
1299
|
+
declare function detectStale(current: Record<string, string>, baseline: Record<string, string> | undefined): StaleResult;
|
|
1300
|
+
|
|
1301
|
+
interface StepResult {
|
|
1302
|
+
index: number;
|
|
1303
|
+
label: string;
|
|
1304
|
+
ok: boolean;
|
|
1305
|
+
error: string | null;
|
|
1306
|
+
}
|
|
1307
|
+
interface FlowRunResult {
|
|
1308
|
+
id: string;
|
|
1309
|
+
name: string;
|
|
1310
|
+
description: string;
|
|
1311
|
+
critical: boolean;
|
|
1312
|
+
status: "green" | "red";
|
|
1313
|
+
finalUrl: string | null;
|
|
1314
|
+
pageTitle: string | null;
|
|
1315
|
+
steps: StepResult[];
|
|
1316
|
+
/** Absolute paths of screenshots captured during the flow. */
|
|
1317
|
+
screenshots: string[];
|
|
1318
|
+
consoleErrors: string[];
|
|
1319
|
+
networkFailures: string[];
|
|
1320
|
+
failureSummary: string | null;
|
|
1321
|
+
durationMs: number;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Run one flow on a fresh page. Attaches console/network listeners, executes steps in
|
|
1325
|
+
* order, stops on the first failing step (capturing a last-state screenshot), and never
|
|
1326
|
+
* throws — failures are returned in the result so the caller can continue to the next flow.
|
|
1327
|
+
*/
|
|
1328
|
+
declare function runFlow(page: Page, flow: Flow, baseUrl: string, runDir: string, env: Record<string, string>, cwd: string): Promise<FlowRunResult>;
|
|
1329
|
+
|
|
1330
|
+
interface ParsedFlowYaml {
|
|
1331
|
+
id: string;
|
|
1332
|
+
yaml: string;
|
|
1333
|
+
flow: Flow;
|
|
1334
|
+
}
|
|
1335
|
+
interface RejectedFlowYaml {
|
|
1336
|
+
yaml: string;
|
|
1337
|
+
error: string;
|
|
1338
|
+
}
|
|
1339
|
+
interface ExtractedYamlResult {
|
|
1340
|
+
valid: ParsedFlowYaml[];
|
|
1341
|
+
rejected: RejectedFlowYaml[];
|
|
1342
|
+
}
|
|
1343
|
+
declare function extractFlowYaml(text: string): ExtractedYamlResult;
|
|
1344
|
+
|
|
1345
|
+
interface FlowSuggestion {
|
|
1346
|
+
id: string;
|
|
1347
|
+
yaml: string;
|
|
1348
|
+
flow: Flow;
|
|
1349
|
+
source: "local" | "ai";
|
|
1350
|
+
assumptions: string[];
|
|
1351
|
+
rejected?: RejectedFlowYaml[];
|
|
1352
|
+
}
|
|
1353
|
+
declare function localFlowSuggestions(inspection: AppInspection, requestedIds?: string[]): FlowSuggestion[];
|
|
1354
|
+
declare function aiFlowSuggestions(output: string): {
|
|
1355
|
+
suggestions: FlowSuggestion[];
|
|
1356
|
+
rejected: RejectedFlowYaml[];
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
declare class OpenAIFlowProvider implements FlowAiProvider {
|
|
1360
|
+
private readonly apiKey;
|
|
1361
|
+
readonly name: "openai";
|
|
1362
|
+
constructor(apiKey?: string | undefined);
|
|
1363
|
+
available(): boolean;
|
|
1364
|
+
generate(request: FlowAiRequest): Promise<string>;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
declare function seatbeltApiUrl(): string;
|
|
1368
|
+
/**
|
|
1369
|
+
* Resolve the Seatbelt token. Precedence: SEATBELT_API_TOKEN / SEATBELT_API_KEY env
|
|
1370
|
+
* (advanced / CI override) → the token stored by `seatbelt login` → none. This lets
|
|
1371
|
+
* the normal flow "just work" after a one-time browser login, with the env var kept
|
|
1372
|
+
* as an advanced/CI fallback.
|
|
1373
|
+
*/
|
|
1374
|
+
declare function seatbeltApiToken(): string | undefined;
|
|
1375
|
+
declare function hostedAiDisabled(): boolean;
|
|
1376
|
+
declare class SeatbeltFlowProvider implements FlowAiProvider {
|
|
1377
|
+
readonly name: "seatbelt";
|
|
1378
|
+
private readonly apiUrl;
|
|
1379
|
+
private readonly token?;
|
|
1380
|
+
constructor(apiUrl?: string, token?: string | undefined);
|
|
1381
|
+
available(): boolean;
|
|
1382
|
+
destination(): string;
|
|
1383
|
+
generate(request: FlowAiRequest): Promise<string>;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Local Seatbelt state — the "QA-certified flow map". It tracks every discovered flow, its
|
|
1388
|
+
* status, the files/routes/services it depends on, the env it requires, and when it was last
|
|
1389
|
+
* certified (verified by an actual run). This is the durable value that grows over time.
|
|
1390
|
+
*/
|
|
1391
|
+
/**
|
|
1392
|
+
* Flow status state machine:
|
|
1393
|
+
* grey = discovered but not tested yet
|
|
1394
|
+
* green = tested and passed
|
|
1395
|
+
* yellow = needs setup / stale / partially verified (e.g. a template not yet configured)
|
|
1396
|
+
* red = tested and failed
|
|
1397
|
+
* blocked = could not run because setup/config is missing (server down, env missing)
|
|
1398
|
+
*/
|
|
1399
|
+
type FlowStatus = "grey" | "green" | "yellow" | "red" | "blocked";
|
|
1400
|
+
interface FlowDependencies {
|
|
1401
|
+
files: string[];
|
|
1402
|
+
routes: string[];
|
|
1403
|
+
services: string[];
|
|
1404
|
+
}
|
|
1405
|
+
interface FlowRequires {
|
|
1406
|
+
env: string[];
|
|
1407
|
+
}
|
|
1408
|
+
interface FlowCertification {
|
|
1409
|
+
lastCertifiedAt: string | null;
|
|
1410
|
+
lastCertifiedRunId: string | null;
|
|
1411
|
+
}
|
|
1412
|
+
interface FlowState {
|
|
1413
|
+
id: string;
|
|
1414
|
+
name: string;
|
|
1415
|
+
status: FlowStatus;
|
|
1416
|
+
description: string;
|
|
1417
|
+
tags: string[];
|
|
1418
|
+
lastRunId: string | null;
|
|
1419
|
+
lastRunAt: string | null;
|
|
1420
|
+
lastReportPath: string | null;
|
|
1421
|
+
lastFailureSummary: string | null;
|
|
1422
|
+
dependencies: FlowDependencies;
|
|
1423
|
+
requires: FlowRequires;
|
|
1424
|
+
needs: string[];
|
|
1425
|
+
certification: FlowCertification;
|
|
1426
|
+
}
|
|
1427
|
+
interface SeatbeltState {
|
|
1428
|
+
version: number;
|
|
1429
|
+
project: {
|
|
1430
|
+
name: string;
|
|
1431
|
+
};
|
|
1432
|
+
flows: FlowState[];
|
|
1433
|
+
}
|
|
1434
|
+
declare const HOMEPAGE_SMOKE_ID = "homepage-smoke";
|
|
1435
|
+
declare function emptyDependencies(): FlowDependencies;
|
|
1436
|
+
declare function emptyCertification(): FlowCertification;
|
|
1437
|
+
/** The initial state written by `seatbelt on`. */
|
|
1438
|
+
declare function seedState(projectName: string): SeatbeltState;
|
|
1439
|
+
declare function loadState(stateFile: string): Promise<SeatbeltState | null>;
|
|
1440
|
+
declare function saveState(stateFile: string, state: SeatbeltState): Promise<void>;
|
|
1441
|
+
/**
|
|
1442
|
+
* Load state, or seed a fresh one if it doesn't exist yet (e.g. the user ran
|
|
1443
|
+
* `seatbelt run` before `seatbelt on`). Keeps `run` resilient.
|
|
1444
|
+
*/
|
|
1445
|
+
declare function loadOrSeedState(stateFile: string, projectName: string): Promise<SeatbeltState>;
|
|
1446
|
+
/**
|
|
1447
|
+
* Update (or insert) a flow by id, returning a new state. Existing status/history is
|
|
1448
|
+
* preserved for fields not provided.
|
|
1449
|
+
*/
|
|
1450
|
+
declare function upsertFlow(state: SeatbeltState, patch: Pick<FlowState, "id" | "name"> & Partial<FlowState>): SeatbeltState;
|
|
1451
|
+
/** The metadata Seatbelt syncs from a flow's YAML into the map (never touches run history). */
|
|
1452
|
+
interface FlowSyncInput {
|
|
1453
|
+
id: string;
|
|
1454
|
+
name: string;
|
|
1455
|
+
description: string;
|
|
1456
|
+
tags: string[];
|
|
1457
|
+
dependencies: FlowDependencies;
|
|
1458
|
+
requires: FlowRequires;
|
|
1459
|
+
needs: string[];
|
|
1460
|
+
/** Declared status from YAML, used only to seed a brand-new flow entry. */
|
|
1461
|
+
declaredStatus?: FlowStatus;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Sync flow metadata (name/description/tags/dependencies/requires) from YAML into the map.
|
|
1465
|
+
* - Existing flows keep their status + run history + certification; only metadata is refreshed.
|
|
1466
|
+
* - New flows are created with their declared status (or grey).
|
|
1467
|
+
*/
|
|
1468
|
+
declare function syncFlowsFromYaml(state: SeatbeltState, inputs: FlowSyncInput[]): SeatbeltState;
|
|
1469
|
+
/** Record that a flow was certified (verified by a certify run). */
|
|
1470
|
+
declare function recordCertification(state: SeatbeltState, id: string, runId: string, at: string): SeatbeltState;
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Tri-state merge verdict (Day 4). One glance = one decision.
|
|
1474
|
+
*
|
|
1475
|
+
* merge ✅ required reset ok/skipped, all selected critical flows passed, no noise
|
|
1476
|
+
* caution ⚠ non-critical trouble, needs-setup flows, stale flows, or passing-with-errors
|
|
1477
|
+
* do_not_merge ⛔ required reset failed/missing, or a critical flow failed / couldn't run
|
|
1478
|
+
*
|
|
1479
|
+
* Exit code: merge/caution -> 0, do_not_merge -> 1 (per REPORT_SPEC).
|
|
1480
|
+
*/
|
|
1481
|
+
type Verdict = "merge" | "caution" | "do_not_merge";
|
|
1482
|
+
interface VerdictResult {
|
|
1483
|
+
verdict: Verdict;
|
|
1484
|
+
reasons: string[];
|
|
1485
|
+
}
|
|
1486
|
+
/** Just the shape the verdict needs from each flow (a subset of FlowReport). */
|
|
1487
|
+
interface FlowVerdictInput {
|
|
1488
|
+
id: string;
|
|
1489
|
+
critical: boolean;
|
|
1490
|
+
status: FlowStatus;
|
|
1491
|
+
stale: boolean;
|
|
1492
|
+
missingEnv: string[];
|
|
1493
|
+
blockedByPrereq: string | null;
|
|
1494
|
+
consoleErrors: string[];
|
|
1495
|
+
networkFailures: string[];
|
|
1496
|
+
}
|
|
1497
|
+
interface VerdictContext {
|
|
1498
|
+
/** The app/dev server could not be reached — nothing could run. */
|
|
1499
|
+
serverUnavailable?: boolean;
|
|
1500
|
+
}
|
|
1501
|
+
declare function verdictLabel(v: Verdict): string;
|
|
1502
|
+
declare function verdictEmoji(v: Verdict): string;
|
|
1503
|
+
declare function verdictExitCode(v: Verdict): number;
|
|
1504
|
+
/** CSS class used in the HTML banner/pill. */
|
|
1505
|
+
declare function verdictClass(v: Verdict): string;
|
|
1506
|
+
declare function computeVerdict(flows: FlowVerdictInput[], reset: ResetReport, context?: VerdictContext): VerdictResult;
|
|
1507
|
+
|
|
1508
|
+
/** A single step's outcome, for the report. */
|
|
1509
|
+
interface StepReportItem {
|
|
1510
|
+
index: number;
|
|
1511
|
+
label: string;
|
|
1512
|
+
ok: boolean;
|
|
1513
|
+
error: string | null;
|
|
1514
|
+
}
|
|
1515
|
+
interface ScreenshotItem {
|
|
1516
|
+
name: string;
|
|
1517
|
+
dataUri: string;
|
|
1518
|
+
}
|
|
1519
|
+
interface FlowReport {
|
|
1520
|
+
id: string;
|
|
1521
|
+
name: string;
|
|
1522
|
+
description: string;
|
|
1523
|
+
critical: boolean;
|
|
1524
|
+
status: FlowStatus;
|
|
1525
|
+
tags: string[];
|
|
1526
|
+
dependencies: FlowDependencies;
|
|
1527
|
+
requiresEnv: string[];
|
|
1528
|
+
missingEnv: string[];
|
|
1529
|
+
needsSetup: boolean;
|
|
1530
|
+
needs: string[];
|
|
1531
|
+
blockedByPrereq: string | null;
|
|
1532
|
+
stale: boolean;
|
|
1533
|
+
staleFiles: string[];
|
|
1534
|
+
finalUrl: string | null;
|
|
1535
|
+
pageTitle: string | null;
|
|
1536
|
+
failureSummary: string | null;
|
|
1537
|
+
/** A plain-language note for flows that were not executed (needs setup / blocked). */
|
|
1538
|
+
note: string | null;
|
|
1539
|
+
durationMs: number;
|
|
1540
|
+
steps: StepReportItem[];
|
|
1541
|
+
screenshots: ScreenshotItem[];
|
|
1542
|
+
videoFile: string | null;
|
|
1543
|
+
traceFile: string | null;
|
|
1544
|
+
consoleErrors: string[];
|
|
1545
|
+
networkFailures: string[];
|
|
1546
|
+
}
|
|
1547
|
+
interface RunReport {
|
|
1548
|
+
runId: string;
|
|
1549
|
+
generatedAt: string;
|
|
1550
|
+
mode: "run" | "certify";
|
|
1551
|
+
verdict: Verdict;
|
|
1552
|
+
verdictReasons: string[];
|
|
1553
|
+
baseUrl: string;
|
|
1554
|
+
framework: string | null;
|
|
1555
|
+
packageManager: string | null;
|
|
1556
|
+
serverStartedBySeatbelt: boolean;
|
|
1557
|
+
serverLogFile: string | null;
|
|
1558
|
+
artifactDir: string;
|
|
1559
|
+
sessionFresh: boolean;
|
|
1560
|
+
seatbeltVersion: string;
|
|
1561
|
+
reset: ResetReport;
|
|
1562
|
+
flows: FlowReport[];
|
|
1563
|
+
}
|
|
1564
|
+
interface StatusCounts {
|
|
1565
|
+
green: number;
|
|
1566
|
+
yellow: number;
|
|
1567
|
+
red: number;
|
|
1568
|
+
blocked: number;
|
|
1569
|
+
grey: number;
|
|
1570
|
+
}
|
|
1571
|
+
declare function countStatuses(flows: FlowReport[]): StatusCounts;
|
|
1572
|
+
/**
|
|
1573
|
+
* Deterministic, paste-ready fix prompt for Claude / Cursor / Codex. Never depends on AI —
|
|
1574
|
+
* this is the local fallback that always works.
|
|
1575
|
+
*/
|
|
1576
|
+
declare function buildFixPrompt(report: RunReport): string;
|
|
1577
|
+
declare function renderReportHtml(r: RunReport): string;
|
|
1578
|
+
/** Compact markdown report, written only when `report.formats` includes "md". */
|
|
1579
|
+
declare function renderReportMarkdown(r: RunReport): string;
|
|
1580
|
+
|
|
1581
|
+
declare function isLocalhostUrl(url: string): boolean;
|
|
1582
|
+
/** TCP connect check — resolves true if something is listening on host:port. */
|
|
1583
|
+
declare function probePort(host: string, port: number, timeoutMs?: number): Promise<boolean>;
|
|
1584
|
+
declare function isUrlReachable(url: string, timeoutMs?: number): Promise<boolean>;
|
|
1585
|
+
/** Probe ports in parallel; return the first (in list order) that is reachable. */
|
|
1586
|
+
declare function firstReachablePort(host: string, ports: number[], timeoutMs?: number): Promise<number | null>;
|
|
1587
|
+
/** Poll until the URL is reachable or the timeout elapses. */
|
|
1588
|
+
declare function waitForUrlReachable(url: string, timeoutMs: number, intervalMs?: number): Promise<boolean>;
|
|
1589
|
+
|
|
1590
|
+
declare const VERSION = "0.0.1";
|
|
1591
|
+
|
|
1592
|
+
export { type AiProviderName, type AppInspection, type CertifyOptions, type DetectedConfig, type DetectedFramework, type DetectedPackageManager, type DetectedRoute, type DetectionSummary, type DoctorOptions, type ExtractedYamlResult, type Flow, type FlowAiProvider, type FlowAiRequest, type FlowCertification, type FlowDependencies, type FlowGenerationRunOptions, type FlowLoadError, type FlowMeta, type FlowMetaEntry, type FlowReport, type FlowRequires, type FlowRunResult, type FlowState, type FlowStatus, type FlowStep, type FlowSuggestion, type FlowSyncInput, type FlowTemplate, type FlowVerdictInput, type GenerateFlowsOptions, type GraphError, HOMEPAGE_SMOKE_ID, type InitOptions, type LintOptions, type LoadFlowsResult, type LoadOptions, type LoadedConfig, OpenAIFlowProvider, type ParsedFlowYaml, type PrivacyDecision, type RefreshFlowsOptions, type RejectedFlowYaml, type ReportOptions, type ResetReport, type ResetStatus, type RunOptions, type RunReport, STARTER_CONFIG_YAML, STARTER_FLOW_ID, STARTER_FLOW_TEMPLATES, type SeatbeltConfig, SeatbeltFlowProvider, type SeatbeltState, type ServerHandle, type SetupResetOptions, type SourceCollection, type SourceSnippet, type StaleResult, type StatusCounts, type StepReportItem, type StepResult, VERSION, type Verdict, type VerdictContext, type VerdictResult, aiFlowSuggestions, buildCopyPasteFallbackPrompt, buildFixPrompt, buildFlowPrompt, buildResetPrompt, certifyCommand, collectEnvVarNames, collectSourceFiles, computeVerdict, configSchema, countStatuses, defaultConfig, defaultModel, detect, detectConfig, detectRoutes, detectStale, doctorCommand, emptyCertification, emptyDependencies, ensureFlowsDir, ensureServer, ensureStarterFlows, envFileExists, expandWithPrereqs, extractFlowYaml, findCycle, fingerprintDependencies, firstReachablePort, flowSchema, flowStepSchema, formatInspectionForPrompt, generateFlowsCommand, hostedAiDisabled, inferCandidateFlows, initCommand, inspectApp, isLocalhostUrl, isNeedsSetup, isUrlReachable, journeys, lintCommand, loadConfig, loadEnvValues, loadFlows, loadOrSeedState, loadState, localFlowSuggestions, missingEnvForFlow, privacyDecision, probePort, readFlowMeta, recordCertification, redactSecrets, refreshFlowsCommand, renderReportHtml, renderReportMarkdown, reportCommand, requiredEnvForFlow, resetBlocksRun, resetConfigSnippet, resetIsCaution, resetNotRun, resetRunCommand, resolveVerifyUrl, runCommand, runFlow, runFlowGeneration, runReset, saveState, seatbeltApiToken, seatbeltApiUrl, seedState, setupResetCommand, syncFlowsFromYaml, topoSort, upsertFlow, validateNeeds, verdictClass, verdictEmoji, verdictExitCode, verdictLabel, waitForUrlReachable, writeFlowMeta };
|