@hasna/testers 0.0.12 → 0.0.14
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/dashboard/dist/assets/{index-DyXKnBM8.css → index-PT-52SEY.css} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +3959 -2496
- package/dist/db/agents.d.ts +2 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/scan-issues.d.ts +29 -0
- package/dist/db/scan-issues.d.ts.map +1 -0
- package/dist/index.js +239 -117
- package/dist/lib/affected.d.ts +23 -0
- package/dist/lib/affected.d.ts.map +1 -0
- package/dist/lib/failure-pipeline.d.ts +20 -0
- package/dist/lib/failure-pipeline.d.ts.map +1 -0
- package/dist/lib/git-watch.d.ts +16 -0
- package/dist/lib/git-watch.d.ts.map +1 -0
- package/dist/lib/health-scan.d.ts +22 -0
- package/dist/lib/health-scan.d.ts.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/scanners/console.d.ts +12 -0
- package/dist/lib/scanners/console.d.ts.map +1 -0
- package/dist/lib/scanners/links.d.ts +12 -0
- package/dist/lib/scanners/links.d.ts.map +1 -0
- package/dist/lib/scanners/network.d.ts +15 -0
- package/dist/lib/scanners/network.d.ts.map +1 -0
- package/dist/lib/scanners/performance.d.ts +19 -0
- package/dist/lib/scanners/performance.d.ts.map +1 -0
- package/dist/mcp/index.js +1428 -400
- package/dist/server/index.js +161 -13
- package/dist/types/index.d.ts +54 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -3
- package/dist/mcp/index.d.ts.map +0 -1
- /package/dashboard/dist/assets/{index-jNG_Nd_Q.js → index-FZ9gzLaz.js} +0 -0
package/dist/db/agents.d.ts
CHANGED
|
@@ -7,4 +7,6 @@ export declare function registerAgent(input: {
|
|
|
7
7
|
export declare function getAgent(id: string): Agent | null;
|
|
8
8
|
export declare function getAgentByName(name: string): Agent | null;
|
|
9
9
|
export declare function listAgents(): Agent[];
|
|
10
|
+
export declare function heartbeatAgent(id: string): Agent | null;
|
|
11
|
+
export declare function setAgentFocus(id: string, scenarioId: string | null): Agent | null;
|
|
10
12
|
//# sourceMappingURL=agents.d.ts.map
|
package/dist/db/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EAGX,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,KAAK,CA2BR;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIzD;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAIpC"}
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EAGX,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,KAAK,CA2BR;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIzD;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAIpC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAKvD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAWjF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,iBAAS,GAAG,IAAI,MAAM,CAErB;AAED,iBAAS,IAAI,IAAI,MAAM,CAEtB;AAED,iBAAS,SAAS,IAAI,MAAM,CAE3B;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAOtC,iBAAS,GAAG,IAAI,MAAM,CAErB;AAED,iBAAS,IAAI,IAAI,MAAM,CAEtB;AAED,iBAAS,SAAS,IAAI,MAAM,CAE3B;AAiRD,wBAAgB,WAAW,IAAI,QAAQ,CAwBtC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAgBpC;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAOf;AAED,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type PersistedScanIssue, type ScanIssue } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Derive a stable fingerprint from an issue.
|
|
4
|
+
* Normalises the page URL to its pathname (ignores query/fragment) so the same
|
|
5
|
+
* error on foo.com/page?x=1 and foo.com/page?x=2 is treated as the same issue.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fingerprintIssue(issue: ScanIssue): string;
|
|
8
|
+
export type UpsertOutcome = "new" | "existing" | "regressed";
|
|
9
|
+
export interface UpsertResult {
|
|
10
|
+
issue: PersistedScanIssue;
|
|
11
|
+
outcome: UpsertOutcome;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Upsert a scan issue by fingerprint:
|
|
15
|
+
* - New (never seen): insert with status=open
|
|
16
|
+
* - Known open: bump occurrence_count + last_seen_at
|
|
17
|
+
* - Known resolved: mark as regressed (status=regressed, increment count)
|
|
18
|
+
*/
|
|
19
|
+
export declare function upsertScanIssue(issue: ScanIssue, projectId?: string): UpsertResult;
|
|
20
|
+
export declare function resolveScanIssue(id: string): boolean;
|
|
21
|
+
export declare function setScanIssueTodoTaskId(id: string, todoTaskId: string): void;
|
|
22
|
+
export declare function listScanIssues(opts?: {
|
|
23
|
+
status?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
projectId?: string;
|
|
26
|
+
limit?: number;
|
|
27
|
+
}): PersistedScanIssue[];
|
|
28
|
+
export declare function getScanIssue(id: string): PersistedScanIssue | null;
|
|
29
|
+
//# sourceMappingURL=scan-issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-issues.d.ts","sourceRoot":"","sources":["../../src/db/scan-issues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAuC,MAAM,mBAAmB,CAAC;AAKjH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAezD;AAID,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,SAAS,EAChB,SAAS,CAAC,EAAE,MAAM,GACjB,YAAY,CAoDd;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAG3E;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACX,GAAG,kBAAkB,EAAE,CAc5B;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAIlE"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -295,6 +299,7 @@ function resetDatabase() {
|
|
|
295
299
|
database.exec("DELETE FROM runs");
|
|
296
300
|
database.exec("DELETE FROM scenarios");
|
|
297
301
|
database.exec("DELETE FROM agents");
|
|
302
|
+
database.exec("DELETE FROM scan_issues");
|
|
298
303
|
database.exec("DELETE FROM projects");
|
|
299
304
|
}
|
|
300
305
|
function resolvePartialId(table, partialId) {
|
|
@@ -502,6 +507,29 @@ var init_database = __esm(() => {
|
|
|
502
507
|
`,
|
|
503
508
|
`
|
|
504
509
|
ALTER TABLE runs ADD COLUMN is_baseline INTEGER NOT NULL DEFAULT 0;
|
|
510
|
+
`,
|
|
511
|
+
`
|
|
512
|
+
CREATE TABLE IF NOT EXISTS scan_issues (
|
|
513
|
+
id TEXT PRIMARY KEY,
|
|
514
|
+
fingerprint TEXT NOT NULL UNIQUE,
|
|
515
|
+
type TEXT NOT NULL,
|
|
516
|
+
severity TEXT NOT NULL DEFAULT 'medium',
|
|
517
|
+
page_url TEXT NOT NULL,
|
|
518
|
+
message TEXT NOT NULL,
|
|
519
|
+
detail TEXT,
|
|
520
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
521
|
+
occurrence_count INTEGER NOT NULL DEFAULT 1,
|
|
522
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
523
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
524
|
+
resolved_at TEXT,
|
|
525
|
+
todo_task_id TEXT,
|
|
526
|
+
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
CREATE INDEX IF NOT EXISTS idx_scan_issues_fingerprint ON scan_issues(fingerprint);
|
|
530
|
+
CREATE INDEX IF NOT EXISTS idx_scan_issues_status ON scan_issues(status);
|
|
531
|
+
CREATE INDEX IF NOT EXISTS idx_scan_issues_type ON scan_issues(type);
|
|
532
|
+
CREATE INDEX IF NOT EXISTS idx_scan_issues_project ON scan_issues(project_id);
|
|
505
533
|
`
|
|
506
534
|
];
|
|
507
535
|
});
|
|
@@ -2722,6 +2750,214 @@ async function pushFailedRunToLogs(run, failedResults, scenarios) {
|
|
|
2722
2750
|
} catch {}
|
|
2723
2751
|
}
|
|
2724
2752
|
|
|
2753
|
+
// src/lib/todos-connector.ts
|
|
2754
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
2755
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2756
|
+
import { join as join4 } from "path";
|
|
2757
|
+
import { homedir as homedir4 } from "os";
|
|
2758
|
+
init_types();
|
|
2759
|
+
function resolveTodosDbPath() {
|
|
2760
|
+
const envPath = process.env["TODOS_DB_PATH"];
|
|
2761
|
+
if (envPath)
|
|
2762
|
+
return envPath;
|
|
2763
|
+
return join4(homedir4(), ".todos", "todos.db");
|
|
2764
|
+
}
|
|
2765
|
+
function connectToTodos() {
|
|
2766
|
+
const dbPath = resolveTodosDbPath();
|
|
2767
|
+
if (!existsSync4(dbPath)) {
|
|
2768
|
+
throw new TodosConnectionError(`Todos database not found at ${dbPath}. Install @hasna/todos or set TODOS_DB_PATH.`);
|
|
2769
|
+
}
|
|
2770
|
+
const db2 = new Database2(dbPath, { readonly: true });
|
|
2771
|
+
db2.exec("PRAGMA foreign_keys = ON");
|
|
2772
|
+
return db2;
|
|
2773
|
+
}
|
|
2774
|
+
function pullTasks(options = {}) {
|
|
2775
|
+
const db2 = connectToTodos();
|
|
2776
|
+
try {
|
|
2777
|
+
let query = "SELECT id, short_id, title, description, status, priority, tags, project_id FROM tasks WHERE 1=1";
|
|
2778
|
+
const params = [];
|
|
2779
|
+
if (options.status) {
|
|
2780
|
+
query += " AND status = ?";
|
|
2781
|
+
params.push(options.status);
|
|
2782
|
+
} else {
|
|
2783
|
+
query += " AND status IN ('pending', 'in_progress')";
|
|
2784
|
+
}
|
|
2785
|
+
if (options.priority) {
|
|
2786
|
+
query += " AND priority = ?";
|
|
2787
|
+
params.push(options.priority);
|
|
2788
|
+
}
|
|
2789
|
+
if (options.projectName) {
|
|
2790
|
+
const project = db2.query("SELECT id FROM projects WHERE name = ?").get(options.projectName);
|
|
2791
|
+
if (project) {
|
|
2792
|
+
query += " AND project_id = ?";
|
|
2793
|
+
params.push(project.id);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
query += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END";
|
|
2797
|
+
const tasks = db2.query(query).all(...params);
|
|
2798
|
+
if (options.tags && options.tags.length > 0) {
|
|
2799
|
+
return tasks.filter((task) => {
|
|
2800
|
+
const taskTags = JSON.parse(task.tags || "[]");
|
|
2801
|
+
return options.tags.some((tag) => taskTags.includes(tag));
|
|
2802
|
+
});
|
|
2803
|
+
}
|
|
2804
|
+
return tasks;
|
|
2805
|
+
} finally {
|
|
2806
|
+
db2.close();
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
function taskToScenarioInput(task, projectId) {
|
|
2810
|
+
const tags = JSON.parse(task.tags || "[]");
|
|
2811
|
+
const priority = ["low", "medium", "high", "critical"].includes(task.priority) ? task.priority : "medium";
|
|
2812
|
+
const steps = [];
|
|
2813
|
+
if (task.description) {
|
|
2814
|
+
const lines = task.description.split(`
|
|
2815
|
+
`);
|
|
2816
|
+
for (const line of lines) {
|
|
2817
|
+
const match = line.match(/^\s*\d+[\.\)]\s*(.+)/);
|
|
2818
|
+
if (match?.[1]) {
|
|
2819
|
+
steps.push(match[1].trim());
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
return {
|
|
2824
|
+
name: task.title.replace(/^(OPE\d+-\d+|[A-Z]+-\d+):\s*/, ""),
|
|
2825
|
+
description: task.description || task.title,
|
|
2826
|
+
steps,
|
|
2827
|
+
tags,
|
|
2828
|
+
priority,
|
|
2829
|
+
projectId,
|
|
2830
|
+
metadata: { todosTaskId: task.id, todosShortId: task.short_id }
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
function importFromTodos(options = {}) {
|
|
2834
|
+
const tasks = pullTasks({
|
|
2835
|
+
projectName: options.projectName,
|
|
2836
|
+
tags: options.tags ?? ["qa", "test", "testing"],
|
|
2837
|
+
priority: options.priority
|
|
2838
|
+
});
|
|
2839
|
+
const existing = listScenarios({ projectId: options.projectId });
|
|
2840
|
+
const existingTodoIds = new Set(existing.filter((s) => s.metadata?.todosTaskId).map((s) => s.metadata.todosTaskId));
|
|
2841
|
+
let imported = 0;
|
|
2842
|
+
let skipped = 0;
|
|
2843
|
+
for (const task of tasks) {
|
|
2844
|
+
if (existingTodoIds.has(task.id)) {
|
|
2845
|
+
skipped++;
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
const input = taskToScenarioInput(task, options.projectId);
|
|
2849
|
+
createScenario(input);
|
|
2850
|
+
imported++;
|
|
2851
|
+
}
|
|
2852
|
+
return { imported, skipped };
|
|
2853
|
+
}
|
|
2854
|
+
function markTodoDone(taskId) {
|
|
2855
|
+
const dbPath = resolveTodosDbPath();
|
|
2856
|
+
if (!existsSync4(dbPath))
|
|
2857
|
+
return false;
|
|
2858
|
+
const db2 = new Database2(dbPath);
|
|
2859
|
+
try {
|
|
2860
|
+
const task = db2.query("SELECT id, version FROM tasks WHERE id LIKE ? || '%'").get(taskId);
|
|
2861
|
+
if (!task)
|
|
2862
|
+
return false;
|
|
2863
|
+
db2.query("UPDATE tasks SET status = 'completed', completed_at = datetime('now'), version = version + 1, updated_at = datetime('now') WHERE id = ? AND version = ?").run(task.id, task.version);
|
|
2864
|
+
return true;
|
|
2865
|
+
} finally {
|
|
2866
|
+
db2.close();
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
// src/lib/failure-pipeline.ts
|
|
2871
|
+
async function createFailureTasks(run, failedResults, scenarios) {
|
|
2872
|
+
if (failedResults.length === 0)
|
|
2873
|
+
return { created: 0, skipped: 0 };
|
|
2874
|
+
const projectId = process.env["TESTERS_TODOS_PROJECT_ID"];
|
|
2875
|
+
if (!projectId)
|
|
2876
|
+
return { created: 0, skipped: 0 };
|
|
2877
|
+
let db2 = null;
|
|
2878
|
+
try {
|
|
2879
|
+
db2 = connectToTodos();
|
|
2880
|
+
} catch {
|
|
2881
|
+
return { created: 0, skipped: 0 };
|
|
2882
|
+
}
|
|
2883
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
2884
|
+
let created = 0;
|
|
2885
|
+
let skipped = 0;
|
|
2886
|
+
try {
|
|
2887
|
+
for (const result of failedResults) {
|
|
2888
|
+
const scenario = scenarioMap.get(result.scenarioId);
|
|
2889
|
+
const title = `BUG: [testers] ${scenario?.name ?? result.scenarioId} failed`;
|
|
2890
|
+
const existing = db2.query("SELECT id FROM tasks WHERE title = ? AND status NOT IN ('completed', 'cancelled') LIMIT 1").get(title);
|
|
2891
|
+
if (existing) {
|
|
2892
|
+
skipped++;
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
const id = crypto.randomUUID();
|
|
2896
|
+
const now2 = new Date().toISOString();
|
|
2897
|
+
const description = [
|
|
2898
|
+
`Test failure detected by open-testers.`,
|
|
2899
|
+
``,
|
|
2900
|
+
`**Run:** ${run.id}`,
|
|
2901
|
+
`**URL:** ${run.url}`,
|
|
2902
|
+
`**Scenario:** ${scenario?.name ?? result.scenarioId}`,
|
|
2903
|
+
`**Status:** ${result.status}`,
|
|
2904
|
+
result.error ? `**Error:** ${result.error}` : null,
|
|
2905
|
+
result.reasoning ? `**Reasoning:** ${result.reasoning.slice(0, 500)}` : null,
|
|
2906
|
+
`**Duration:** ${result.durationMs ? `${(result.durationMs / 1000).toFixed(1)}s` : "N/A"}`,
|
|
2907
|
+
`**Tokens:** ${result.tokensUsed ?? 0}`
|
|
2908
|
+
].filter(Boolean).join(`
|
|
2909
|
+
`);
|
|
2910
|
+
try {
|
|
2911
|
+
db2.query(`
|
|
2912
|
+
INSERT INTO tasks (id, short_id, title, description, status, priority, tags, project_id, version, created_at, updated_at)
|
|
2913
|
+
VALUES (?, ?, ?, ?, 'pending', 'high', ?, ?, 1, ?, ?)
|
|
2914
|
+
`).run(id, `BUG-${id.slice(0, 6)}`, title, description, JSON.stringify(["bug", "testers", "auto-created"]), projectId, now2, now2);
|
|
2915
|
+
created++;
|
|
2916
|
+
} catch {
|
|
2917
|
+
skipped++;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
} finally {
|
|
2921
|
+
db2.close();
|
|
2922
|
+
}
|
|
2923
|
+
return { created, skipped };
|
|
2924
|
+
}
|
|
2925
|
+
async function notifyFailureToConversations(run, failedResults, scenarios) {
|
|
2926
|
+
const baseUrl = process.env["TESTERS_CONVERSATIONS_URL"];
|
|
2927
|
+
const space = process.env["TESTERS_CONVERSATIONS_SPACE"];
|
|
2928
|
+
if (!baseUrl || !space)
|
|
2929
|
+
return;
|
|
2930
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
2931
|
+
const total = run.total;
|
|
2932
|
+
const failedCount = failedResults.length;
|
|
2933
|
+
const passedCount = run.passed;
|
|
2934
|
+
const failureLines = failedResults.slice(0, 5).map((r) => {
|
|
2935
|
+
const name = scenarioMap.get(r.scenarioId)?.name ?? r.scenarioId;
|
|
2936
|
+
const err = r.error ? ` \u2014 ${r.error.slice(0, 120)}` : "";
|
|
2937
|
+
return ` \u274C ${name}${err}`;
|
|
2938
|
+
});
|
|
2939
|
+
const extra = failedResults.length > 5 ? ` \u2026 and ${failedResults.length - 5} more` : "";
|
|
2940
|
+
const message = [
|
|
2941
|
+
`\uD83D\uDEA8 **Testers run failed** \u2014 ${failedCount}/${total} scenarios failed`,
|
|
2942
|
+
``,
|
|
2943
|
+
`**URL:** ${run.url}`,
|
|
2944
|
+
`**Run ID:** \`${run.id}\``,
|
|
2945
|
+
`**Pass rate:** ${passedCount}/${total}`,
|
|
2946
|
+
``,
|
|
2947
|
+
`**Failures:**`,
|
|
2948
|
+
...failureLines,
|
|
2949
|
+
extra
|
|
2950
|
+
].filter((l) => l !== "").join(`
|
|
2951
|
+
`);
|
|
2952
|
+
try {
|
|
2953
|
+
await fetch(`${baseUrl.replace(/\/$/, "")}/api/spaces/${encodeURIComponent(space)}/messages`, {
|
|
2954
|
+
method: "POST",
|
|
2955
|
+
headers: { "Content-Type": "application/json" },
|
|
2956
|
+
body: JSON.stringify({ content: message, from: "testers" })
|
|
2957
|
+
});
|
|
2958
|
+
} catch {}
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2725
2961
|
// src/lib/runner.ts
|
|
2726
2962
|
var eventHandler = null;
|
|
2727
2963
|
function onRunEvent(handler) {
|
|
@@ -2958,6 +3194,8 @@ async function runBatch(scenarios, options) {
|
|
|
2958
3194
|
if (finalRun.status === "failed") {
|
|
2959
3195
|
const failedResults = results.filter((r) => r.status === "failed" || r.status === "error");
|
|
2960
3196
|
pushFailedRunToLogs(finalRun, failedResults, scenarios).catch(() => {});
|
|
3197
|
+
createFailureTasks(finalRun, failedResults, scenarios).catch(() => {});
|
|
3198
|
+
notifyFailureToConversations(finalRun, failedResults, scenarios).catch(() => {});
|
|
2961
3199
|
}
|
|
2962
3200
|
return { run: finalRun, results };
|
|
2963
3201
|
}
|
|
@@ -3808,122 +4046,6 @@ function formatResultDetail(result, screenshots) {
|
|
|
3808
4046
|
return lines.join(`
|
|
3809
4047
|
`);
|
|
3810
4048
|
}
|
|
3811
|
-
// src/lib/todos-connector.ts
|
|
3812
|
-
import { Database as Database2 } from "bun:sqlite";
|
|
3813
|
-
import { existsSync as existsSync4 } from "fs";
|
|
3814
|
-
import { join as join4 } from "path";
|
|
3815
|
-
import { homedir as homedir4 } from "os";
|
|
3816
|
-
init_types();
|
|
3817
|
-
function resolveTodosDbPath() {
|
|
3818
|
-
const envPath = process.env["TODOS_DB_PATH"];
|
|
3819
|
-
if (envPath)
|
|
3820
|
-
return envPath;
|
|
3821
|
-
return join4(homedir4(), ".todos", "todos.db");
|
|
3822
|
-
}
|
|
3823
|
-
function connectToTodos() {
|
|
3824
|
-
const dbPath = resolveTodosDbPath();
|
|
3825
|
-
if (!existsSync4(dbPath)) {
|
|
3826
|
-
throw new TodosConnectionError(`Todos database not found at ${dbPath}. Install @hasna/todos or set TODOS_DB_PATH.`);
|
|
3827
|
-
}
|
|
3828
|
-
const db2 = new Database2(dbPath, { readonly: true });
|
|
3829
|
-
db2.exec("PRAGMA foreign_keys = ON");
|
|
3830
|
-
return db2;
|
|
3831
|
-
}
|
|
3832
|
-
function pullTasks(options = {}) {
|
|
3833
|
-
const db2 = connectToTodos();
|
|
3834
|
-
try {
|
|
3835
|
-
let query = "SELECT id, short_id, title, description, status, priority, tags, project_id FROM tasks WHERE 1=1";
|
|
3836
|
-
const params = [];
|
|
3837
|
-
if (options.status) {
|
|
3838
|
-
query += " AND status = ?";
|
|
3839
|
-
params.push(options.status);
|
|
3840
|
-
} else {
|
|
3841
|
-
query += " AND status IN ('pending', 'in_progress')";
|
|
3842
|
-
}
|
|
3843
|
-
if (options.priority) {
|
|
3844
|
-
query += " AND priority = ?";
|
|
3845
|
-
params.push(options.priority);
|
|
3846
|
-
}
|
|
3847
|
-
if (options.projectName) {
|
|
3848
|
-
const project = db2.query("SELECT id FROM projects WHERE name = ?").get(options.projectName);
|
|
3849
|
-
if (project) {
|
|
3850
|
-
query += " AND project_id = ?";
|
|
3851
|
-
params.push(project.id);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
query += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END";
|
|
3855
|
-
const tasks = db2.query(query).all(...params);
|
|
3856
|
-
if (options.tags && options.tags.length > 0) {
|
|
3857
|
-
return tasks.filter((task) => {
|
|
3858
|
-
const taskTags = JSON.parse(task.tags || "[]");
|
|
3859
|
-
return options.tags.some((tag) => taskTags.includes(tag));
|
|
3860
|
-
});
|
|
3861
|
-
}
|
|
3862
|
-
return tasks;
|
|
3863
|
-
} finally {
|
|
3864
|
-
db2.close();
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
function taskToScenarioInput(task, projectId) {
|
|
3868
|
-
const tags = JSON.parse(task.tags || "[]");
|
|
3869
|
-
const priority = ["low", "medium", "high", "critical"].includes(task.priority) ? task.priority : "medium";
|
|
3870
|
-
const steps = [];
|
|
3871
|
-
if (task.description) {
|
|
3872
|
-
const lines = task.description.split(`
|
|
3873
|
-
`);
|
|
3874
|
-
for (const line of lines) {
|
|
3875
|
-
const match = line.match(/^\s*\d+[\.\)]\s*(.+)/);
|
|
3876
|
-
if (match?.[1]) {
|
|
3877
|
-
steps.push(match[1].trim());
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
return {
|
|
3882
|
-
name: task.title.replace(/^(OPE\d+-\d+|[A-Z]+-\d+):\s*/, ""),
|
|
3883
|
-
description: task.description || task.title,
|
|
3884
|
-
steps,
|
|
3885
|
-
tags,
|
|
3886
|
-
priority,
|
|
3887
|
-
projectId,
|
|
3888
|
-
metadata: { todosTaskId: task.id, todosShortId: task.short_id }
|
|
3889
|
-
};
|
|
3890
|
-
}
|
|
3891
|
-
function importFromTodos(options = {}) {
|
|
3892
|
-
const tasks = pullTasks({
|
|
3893
|
-
projectName: options.projectName,
|
|
3894
|
-
tags: options.tags ?? ["qa", "test", "testing"],
|
|
3895
|
-
priority: options.priority
|
|
3896
|
-
});
|
|
3897
|
-
const existing = listScenarios({ projectId: options.projectId });
|
|
3898
|
-
const existingTodoIds = new Set(existing.filter((s) => s.metadata?.todosTaskId).map((s) => s.metadata.todosTaskId));
|
|
3899
|
-
let imported = 0;
|
|
3900
|
-
let skipped = 0;
|
|
3901
|
-
for (const task of tasks) {
|
|
3902
|
-
if (existingTodoIds.has(task.id)) {
|
|
3903
|
-
skipped++;
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
const input = taskToScenarioInput(task, options.projectId);
|
|
3907
|
-
createScenario(input);
|
|
3908
|
-
imported++;
|
|
3909
|
-
}
|
|
3910
|
-
return { imported, skipped };
|
|
3911
|
-
}
|
|
3912
|
-
function markTodoDone(taskId) {
|
|
3913
|
-
const dbPath = resolveTodosDbPath();
|
|
3914
|
-
if (!existsSync4(dbPath))
|
|
3915
|
-
return false;
|
|
3916
|
-
const db2 = new Database2(dbPath);
|
|
3917
|
-
try {
|
|
3918
|
-
const task = db2.query("SELECT id, version FROM tasks WHERE id LIKE ? || '%'").get(taskId);
|
|
3919
|
-
if (!task)
|
|
3920
|
-
return false;
|
|
3921
|
-
db2.query("UPDATE tasks SET status = 'completed', completed_at = datetime('now'), version = version + 1, updated_at = datetime('now') WHERE id = ? AND version = ?").run(task.id, task.version);
|
|
3922
|
-
return true;
|
|
3923
|
-
} finally {
|
|
3924
|
-
db2.close();
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
4049
|
// src/lib/scheduler.ts
|
|
3928
4050
|
init_types();
|
|
3929
4051
|
function parseCronField(field, min, max) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Scenario } from "../types/index.js";
|
|
2
|
+
export interface FileMapping {
|
|
3
|
+
/** Glob pattern for source files (supports * and **) */
|
|
4
|
+
glob: string;
|
|
5
|
+
/** Tags of scenarios to run when this glob matches */
|
|
6
|
+
tags: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Given a set of changed file paths and all scenarios, return the scenarios
|
|
10
|
+
* relevant to those changes.
|
|
11
|
+
*
|
|
12
|
+
* Matching strategies (all applied, union of results):
|
|
13
|
+
* 1. Explicit glob → tag mappings: if a file matches a glob, scenarios with
|
|
14
|
+
* any of the mapped tags are included.
|
|
15
|
+
* 2. targetPath keyword: path segments of scenario.targetPath matched against
|
|
16
|
+
* file path components.
|
|
17
|
+
* 3. Tag keywords: scenario tags matched as substrings of file paths (>2 chars).
|
|
18
|
+
* 4. Name keywords: words in the scenario name matched against file paths (>3 chars).
|
|
19
|
+
*
|
|
20
|
+
* If filePaths is empty, all scenarios are returned (run everything).
|
|
21
|
+
*/
|
|
22
|
+
export declare function matchFilesToScenarios(filePaths: string[], scenarios: Scenario[], mappings?: FileMapping[]): Scenario[];
|
|
23
|
+
//# sourceMappingURL=affected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"affected.d.ts","sourceRoot":"","sources":["../../src/lib/affected.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,GAAE,WAAW,EAAO,GAC3B,QAAQ,EAAE,CA+DZ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Run, Result, Scenario } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Auto-create a todo task for each failed scenario in a run.
|
|
4
|
+
* Uses the todos DB directly (like todos-connector.ts does).
|
|
5
|
+
* No-op if TODOS_DB_PATH is not set and ~/.todos/todos.db does not exist.
|
|
6
|
+
* Controlled by TESTERS_TODOS_PROJECT_ID env var (which project to create tasks in).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createFailureTasks(run: Run, failedResults: Result[], scenarios: Scenario[]): Promise<{
|
|
9
|
+
created: number;
|
|
10
|
+
skipped: number;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Post a failure notification to a conversations space.
|
|
14
|
+
* Controlled by:
|
|
15
|
+
* TESTERS_CONVERSATIONS_URL — base URL of the conversations service
|
|
16
|
+
* TESTERS_CONVERSATIONS_SPACE — space name/slug to post to
|
|
17
|
+
* No-op if either env var is missing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function notifyFailureToConversations(run: Run, failedResults: Result[], scenarios: Scenario[]): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=failure-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/failure-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAK/D;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EAAE,EACvB,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyE/C;AAID;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EAAE,EACvB,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FileMapping } from "./affected.js";
|
|
2
|
+
import type { RunOptions } from "./runner.js";
|
|
3
|
+
export interface GitWatchOptions extends Omit<RunOptions, "url"> {
|
|
4
|
+
url: string;
|
|
5
|
+
dir?: string;
|
|
6
|
+
pollIntervalMs?: number;
|
|
7
|
+
mappings?: FileMapping[];
|
|
8
|
+
projectId?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Poll git for new commits and run affected scenarios when changes land.
|
|
13
|
+
* Designed for use in CI or as a long-running watcher.
|
|
14
|
+
*/
|
|
15
|
+
export declare function startGitWatcher(options: GitWatchOptions): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=git-watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-watch.d.ts","sourceRoot":"","sources":["../../src/lib/git-watch.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAwBD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiF7E"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScanResult } from "../types/index.js";
|
|
2
|
+
export interface HealthScanOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
pages?: string[];
|
|
5
|
+
projectId?: string;
|
|
6
|
+
headed?: boolean;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
scanners?: ("console" | "network" | "links" | "performance")[];
|
|
9
|
+
maxPages?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface HealthScanSummary {
|
|
12
|
+
url: string;
|
|
13
|
+
scannedAt: string;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
totalIssues: number;
|
|
16
|
+
newIssues: number;
|
|
17
|
+
regressedIssues: number;
|
|
18
|
+
existingIssues: number;
|
|
19
|
+
results: ScanResult[];
|
|
20
|
+
}
|
|
21
|
+
export declare function runHealthScan(options: HealthScanOptions): Promise<HealthScanSummary>;
|
|
22
|
+
//# sourceMappingURL=health-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-scan.d.ts","sourceRoot":"","sources":["../../src/lib/health-scan.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,mBAAmB,CAAC;AAI/D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAID,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0D1F"}
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAc/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;CACtC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EACA,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,GACf,0BAA0B,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AA+BD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAiHjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAwI1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAmF1C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScanResult } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Visit each page and collect JS/React/unhandled errors from the browser console.
|
|
4
|
+
* Captures: console.error(), uncaught exceptions, unhandled promise rejections.
|
|
5
|
+
*/
|
|
6
|
+
export declare function scanConsoleErrors(options: {
|
|
7
|
+
url: string;
|
|
8
|
+
pages?: string[];
|
|
9
|
+
headed?: boolean;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}): Promise<ScanResult>;
|
|
12
|
+
//# sourceMappingURL=console.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../../src/lib/scanners/console.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAQlE;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,UAAU,CAAC,CAqEtB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScanResult } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Crawl the app starting from rootUrl, follow all internal links,
|
|
4
|
+
* and flag any that return 404 or redirect loops.
|
|
5
|
+
*/
|
|
6
|
+
export declare function scanBrokenLinks(options: {
|
|
7
|
+
url: string;
|
|
8
|
+
maxPages?: number;
|
|
9
|
+
headed?: boolean;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}): Promise<ScanResult>;
|
|
12
|
+
//# sourceMappingURL=links.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/lib/scanners/links.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElE;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0FtB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ScanResult } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Visit each page and intercept network requests, flagging:
|
|
4
|
+
* - HTTP 5xx (server errors)
|
|
5
|
+
* - HTTP 4xx on API routes
|
|
6
|
+
* - CORS errors
|
|
7
|
+
* - Request failures (net::ERR_*)
|
|
8
|
+
*/
|
|
9
|
+
export declare function scanNetworkErrors(options: {
|
|
10
|
+
url: string;
|
|
11
|
+
pages?: string[];
|
|
12
|
+
headed?: boolean;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}): Promise<ScanResult>;
|
|
15
|
+
//# sourceMappingURL=network.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../src/lib/scanners/network.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAWlE;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0EtB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ScanResult } from "../../types/index.js";
|
|
2
|
+
interface PerfThresholds {
|
|
3
|
+
loadTimeMs: number;
|
|
4
|
+
domContentLoadedMs: number;
|
|
5
|
+
lcpMs: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Visit each page and collect Web Performance API metrics.
|
|
9
|
+
* Flags pages that exceed configurable thresholds.
|
|
10
|
+
*/
|
|
11
|
+
export declare function scanPerformance(options: {
|
|
12
|
+
url: string;
|
|
13
|
+
pages?: string[];
|
|
14
|
+
headed?: boolean;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
thresholds?: Partial<PerfThresholds>;
|
|
17
|
+
}): Promise<ScanResult>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=performance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../../src/lib/scanners/performance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAYlE,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAQD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CACtC,GAAG,OAAO,CAAC,UAAU,CAAC,CA2GtB"}
|