@hasna/testers 0.0.5 → 0.0.7
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/cli/index.js +1580 -342
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/environments.d.ts +22 -0
- package/dist/db/environments.d.ts.map +1 -0
- package/dist/db/flows.d.ts +12 -0
- package/dist/db/flows.d.ts.map +1 -0
- package/dist/db/runs.d.ts.map +1 -1
- package/dist/db/scenarios.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +481 -165
- package/dist/lib/assertions.d.ts +26 -0
- package/dist/lib/assertions.d.ts.map +1 -0
- package/dist/lib/ci.d.ts +2 -0
- package/dist/lib/ci.d.ts.map +1 -0
- package/dist/lib/openapi-import.d.ts +8 -0
- package/dist/lib/openapi-import.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +24 -0
- package/dist/lib/recorder.d.ts.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/visual-diff.d.ts +36 -0
- package/dist/lib/visual-diff.d.ts.map +1 -0
- package/dist/mcp/index.js +3102 -2693
- package/dist/server/index.js +500 -88
- package/dist/types/index.d.ts +44 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ function scenarioFromRow(row) {
|
|
|
49
49
|
requiresAuth: row.requires_auth === 1,
|
|
50
50
|
authConfig: row.auth_config ? JSON.parse(row.auth_config) : null,
|
|
51
51
|
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
52
|
+
assertions: JSON.parse(row.assertions || "[]"),
|
|
52
53
|
version: row.version,
|
|
53
54
|
createdAt: row.created_at,
|
|
54
55
|
updatedAt: row.updated_at
|
|
@@ -68,7 +69,8 @@ function runFromRow(row) {
|
|
|
68
69
|
failed: row.failed,
|
|
69
70
|
startedAt: row.started_at,
|
|
70
71
|
finishedAt: row.finished_at,
|
|
71
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
72
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
73
|
+
isBaseline: row.is_baseline === 1
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
function resultFromRow(row) {
|
|
@@ -124,7 +126,18 @@ function scheduleFromRow(row) {
|
|
|
124
126
|
updatedAt: row.updated_at
|
|
125
127
|
};
|
|
126
128
|
}
|
|
127
|
-
|
|
129
|
+
function flowFromRow(row) {
|
|
130
|
+
return {
|
|
131
|
+
id: row.id,
|
|
132
|
+
projectId: row.project_id,
|
|
133
|
+
name: row.name,
|
|
134
|
+
description: row.description,
|
|
135
|
+
scenarioIds: JSON.parse(row.scenario_ids),
|
|
136
|
+
createdAt: row.created_at,
|
|
137
|
+
updatedAt: row.updated_at
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var MODEL_MAP, ScenarioNotFoundError, RunNotFoundError, ResultNotFoundError, VersionConflictError, BrowserError, AIClientError, TodosConnectionError, ProjectNotFoundError, AgentNotFoundError, ScheduleNotFoundError, FlowNotFoundError, DependencyCycleError;
|
|
128
141
|
var init_types = __esm(() => {
|
|
129
142
|
MODEL_MAP = {
|
|
130
143
|
quick: "claude-haiku-4-5-20251001",
|
|
@@ -191,6 +204,18 @@ var init_types = __esm(() => {
|
|
|
191
204
|
this.name = "ScheduleNotFoundError";
|
|
192
205
|
}
|
|
193
206
|
};
|
|
207
|
+
FlowNotFoundError = class FlowNotFoundError extends Error {
|
|
208
|
+
constructor(id) {
|
|
209
|
+
super(`Flow not found: ${id}`);
|
|
210
|
+
this.name = "FlowNotFoundError";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
DependencyCycleError = class DependencyCycleError extends Error {
|
|
214
|
+
constructor(scenarioId, dependsOn) {
|
|
215
|
+
super(`Adding dependency ${dependsOn} to ${scenarioId} would create a cycle`);
|
|
216
|
+
this.name = "DependencyCycleError";
|
|
217
|
+
}
|
|
218
|
+
};
|
|
194
219
|
});
|
|
195
220
|
|
|
196
221
|
// src/db/database.ts
|
|
@@ -260,8 +285,11 @@ function resetDatabase() {
|
|
|
260
285
|
const database = getDatabase();
|
|
261
286
|
database.exec("DELETE FROM screenshots");
|
|
262
287
|
database.exec("DELETE FROM results");
|
|
288
|
+
database.exec("DELETE FROM scenario_dependencies");
|
|
289
|
+
database.exec("DELETE FROM flows");
|
|
263
290
|
database.exec("DELETE FROM webhooks");
|
|
264
291
|
database.exec("DELETE FROM auth_presets");
|
|
292
|
+
database.exec("DELETE FROM environments");
|
|
265
293
|
database.exec("DELETE FROM schedules");
|
|
266
294
|
database.exec("DELETE FROM runs");
|
|
267
295
|
database.exec("DELETE FROM scenarios");
|
|
@@ -433,6 +461,46 @@ var init_database = __esm(() => {
|
|
|
433
461
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
434
462
|
);
|
|
435
463
|
CREATE INDEX IF NOT EXISTS idx_webhooks_active ON webhooks(active);
|
|
464
|
+
`,
|
|
465
|
+
`
|
|
466
|
+
CREATE TABLE IF NOT EXISTS scenario_dependencies (
|
|
467
|
+
scenario_id TEXT NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
468
|
+
depends_on TEXT NOT NULL REFERENCES scenarios(id) ON DELETE CASCADE,
|
|
469
|
+
PRIMARY KEY (scenario_id, depends_on),
|
|
470
|
+
CHECK (scenario_id != depends_on)
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
CREATE TABLE IF NOT EXISTS flows (
|
|
474
|
+
id TEXT PRIMARY KEY,
|
|
475
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
476
|
+
name TEXT NOT NULL,
|
|
477
|
+
description TEXT,
|
|
478
|
+
scenario_ids TEXT NOT NULL DEFAULT '[]',
|
|
479
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
480
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
CREATE INDEX IF NOT EXISTS idx_deps_scenario ON scenario_dependencies(scenario_id);
|
|
484
|
+
CREATE INDEX IF NOT EXISTS idx_deps_depends ON scenario_dependencies(depends_on);
|
|
485
|
+
CREATE INDEX IF NOT EXISTS idx_flows_project ON flows(project_id);
|
|
486
|
+
`,
|
|
487
|
+
`
|
|
488
|
+
ALTER TABLE scenarios ADD COLUMN assertions TEXT DEFAULT '[]';
|
|
489
|
+
`,
|
|
490
|
+
`
|
|
491
|
+
CREATE TABLE IF NOT EXISTS environments (
|
|
492
|
+
id TEXT PRIMARY KEY,
|
|
493
|
+
name TEXT NOT NULL UNIQUE,
|
|
494
|
+
url TEXT NOT NULL,
|
|
495
|
+
auth_preset_name TEXT,
|
|
496
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
497
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
498
|
+
metadata TEXT DEFAULT '{}',
|
|
499
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
500
|
+
);
|
|
501
|
+
`,
|
|
502
|
+
`
|
|
503
|
+
ALTER TABLE runs ADD COLUMN is_baseline INTEGER NOT NULL DEFAULT 0;
|
|
436
504
|
`
|
|
437
505
|
];
|
|
438
506
|
});
|
|
@@ -549,6 +617,10 @@ function updateRun(id, updates) {
|
|
|
549
617
|
sets.push("metadata = ?");
|
|
550
618
|
params.push(updates.metadata);
|
|
551
619
|
}
|
|
620
|
+
if (updates.is_baseline !== undefined) {
|
|
621
|
+
sets.push("is_baseline = ?");
|
|
622
|
+
params.push(updates.is_baseline);
|
|
623
|
+
}
|
|
552
624
|
if (sets.length === 0) {
|
|
553
625
|
return existing;
|
|
554
626
|
}
|
|
@@ -569,6 +641,157 @@ var init_runs = __esm(() => {
|
|
|
569
641
|
init_database();
|
|
570
642
|
});
|
|
571
643
|
|
|
644
|
+
// src/db/flows.ts
|
|
645
|
+
var exports_flows = {};
|
|
646
|
+
__export(exports_flows, {
|
|
647
|
+
topologicalSort: () => topologicalSort,
|
|
648
|
+
removeDependency: () => removeDependency,
|
|
649
|
+
listFlows: () => listFlows,
|
|
650
|
+
getTransitiveDependencies: () => getTransitiveDependencies,
|
|
651
|
+
getFlow: () => getFlow,
|
|
652
|
+
getDependents: () => getDependents,
|
|
653
|
+
getDependencies: () => getDependencies,
|
|
654
|
+
deleteFlow: () => deleteFlow,
|
|
655
|
+
createFlow: () => createFlow,
|
|
656
|
+
addDependency: () => addDependency
|
|
657
|
+
});
|
|
658
|
+
function addDependency(scenarioId, dependsOn) {
|
|
659
|
+
const db2 = getDatabase();
|
|
660
|
+
const visited = new Set;
|
|
661
|
+
const queue = [dependsOn];
|
|
662
|
+
while (queue.length > 0) {
|
|
663
|
+
const current = queue.shift();
|
|
664
|
+
if (current === scenarioId) {
|
|
665
|
+
throw new DependencyCycleError(scenarioId, dependsOn);
|
|
666
|
+
}
|
|
667
|
+
if (visited.has(current))
|
|
668
|
+
continue;
|
|
669
|
+
visited.add(current);
|
|
670
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(current);
|
|
671
|
+
for (const dep of deps) {
|
|
672
|
+
if (!visited.has(dep.depends_on)) {
|
|
673
|
+
queue.push(dep.depends_on);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
db2.query("INSERT OR IGNORE INTO scenario_dependencies (scenario_id, depends_on) VALUES (?, ?)").run(scenarioId, dependsOn);
|
|
678
|
+
}
|
|
679
|
+
function removeDependency(scenarioId, dependsOn) {
|
|
680
|
+
const db2 = getDatabase();
|
|
681
|
+
const result = db2.query("DELETE FROM scenario_dependencies WHERE scenario_id = ? AND depends_on = ?").run(scenarioId, dependsOn);
|
|
682
|
+
return result.changes > 0;
|
|
683
|
+
}
|
|
684
|
+
function getDependencies(scenarioId) {
|
|
685
|
+
const db2 = getDatabase();
|
|
686
|
+
const rows = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(scenarioId);
|
|
687
|
+
return rows.map((r) => r.depends_on);
|
|
688
|
+
}
|
|
689
|
+
function getDependents(scenarioId) {
|
|
690
|
+
const db2 = getDatabase();
|
|
691
|
+
const rows = db2.query("SELECT scenario_id FROM scenario_dependencies WHERE depends_on = ?").all(scenarioId);
|
|
692
|
+
return rows.map((r) => r.scenario_id);
|
|
693
|
+
}
|
|
694
|
+
function getTransitiveDependencies(scenarioId) {
|
|
695
|
+
const db2 = getDatabase();
|
|
696
|
+
const visited = new Set;
|
|
697
|
+
const queue = [scenarioId];
|
|
698
|
+
while (queue.length > 0) {
|
|
699
|
+
const current = queue.shift();
|
|
700
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(current);
|
|
701
|
+
for (const dep of deps) {
|
|
702
|
+
if (!visited.has(dep.depends_on)) {
|
|
703
|
+
visited.add(dep.depends_on);
|
|
704
|
+
queue.push(dep.depends_on);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return Array.from(visited);
|
|
709
|
+
}
|
|
710
|
+
function topologicalSort(scenarioIds) {
|
|
711
|
+
const db2 = getDatabase();
|
|
712
|
+
const idSet = new Set(scenarioIds);
|
|
713
|
+
const inDegree = new Map;
|
|
714
|
+
const dependents = new Map;
|
|
715
|
+
for (const id of scenarioIds) {
|
|
716
|
+
inDegree.set(id, 0);
|
|
717
|
+
dependents.set(id, []);
|
|
718
|
+
}
|
|
719
|
+
for (const id of scenarioIds) {
|
|
720
|
+
const deps = db2.query("SELECT depends_on FROM scenario_dependencies WHERE scenario_id = ?").all(id);
|
|
721
|
+
for (const dep of deps) {
|
|
722
|
+
if (idSet.has(dep.depends_on)) {
|
|
723
|
+
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
724
|
+
dependents.get(dep.depends_on).push(id);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const queue = [];
|
|
729
|
+
for (const [id, deg] of inDegree) {
|
|
730
|
+
if (deg === 0)
|
|
731
|
+
queue.push(id);
|
|
732
|
+
}
|
|
733
|
+
const sorted = [];
|
|
734
|
+
while (queue.length > 0) {
|
|
735
|
+
const current = queue.shift();
|
|
736
|
+
sorted.push(current);
|
|
737
|
+
for (const dep of dependents.get(current) ?? []) {
|
|
738
|
+
const newDeg = (inDegree.get(dep) ?? 1) - 1;
|
|
739
|
+
inDegree.set(dep, newDeg);
|
|
740
|
+
if (newDeg === 0)
|
|
741
|
+
queue.push(dep);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (sorted.length !== scenarioIds.length) {
|
|
745
|
+
throw new DependencyCycleError("multiple", "multiple");
|
|
746
|
+
}
|
|
747
|
+
return sorted;
|
|
748
|
+
}
|
|
749
|
+
function createFlow(input) {
|
|
750
|
+
const db2 = getDatabase();
|
|
751
|
+
const id = uuid();
|
|
752
|
+
const timestamp = now();
|
|
753
|
+
db2.query(`
|
|
754
|
+
INSERT INTO flows (id, project_id, name, description, scenario_ids, created_at, updated_at)
|
|
755
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
756
|
+
`).run(id, input.projectId ?? null, input.name, input.description ?? null, JSON.stringify(input.scenarioIds), timestamp, timestamp);
|
|
757
|
+
return getFlow(id);
|
|
758
|
+
}
|
|
759
|
+
function getFlow(id) {
|
|
760
|
+
const db2 = getDatabase();
|
|
761
|
+
let row = db2.query("SELECT * FROM flows WHERE id = ?").get(id);
|
|
762
|
+
if (row)
|
|
763
|
+
return flowFromRow(row);
|
|
764
|
+
const fullId = resolvePartialId("flows", id);
|
|
765
|
+
if (fullId) {
|
|
766
|
+
row = db2.query("SELECT * FROM flows WHERE id = ?").get(fullId);
|
|
767
|
+
if (row)
|
|
768
|
+
return flowFromRow(row);
|
|
769
|
+
}
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
function listFlows(projectId) {
|
|
773
|
+
const db2 = getDatabase();
|
|
774
|
+
if (projectId) {
|
|
775
|
+
const rows2 = db2.query("SELECT * FROM flows WHERE project_id = ? ORDER BY created_at DESC").all(projectId);
|
|
776
|
+
return rows2.map(flowFromRow);
|
|
777
|
+
}
|
|
778
|
+
const rows = db2.query("SELECT * FROM flows ORDER BY created_at DESC").all();
|
|
779
|
+
return rows.map(flowFromRow);
|
|
780
|
+
}
|
|
781
|
+
function deleteFlow(id) {
|
|
782
|
+
const db2 = getDatabase();
|
|
783
|
+
const flow = getFlow(id);
|
|
784
|
+
if (!flow)
|
|
785
|
+
return false;
|
|
786
|
+
const result = db2.query("DELETE FROM flows WHERE id = ?").run(flow.id);
|
|
787
|
+
return result.changes > 0;
|
|
788
|
+
}
|
|
789
|
+
var init_flows = __esm(() => {
|
|
790
|
+
init_database();
|
|
791
|
+
init_database();
|
|
792
|
+
init_types();
|
|
793
|
+
});
|
|
794
|
+
|
|
572
795
|
// src/index.ts
|
|
573
796
|
init_types();
|
|
574
797
|
init_database();
|
|
@@ -594,9 +817,9 @@ function createScenario(input) {
|
|
|
594
817
|
const short_id = nextShortId(input.projectId);
|
|
595
818
|
const timestamp = now();
|
|
596
819
|
db2.query(`
|
|
597
|
-
INSERT INTO scenarios (id, short_id, project_id, name, description, steps, tags, priority, model, timeout_ms, target_path, requires_auth, auth_config, metadata, version, created_at, updated_at)
|
|
598
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
599
|
-
`).run(id, short_id, input.projectId ?? null, input.name, input.description, JSON.stringify(input.steps ?? []), JSON.stringify(input.tags ?? []), input.priority ?? "medium", input.model ?? null, input.timeoutMs ?? null, input.targetPath ?? null, input.requiresAuth ? 1 : 0, input.authConfig ? JSON.stringify(input.authConfig) : null, input.metadata ? JSON.stringify(input.metadata) : null, timestamp, timestamp);
|
|
820
|
+
INSERT INTO scenarios (id, short_id, project_id, name, description, steps, tags, priority, model, timeout_ms, target_path, requires_auth, auth_config, metadata, assertions, version, created_at, updated_at)
|
|
821
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
|
|
822
|
+
`).run(id, short_id, input.projectId ?? null, input.name, input.description, JSON.stringify(input.steps ?? []), JSON.stringify(input.tags ?? []), input.priority ?? "medium", input.model ?? null, input.timeoutMs ?? null, input.targetPath ?? null, input.requiresAuth ? 1 : 0, input.authConfig ? JSON.stringify(input.authConfig) : null, input.metadata ? JSON.stringify(input.metadata) : null, JSON.stringify(input.assertions ?? []), timestamp, timestamp);
|
|
600
823
|
return getScenario(id);
|
|
601
824
|
}
|
|
602
825
|
function getScenario(id) {
|
|
@@ -714,6 +937,10 @@ function updateScenario(id, input, version) {
|
|
|
714
937
|
sets.push("metadata = ?");
|
|
715
938
|
params.push(JSON.stringify(input.metadata));
|
|
716
939
|
}
|
|
940
|
+
if (input.assertions !== undefined) {
|
|
941
|
+
sets.push("assertions = ?");
|
|
942
|
+
params.push(JSON.stringify(input.assertions));
|
|
943
|
+
}
|
|
717
944
|
if (sets.length === 0) {
|
|
718
945
|
return existing;
|
|
719
946
|
}
|
|
@@ -1043,6 +1270,10 @@ function updateLastRun(id, runId, nextRunAt) {
|
|
|
1043
1270
|
UPDATE schedules SET last_run_id = ?, last_run_at = ?, next_run_at = ?, updated_at = ? WHERE id = ?
|
|
1044
1271
|
`).run(runId, timestamp, nextRunAt, timestamp, id);
|
|
1045
1272
|
}
|
|
1273
|
+
|
|
1274
|
+
// src/index.ts
|
|
1275
|
+
init_flows();
|
|
1276
|
+
|
|
1046
1277
|
// src/lib/config.ts
|
|
1047
1278
|
init_types();
|
|
1048
1279
|
import { homedir as homedir2 } from "os";
|
|
@@ -2090,6 +2321,160 @@ function createClient(apiKey) {
|
|
|
2090
2321
|
}
|
|
2091
2322
|
// src/lib/runner.ts
|
|
2092
2323
|
init_runs();
|
|
2324
|
+
|
|
2325
|
+
// src/lib/webhooks.ts
|
|
2326
|
+
init_database();
|
|
2327
|
+
function fromRow(row) {
|
|
2328
|
+
return {
|
|
2329
|
+
id: row.id,
|
|
2330
|
+
url: row.url,
|
|
2331
|
+
events: JSON.parse(row.events),
|
|
2332
|
+
projectId: row.project_id,
|
|
2333
|
+
secret: row.secret,
|
|
2334
|
+
active: row.active === 1,
|
|
2335
|
+
createdAt: row.created_at
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function createWebhook(input) {
|
|
2339
|
+
const db2 = getDatabase();
|
|
2340
|
+
const id = uuid();
|
|
2341
|
+
const events = input.events ?? ["failed"];
|
|
2342
|
+
const secret = input.secret ?? crypto.randomUUID().replace(/-/g, "");
|
|
2343
|
+
db2.query(`
|
|
2344
|
+
INSERT INTO webhooks (id, url, events, project_id, secret, active, created_at)
|
|
2345
|
+
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
2346
|
+
`).run(id, input.url, JSON.stringify(events), input.projectId ?? null, secret, now());
|
|
2347
|
+
return getWebhook(id);
|
|
2348
|
+
}
|
|
2349
|
+
function getWebhook(id) {
|
|
2350
|
+
const db2 = getDatabase();
|
|
2351
|
+
const row = db2.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
2352
|
+
if (!row) {
|
|
2353
|
+
const rows = db2.query("SELECT * FROM webhooks WHERE id LIKE ? || '%'").all(id);
|
|
2354
|
+
if (rows.length === 1)
|
|
2355
|
+
return fromRow(rows[0]);
|
|
2356
|
+
return null;
|
|
2357
|
+
}
|
|
2358
|
+
return fromRow(row);
|
|
2359
|
+
}
|
|
2360
|
+
function listWebhooks(projectId) {
|
|
2361
|
+
const db2 = getDatabase();
|
|
2362
|
+
let query = "SELECT * FROM webhooks WHERE active = 1";
|
|
2363
|
+
const params = [];
|
|
2364
|
+
if (projectId) {
|
|
2365
|
+
query += " AND (project_id = ? OR project_id IS NULL)";
|
|
2366
|
+
params.push(projectId);
|
|
2367
|
+
}
|
|
2368
|
+
query += " ORDER BY created_at DESC";
|
|
2369
|
+
const rows = db2.query(query).all(...params);
|
|
2370
|
+
return rows.map(fromRow);
|
|
2371
|
+
}
|
|
2372
|
+
function deleteWebhook(id) {
|
|
2373
|
+
const db2 = getDatabase();
|
|
2374
|
+
const webhook = getWebhook(id);
|
|
2375
|
+
if (!webhook)
|
|
2376
|
+
return false;
|
|
2377
|
+
db2.query("DELETE FROM webhooks WHERE id = ?").run(webhook.id);
|
|
2378
|
+
return true;
|
|
2379
|
+
}
|
|
2380
|
+
function signPayload(body, secret) {
|
|
2381
|
+
const encoder = new TextEncoder;
|
|
2382
|
+
const key = encoder.encode(secret);
|
|
2383
|
+
const data = encoder.encode(body);
|
|
2384
|
+
let hash = 0;
|
|
2385
|
+
for (let i = 0;i < data.length; i++) {
|
|
2386
|
+
hash = (hash << 5) - hash + data[i] + (key[i % key.length] ?? 0) | 0;
|
|
2387
|
+
}
|
|
2388
|
+
return `sha256=${Math.abs(hash).toString(16).padStart(16, "0")}`;
|
|
2389
|
+
}
|
|
2390
|
+
function formatSlackPayload(payload) {
|
|
2391
|
+
const status = payload.run.status === "passed" ? ":white_check_mark:" : ":x:";
|
|
2392
|
+
const color = payload.run.status === "passed" ? "#22c55e" : "#ef4444";
|
|
2393
|
+
return {
|
|
2394
|
+
attachments: [
|
|
2395
|
+
{
|
|
2396
|
+
color,
|
|
2397
|
+
blocks: [
|
|
2398
|
+
{
|
|
2399
|
+
type: "section",
|
|
2400
|
+
text: {
|
|
2401
|
+
type: "mrkdwn",
|
|
2402
|
+
text: `${status} *Test Run ${payload.run.status.toUpperCase()}*
|
|
2403
|
+
` + `URL: ${payload.run.url}
|
|
2404
|
+
` + `Results: ${payload.run.passed}/${payload.run.total} passed` + (payload.run.failed > 0 ? ` (${payload.run.failed} failed)` : "") + (payload.schedule ? `
|
|
2405
|
+
Schedule: ${payload.schedule.name}` : "")
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
]
|
|
2409
|
+
}
|
|
2410
|
+
]
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
async function dispatchWebhooks(event, run, schedule) {
|
|
2414
|
+
const webhooks = listWebhooks(run.projectId ?? undefined);
|
|
2415
|
+
const payload = {
|
|
2416
|
+
event,
|
|
2417
|
+
run: {
|
|
2418
|
+
id: run.id,
|
|
2419
|
+
url: run.url,
|
|
2420
|
+
status: run.status,
|
|
2421
|
+
passed: run.passed,
|
|
2422
|
+
failed: run.failed,
|
|
2423
|
+
total: run.total
|
|
2424
|
+
},
|
|
2425
|
+
schedule,
|
|
2426
|
+
timestamp: new Date().toISOString()
|
|
2427
|
+
};
|
|
2428
|
+
for (const webhook of webhooks) {
|
|
2429
|
+
if (!webhook.events.includes(event) && !webhook.events.includes("*"))
|
|
2430
|
+
continue;
|
|
2431
|
+
const isSlack = webhook.url.includes("hooks.slack.com");
|
|
2432
|
+
const body = isSlack ? JSON.stringify(formatSlackPayload(payload)) : JSON.stringify(payload);
|
|
2433
|
+
const headers = {
|
|
2434
|
+
"Content-Type": "application/json"
|
|
2435
|
+
};
|
|
2436
|
+
if (webhook.secret) {
|
|
2437
|
+
headers["X-Testers-Signature"] = signPayload(body, webhook.secret);
|
|
2438
|
+
}
|
|
2439
|
+
try {
|
|
2440
|
+
const response = await fetch(webhook.url, {
|
|
2441
|
+
method: "POST",
|
|
2442
|
+
headers,
|
|
2443
|
+
body
|
|
2444
|
+
});
|
|
2445
|
+
if (!response.ok) {
|
|
2446
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
2447
|
+
await fetch(webhook.url, { method: "POST", headers, body });
|
|
2448
|
+
}
|
|
2449
|
+
} catch {}
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
async function testWebhook(id) {
|
|
2453
|
+
const webhook = getWebhook(id);
|
|
2454
|
+
if (!webhook)
|
|
2455
|
+
return false;
|
|
2456
|
+
const testPayload = {
|
|
2457
|
+
event: "test",
|
|
2458
|
+
run: { id: "test-run", url: "http://localhost:3000", status: "passed", passed: 3, failed: 0, total: 3 },
|
|
2459
|
+
timestamp: new Date().toISOString()
|
|
2460
|
+
};
|
|
2461
|
+
try {
|
|
2462
|
+
const body = JSON.stringify(testPayload);
|
|
2463
|
+
const response = await fetch(webhook.url, {
|
|
2464
|
+
method: "POST",
|
|
2465
|
+
headers: {
|
|
2466
|
+
"Content-Type": "application/json",
|
|
2467
|
+
...webhook.secret ? { "X-Testers-Signature": signPayload(body, webhook.secret) } : {}
|
|
2468
|
+
},
|
|
2469
|
+
body
|
|
2470
|
+
});
|
|
2471
|
+
return response.ok;
|
|
2472
|
+
} catch {
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// src/lib/runner.ts
|
|
2093
2478
|
var eventHandler = null;
|
|
2094
2479
|
function onRunEvent(handler) {
|
|
2095
2480
|
eventHandler = handler;
|
|
@@ -2098,6 +2483,20 @@ function emit(event) {
|
|
|
2098
2483
|
if (eventHandler)
|
|
2099
2484
|
eventHandler(event);
|
|
2100
2485
|
}
|
|
2486
|
+
function withTimeout(promise, ms, label) {
|
|
2487
|
+
return new Promise((resolve, reject) => {
|
|
2488
|
+
const timer = setTimeout(() => {
|
|
2489
|
+
reject(new Error(`Scenario timeout after ${ms}ms: ${label}`));
|
|
2490
|
+
}, ms);
|
|
2491
|
+
promise.then((val) => {
|
|
2492
|
+
clearTimeout(timer);
|
|
2493
|
+
resolve(val);
|
|
2494
|
+
}, (err) => {
|
|
2495
|
+
clearTimeout(timer);
|
|
2496
|
+
reject(err);
|
|
2497
|
+
});
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2101
2500
|
async function runSingleScenario(scenario, runId, options) {
|
|
2102
2501
|
const config = loadConfig();
|
|
2103
2502
|
const model = resolveModel2(options.model ?? scenario.model ?? config.defaultModel);
|
|
@@ -2120,8 +2519,9 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
2120
2519
|
viewport: config.browser.viewport
|
|
2121
2520
|
});
|
|
2122
2521
|
const targetUrl = scenario.targetPath ? `${options.url.replace(/\/$/, "")}${scenario.targetPath}` : options.url;
|
|
2123
|
-
|
|
2124
|
-
|
|
2522
|
+
const scenarioTimeout = scenario.timeoutMs ?? options.timeout ?? config.browser.timeout ?? 60000;
|
|
2523
|
+
await page.goto(targetUrl, { timeout: Math.min(scenarioTimeout, 30000) });
|
|
2524
|
+
const agentResult = await withTimeout(runAgentLoop({
|
|
2125
2525
|
client,
|
|
2126
2526
|
page,
|
|
2127
2527
|
scenario,
|
|
@@ -2142,7 +2542,7 @@ async function runSingleScenario(scenario, runId, options) {
|
|
|
2142
2542
|
stepNumber: stepEvent.stepNumber
|
|
2143
2543
|
});
|
|
2144
2544
|
}
|
|
2145
|
-
});
|
|
2545
|
+
}), scenarioTimeout, scenario.name);
|
|
2146
2546
|
for (const ss of agentResult.screenshots) {
|
|
2147
2547
|
createScreenshot({
|
|
2148
2548
|
resultId: result.id,
|
|
@@ -2194,24 +2594,70 @@ async function runBatch(scenarios, options) {
|
|
|
2194
2594
|
projectId: options.projectId
|
|
2195
2595
|
});
|
|
2196
2596
|
updateRun(run.id, { status: "running", total: scenarios.length });
|
|
2597
|
+
let sortedScenarios = scenarios;
|
|
2598
|
+
try {
|
|
2599
|
+
const { topologicalSort: topologicalSort2 } = await Promise.resolve().then(() => (init_flows(), exports_flows));
|
|
2600
|
+
const scenarioIds = scenarios.map((s) => s.id);
|
|
2601
|
+
const sortedIds = topologicalSort2(scenarioIds);
|
|
2602
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
2603
|
+
sortedScenarios = sortedIds.map((id) => scenarioMap.get(id)).filter((s) => s !== undefined);
|
|
2604
|
+
for (const s of scenarios) {
|
|
2605
|
+
if (!sortedIds.includes(s.id))
|
|
2606
|
+
sortedScenarios.push(s);
|
|
2607
|
+
}
|
|
2608
|
+
} catch {}
|
|
2197
2609
|
const results = [];
|
|
2610
|
+
const failedScenarioIds = new Set;
|
|
2611
|
+
const canRun = async (scenario) => {
|
|
2612
|
+
try {
|
|
2613
|
+
const { getDependencies: getDependencies2 } = await Promise.resolve().then(() => (init_flows(), exports_flows));
|
|
2614
|
+
const deps = getDependencies2(scenario.id);
|
|
2615
|
+
for (const depId of deps) {
|
|
2616
|
+
if (failedScenarioIds.has(depId))
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
} catch {}
|
|
2620
|
+
return true;
|
|
2621
|
+
};
|
|
2198
2622
|
if (parallel <= 1) {
|
|
2199
|
-
for (const scenario of
|
|
2623
|
+
for (const scenario of sortedScenarios) {
|
|
2624
|
+
if (!await canRun(scenario)) {
|
|
2625
|
+
const result2 = createResult({ runId: run.id, scenarioId: scenario.id, model, stepsTotal: 0 });
|
|
2626
|
+
const skipped = updateResult(result2.id, { status: "skipped", error: "Skipped: dependency failed" });
|
|
2627
|
+
results.push(skipped);
|
|
2628
|
+
failedScenarioIds.add(scenario.id);
|
|
2629
|
+
emit({ type: "scenario:error", scenarioId: scenario.id, scenarioName: scenario.name, error: "Dependency failed \u2014 skipped", runId: run.id });
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2200
2632
|
const result = await runSingleScenario(scenario, run.id, options);
|
|
2201
2633
|
results.push(result);
|
|
2634
|
+
if (result.status === "failed" || result.status === "error") {
|
|
2635
|
+
failedScenarioIds.add(scenario.id);
|
|
2636
|
+
}
|
|
2202
2637
|
}
|
|
2203
2638
|
} else {
|
|
2204
|
-
const queue = [...
|
|
2639
|
+
const queue = [...sortedScenarios];
|
|
2205
2640
|
const running = [];
|
|
2206
2641
|
const processNext = async () => {
|
|
2207
2642
|
const scenario = queue.shift();
|
|
2208
2643
|
if (!scenario)
|
|
2209
2644
|
return;
|
|
2645
|
+
if (!await canRun(scenario)) {
|
|
2646
|
+
const result2 = createResult({ runId: run.id, scenarioId: scenario.id, model, stepsTotal: 0 });
|
|
2647
|
+
const skipped = updateResult(result2.id, { status: "skipped", error: "Skipped: dependency failed" });
|
|
2648
|
+
results.push(skipped);
|
|
2649
|
+
failedScenarioIds.add(scenario.id);
|
|
2650
|
+
await processNext();
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2210
2653
|
const result = await runSingleScenario(scenario, run.id, options);
|
|
2211
2654
|
results.push(result);
|
|
2655
|
+
if (result.status === "failed" || result.status === "error") {
|
|
2656
|
+
failedScenarioIds.add(scenario.id);
|
|
2657
|
+
}
|
|
2212
2658
|
await processNext();
|
|
2213
2659
|
};
|
|
2214
|
-
const workers = Math.min(parallel,
|
|
2660
|
+
const workers = Math.min(parallel, sortedScenarios.length);
|
|
2215
2661
|
for (let i = 0;i < workers; i++) {
|
|
2216
2662
|
running.push(processNext());
|
|
2217
2663
|
}
|
|
@@ -2228,6 +2674,8 @@ async function runBatch(scenarios, options) {
|
|
|
2228
2674
|
finished_at: new Date().toISOString()
|
|
2229
2675
|
});
|
|
2230
2676
|
emit({ type: "run:complete", runId: run.id });
|
|
2677
|
+
const eventType = finalRun.status === "failed" ? "failed" : "completed";
|
|
2678
|
+
dispatchWebhooks(eventType, finalRun).catch(() => {});
|
|
2231
2679
|
return { run: finalRun, results };
|
|
2232
2680
|
}
|
|
2233
2681
|
async function runByFilter(options) {
|
|
@@ -2313,6 +2761,9 @@ function startRunAsync(options) {
|
|
|
2313
2761
|
finished_at: new Date().toISOString()
|
|
2314
2762
|
});
|
|
2315
2763
|
emit({ type: "run:complete", runId: run.id });
|
|
2764
|
+
const asyncRun = getRun(run.id);
|
|
2765
|
+
if (asyncRun)
|
|
2766
|
+
dispatchWebhooks(asyncRun.status === "failed" ? "failed" : "completed", asyncRun).catch(() => {});
|
|
2316
2767
|
} catch (error) {
|
|
2317
2768
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2318
2769
|
updateRun(run.id, {
|
|
@@ -2320,6 +2771,9 @@ function startRunAsync(options) {
|
|
|
2320
2771
|
finished_at: new Date().toISOString()
|
|
2321
2772
|
});
|
|
2322
2773
|
emit({ type: "run:complete", runId: run.id, error: errorMsg });
|
|
2774
|
+
const failedRun = getRun(run.id);
|
|
2775
|
+
if (failedRun)
|
|
2776
|
+
dispatchWebhooks("failed", failedRun).catch(() => {});
|
|
2323
2777
|
}
|
|
2324
2778
|
})();
|
|
2325
2779
|
return { runId: run.id, scenarioCount: scenarios.length };
|
|
@@ -3856,7 +4310,7 @@ function listTemplateNames() {
|
|
|
3856
4310
|
}
|
|
3857
4311
|
// src/db/auth-presets.ts
|
|
3858
4312
|
init_database();
|
|
3859
|
-
function
|
|
4313
|
+
function fromRow2(row) {
|
|
3860
4314
|
return {
|
|
3861
4315
|
id: row.id,
|
|
3862
4316
|
name: row.name,
|
|
@@ -3880,12 +4334,12 @@ function createAuthPreset(input) {
|
|
|
3880
4334
|
function getAuthPreset(name) {
|
|
3881
4335
|
const db2 = getDatabase();
|
|
3882
4336
|
const row = db2.query("SELECT * FROM auth_presets WHERE name = ?").get(name);
|
|
3883
|
-
return row ?
|
|
4337
|
+
return row ? fromRow2(row) : null;
|
|
3884
4338
|
}
|
|
3885
4339
|
function listAuthPresets() {
|
|
3886
4340
|
const db2 = getDatabase();
|
|
3887
4341
|
const rows = db2.query("SELECT * FROM auth_presets ORDER BY created_at DESC").all();
|
|
3888
|
-
return rows.map(
|
|
4342
|
+
return rows.map(fromRow2);
|
|
3889
4343
|
}
|
|
3890
4344
|
function deleteAuthPreset(name) {
|
|
3891
4345
|
const db2 = getDatabase();
|
|
@@ -4354,157 +4808,6 @@ async function startWatcher(options) {
|
|
|
4354
4808
|
process.on("SIGTERM", cleanup);
|
|
4355
4809
|
await new Promise(() => {});
|
|
4356
4810
|
}
|
|
4357
|
-
// src/lib/webhooks.ts
|
|
4358
|
-
init_database();
|
|
4359
|
-
function fromRow2(row) {
|
|
4360
|
-
return {
|
|
4361
|
-
id: row.id,
|
|
4362
|
-
url: row.url,
|
|
4363
|
-
events: JSON.parse(row.events),
|
|
4364
|
-
projectId: row.project_id,
|
|
4365
|
-
secret: row.secret,
|
|
4366
|
-
active: row.active === 1,
|
|
4367
|
-
createdAt: row.created_at
|
|
4368
|
-
};
|
|
4369
|
-
}
|
|
4370
|
-
function createWebhook(input) {
|
|
4371
|
-
const db2 = getDatabase();
|
|
4372
|
-
const id = uuid();
|
|
4373
|
-
const events = input.events ?? ["failed"];
|
|
4374
|
-
const secret = input.secret ?? crypto.randomUUID().replace(/-/g, "");
|
|
4375
|
-
db2.query(`
|
|
4376
|
-
INSERT INTO webhooks (id, url, events, project_id, secret, active, created_at)
|
|
4377
|
-
VALUES (?, ?, ?, ?, ?, 1, ?)
|
|
4378
|
-
`).run(id, input.url, JSON.stringify(events), input.projectId ?? null, secret, now());
|
|
4379
|
-
return getWebhook(id);
|
|
4380
|
-
}
|
|
4381
|
-
function getWebhook(id) {
|
|
4382
|
-
const db2 = getDatabase();
|
|
4383
|
-
const row = db2.query("SELECT * FROM webhooks WHERE id = ?").get(id);
|
|
4384
|
-
if (!row) {
|
|
4385
|
-
const rows = db2.query("SELECT * FROM webhooks WHERE id LIKE ? || '%'").all(id);
|
|
4386
|
-
if (rows.length === 1)
|
|
4387
|
-
return fromRow2(rows[0]);
|
|
4388
|
-
return null;
|
|
4389
|
-
}
|
|
4390
|
-
return fromRow2(row);
|
|
4391
|
-
}
|
|
4392
|
-
function listWebhooks(projectId) {
|
|
4393
|
-
const db2 = getDatabase();
|
|
4394
|
-
let query = "SELECT * FROM webhooks WHERE active = 1";
|
|
4395
|
-
const params = [];
|
|
4396
|
-
if (projectId) {
|
|
4397
|
-
query += " AND (project_id = ? OR project_id IS NULL)";
|
|
4398
|
-
params.push(projectId);
|
|
4399
|
-
}
|
|
4400
|
-
query += " ORDER BY created_at DESC";
|
|
4401
|
-
const rows = db2.query(query).all(...params);
|
|
4402
|
-
return rows.map(fromRow2);
|
|
4403
|
-
}
|
|
4404
|
-
function deleteWebhook(id) {
|
|
4405
|
-
const db2 = getDatabase();
|
|
4406
|
-
const webhook = getWebhook(id);
|
|
4407
|
-
if (!webhook)
|
|
4408
|
-
return false;
|
|
4409
|
-
db2.query("DELETE FROM webhooks WHERE id = ?").run(webhook.id);
|
|
4410
|
-
return true;
|
|
4411
|
-
}
|
|
4412
|
-
function signPayload(body, secret) {
|
|
4413
|
-
const encoder = new TextEncoder;
|
|
4414
|
-
const key = encoder.encode(secret);
|
|
4415
|
-
const data = encoder.encode(body);
|
|
4416
|
-
let hash = 0;
|
|
4417
|
-
for (let i = 0;i < data.length; i++) {
|
|
4418
|
-
hash = (hash << 5) - hash + data[i] + (key[i % key.length] ?? 0) | 0;
|
|
4419
|
-
}
|
|
4420
|
-
return `sha256=${Math.abs(hash).toString(16).padStart(16, "0")}`;
|
|
4421
|
-
}
|
|
4422
|
-
function formatSlackPayload(payload) {
|
|
4423
|
-
const status = payload.run.status === "passed" ? ":white_check_mark:" : ":x:";
|
|
4424
|
-
const color = payload.run.status === "passed" ? "#22c55e" : "#ef4444";
|
|
4425
|
-
return {
|
|
4426
|
-
attachments: [
|
|
4427
|
-
{
|
|
4428
|
-
color,
|
|
4429
|
-
blocks: [
|
|
4430
|
-
{
|
|
4431
|
-
type: "section",
|
|
4432
|
-
text: {
|
|
4433
|
-
type: "mrkdwn",
|
|
4434
|
-
text: `${status} *Test Run ${payload.run.status.toUpperCase()}*
|
|
4435
|
-
` + `URL: ${payload.run.url}
|
|
4436
|
-
` + `Results: ${payload.run.passed}/${payload.run.total} passed` + (payload.run.failed > 0 ? ` (${payload.run.failed} failed)` : "") + (payload.schedule ? `
|
|
4437
|
-
Schedule: ${payload.schedule.name}` : "")
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4440
|
-
]
|
|
4441
|
-
}
|
|
4442
|
-
]
|
|
4443
|
-
};
|
|
4444
|
-
}
|
|
4445
|
-
async function dispatchWebhooks(event, run, schedule) {
|
|
4446
|
-
const webhooks = listWebhooks(run.projectId ?? undefined);
|
|
4447
|
-
const payload = {
|
|
4448
|
-
event,
|
|
4449
|
-
run: {
|
|
4450
|
-
id: run.id,
|
|
4451
|
-
url: run.url,
|
|
4452
|
-
status: run.status,
|
|
4453
|
-
passed: run.passed,
|
|
4454
|
-
failed: run.failed,
|
|
4455
|
-
total: run.total
|
|
4456
|
-
},
|
|
4457
|
-
schedule,
|
|
4458
|
-
timestamp: new Date().toISOString()
|
|
4459
|
-
};
|
|
4460
|
-
for (const webhook of webhooks) {
|
|
4461
|
-
if (!webhook.events.includes(event) && !webhook.events.includes("*"))
|
|
4462
|
-
continue;
|
|
4463
|
-
const isSlack = webhook.url.includes("hooks.slack.com");
|
|
4464
|
-
const body = isSlack ? JSON.stringify(formatSlackPayload(payload)) : JSON.stringify(payload);
|
|
4465
|
-
const headers = {
|
|
4466
|
-
"Content-Type": "application/json"
|
|
4467
|
-
};
|
|
4468
|
-
if (webhook.secret) {
|
|
4469
|
-
headers["X-Testers-Signature"] = signPayload(body, webhook.secret);
|
|
4470
|
-
}
|
|
4471
|
-
try {
|
|
4472
|
-
const response = await fetch(webhook.url, {
|
|
4473
|
-
method: "POST",
|
|
4474
|
-
headers,
|
|
4475
|
-
body
|
|
4476
|
-
});
|
|
4477
|
-
if (!response.ok) {
|
|
4478
|
-
await new Promise((r) => setTimeout(r, 5000));
|
|
4479
|
-
await fetch(webhook.url, { method: "POST", headers, body });
|
|
4480
|
-
}
|
|
4481
|
-
} catch {}
|
|
4482
|
-
}
|
|
4483
|
-
}
|
|
4484
|
-
async function testWebhook(id) {
|
|
4485
|
-
const webhook = getWebhook(id);
|
|
4486
|
-
if (!webhook)
|
|
4487
|
-
return false;
|
|
4488
|
-
const testPayload = {
|
|
4489
|
-
event: "test",
|
|
4490
|
-
run: { id: "test-run", url: "http://localhost:3000", status: "passed", passed: 3, failed: 0, total: 3 },
|
|
4491
|
-
timestamp: new Date().toISOString()
|
|
4492
|
-
};
|
|
4493
|
-
try {
|
|
4494
|
-
const body = JSON.stringify(testPayload);
|
|
4495
|
-
const response = await fetch(webhook.url, {
|
|
4496
|
-
method: "POST",
|
|
4497
|
-
headers: {
|
|
4498
|
-
"Content-Type": "application/json",
|
|
4499
|
-
...webhook.secret ? { "X-Testers-Signature": signPayload(body, webhook.secret) } : {}
|
|
4500
|
-
},
|
|
4501
|
-
body
|
|
4502
|
-
});
|
|
4503
|
-
return response.ok;
|
|
4504
|
-
} catch {
|
|
4505
|
-
return false;
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
4811
|
export {
|
|
4509
4812
|
writeScenarioMeta,
|
|
4510
4813
|
writeRunMeta,
|
|
@@ -4514,6 +4817,7 @@ export {
|
|
|
4514
4817
|
updateRun,
|
|
4515
4818
|
updateResult,
|
|
4516
4819
|
updateLastRun,
|
|
4820
|
+
topologicalSort,
|
|
4517
4821
|
testWebhook,
|
|
4518
4822
|
taskToScenarioInput,
|
|
4519
4823
|
startWatcher,
|
|
@@ -4535,6 +4839,7 @@ export {
|
|
|
4535
4839
|
resolveModel as resolveModelConfig,
|
|
4536
4840
|
resolveModel2 as resolveModel,
|
|
4537
4841
|
resetDatabase,
|
|
4842
|
+
removeDependency,
|
|
4538
4843
|
registerAgent,
|
|
4539
4844
|
pullTasks,
|
|
4540
4845
|
projectFromRow,
|
|
@@ -4553,6 +4858,7 @@ export {
|
|
|
4553
4858
|
listRuns,
|
|
4554
4859
|
listResults,
|
|
4555
4860
|
listProjects,
|
|
4861
|
+
listFlows,
|
|
4556
4862
|
listAuthPresets,
|
|
4557
4863
|
listAgents,
|
|
4558
4864
|
launchBrowser,
|
|
@@ -4561,6 +4867,7 @@ export {
|
|
|
4561
4867
|
importFromTodos,
|
|
4562
4868
|
imageToBase64,
|
|
4563
4869
|
getWebhook,
|
|
4870
|
+
getTransitiveDependencies,
|
|
4564
4871
|
getTemplate,
|
|
4565
4872
|
getStarterScenarios,
|
|
4566
4873
|
getScreenshotsByResult,
|
|
@@ -4576,8 +4883,11 @@ export {
|
|
|
4576
4883
|
getProject,
|
|
4577
4884
|
getPage,
|
|
4578
4885
|
getNextRunTime,
|
|
4886
|
+
getFlow,
|
|
4579
4887
|
getExitCode,
|
|
4580
4888
|
getEnabledSchedules,
|
|
4889
|
+
getDependents,
|
|
4890
|
+
getDependencies,
|
|
4581
4891
|
getDefaultConfig,
|
|
4582
4892
|
getDatabase,
|
|
4583
4893
|
getCostSummary,
|
|
@@ -4598,6 +4908,7 @@ export {
|
|
|
4598
4908
|
formatDiffJSON,
|
|
4599
4909
|
formatCostsTerminal,
|
|
4600
4910
|
formatCostsJSON,
|
|
4911
|
+
flowFromRow,
|
|
4601
4912
|
executeTool,
|
|
4602
4913
|
ensureProject,
|
|
4603
4914
|
ensureDir,
|
|
@@ -4608,6 +4919,7 @@ export {
|
|
|
4608
4919
|
deleteSchedule,
|
|
4609
4920
|
deleteScenario,
|
|
4610
4921
|
deleteRun,
|
|
4922
|
+
deleteFlow,
|
|
4611
4923
|
deleteAuthPreset,
|
|
4612
4924
|
createWebhook,
|
|
4613
4925
|
createScreenshot,
|
|
@@ -4616,6 +4928,7 @@ export {
|
|
|
4616
4928
|
createRun,
|
|
4617
4929
|
createResult,
|
|
4618
4930
|
createProject,
|
|
4931
|
+
createFlow,
|
|
4619
4932
|
createClient,
|
|
4620
4933
|
createAuthPreset,
|
|
4621
4934
|
connectToTodos,
|
|
@@ -4623,6 +4936,7 @@ export {
|
|
|
4623
4936
|
closeBrowser,
|
|
4624
4937
|
checkBudget,
|
|
4625
4938
|
agentFromRow,
|
|
4939
|
+
addDependency,
|
|
4626
4940
|
VersionConflictError,
|
|
4627
4941
|
TodosConnectionError,
|
|
4628
4942
|
Screenshotter,
|
|
@@ -4634,6 +4948,8 @@ export {
|
|
|
4634
4948
|
ResultNotFoundError,
|
|
4635
4949
|
ProjectNotFoundError,
|
|
4636
4950
|
MODEL_MAP,
|
|
4951
|
+
FlowNotFoundError,
|
|
4952
|
+
DependencyCycleError,
|
|
4637
4953
|
BrowserPool,
|
|
4638
4954
|
BrowserError,
|
|
4639
4955
|
BROWSER_TOOLS,
|