@hasna/testers 0.0.47 → 0.0.49
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 +651 -204
- package/dist/index.js +9 -0
- package/dist/lib/next-route-inventory.d.ts +55 -0
- package/dist/lib/next-route-inventory.d.ts.map +1 -0
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +10 -1
- package/dist/server/index.js +10 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -27025,12 +27025,60 @@ function listTestingWorkflows(filter) {
|
|
|
27025
27025
|
const rows = db2.query(`SELECT * FROM testing_workflows${where} ORDER BY created_at DESC`).all(...params);
|
|
27026
27026
|
return rows.map(workflowFromRow);
|
|
27027
27027
|
}
|
|
27028
|
+
function updateTestingWorkflow(id, input) {
|
|
27029
|
+
const existing = getTestingWorkflow(id);
|
|
27030
|
+
if (!existing)
|
|
27031
|
+
throw new Error(`Testing workflow not found: ${id}`);
|
|
27032
|
+
const fields = [];
|
|
27033
|
+
const values = [];
|
|
27034
|
+
if (input.name !== undefined) {
|
|
27035
|
+
fields.push("name = ?");
|
|
27036
|
+
values.push(input.name);
|
|
27037
|
+
}
|
|
27038
|
+
if (input.description !== undefined) {
|
|
27039
|
+
fields.push("description = ?");
|
|
27040
|
+
values.push(input.description);
|
|
27041
|
+
}
|
|
27042
|
+
if (input.scenarioFilter !== undefined) {
|
|
27043
|
+
fields.push("scenario_filter = ?");
|
|
27044
|
+
values.push(JSON.stringify(normalizeFilter(input.scenarioFilter)));
|
|
27045
|
+
}
|
|
27046
|
+
if (input.personaIds !== undefined) {
|
|
27047
|
+
fields.push("persona_ids = ?");
|
|
27048
|
+
values.push(JSON.stringify(input.personaIds));
|
|
27049
|
+
}
|
|
27050
|
+
if (input.goal !== undefined) {
|
|
27051
|
+
fields.push("goal = ?");
|
|
27052
|
+
values.push(JSON.stringify(normalizeGoal(input.goal)));
|
|
27053
|
+
}
|
|
27054
|
+
if (input.execution !== undefined) {
|
|
27055
|
+
fields.push("execution = ?");
|
|
27056
|
+
values.push(JSON.stringify(normalizeExecution(input.execution)));
|
|
27057
|
+
}
|
|
27058
|
+
if (input.settings !== undefined) {
|
|
27059
|
+
fields.push("settings = ?");
|
|
27060
|
+
values.push(JSON.stringify(input.settings));
|
|
27061
|
+
}
|
|
27062
|
+
if (input.enabled !== undefined) {
|
|
27063
|
+
fields.push("enabled = ?");
|
|
27064
|
+
values.push(input.enabled ? 1 : 0);
|
|
27065
|
+
}
|
|
27066
|
+
if (fields.length === 0)
|
|
27067
|
+
return existing;
|
|
27068
|
+
fields.push("updated_at = ?");
|
|
27069
|
+
values.push(now(), existing.id);
|
|
27070
|
+
db2().query(`UPDATE testing_workflows SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
27071
|
+
return getTestingWorkflow(existing.id);
|
|
27072
|
+
}
|
|
27028
27073
|
function deleteTestingWorkflow(id) {
|
|
27029
27074
|
const existing = getTestingWorkflow(id);
|
|
27030
27075
|
if (!existing)
|
|
27031
27076
|
return false;
|
|
27032
27077
|
return getDatabase().query("DELETE FROM testing_workflows WHERE id = ?").run(existing.id).changes > 0;
|
|
27033
27078
|
}
|
|
27079
|
+
function db2() {
|
|
27080
|
+
return getDatabase();
|
|
27081
|
+
}
|
|
27034
27082
|
var DEFAULT_EXECUTION;
|
|
27035
27083
|
var init_workflows = __esm(() => {
|
|
27036
27084
|
init_types();
|
|
@@ -27293,6 +27341,13 @@ function resolveSandboxEnv(env) {
|
|
|
27293
27341
|
return;
|
|
27294
27342
|
const resolved = {};
|
|
27295
27343
|
for (const [key, value] of Object.entries(env)) {
|
|
27344
|
+
if (value.startsWith("$?")) {
|
|
27345
|
+
const optionalName = value.slice(2).trim();
|
|
27346
|
+
const optionalValue = optionalName ? process.env[optionalName] : undefined;
|
|
27347
|
+
if (optionalValue !== undefined)
|
|
27348
|
+
resolved[key] = optionalValue;
|
|
27349
|
+
continue;
|
|
27350
|
+
}
|
|
27296
27351
|
const resolvedValue = resolveCredential(value);
|
|
27297
27352
|
if (resolvedValue === null) {
|
|
27298
27353
|
throw new Error(`Missing sandbox env value for ${key}`);
|
|
@@ -27326,6 +27381,8 @@ var init_workflow_runner = __esm(() => {
|
|
|
27326
27381
|
".next",
|
|
27327
27382
|
".turbo",
|
|
27328
27383
|
".cache",
|
|
27384
|
+
".env",
|
|
27385
|
+
".env.*",
|
|
27329
27386
|
".venv",
|
|
27330
27387
|
"__pycache__"
|
|
27331
27388
|
];
|
|
@@ -27479,46 +27536,46 @@ __export(exports_sessions, {
|
|
|
27479
27536
|
countSessions: () => countSessions
|
|
27480
27537
|
});
|
|
27481
27538
|
function createSession(input) {
|
|
27482
|
-
const
|
|
27539
|
+
const db3 = getDatabase();
|
|
27483
27540
|
const id = input.sessionId ?? uuid();
|
|
27484
27541
|
const timestamp = now();
|
|
27485
|
-
|
|
27542
|
+
db3.query(`
|
|
27486
27543
|
INSERT INTO sessions (id, tab_id, url, title, entries, entry_count, error_count, console_count, nav_count, status, start_time, end_time, created_at)
|
|
27487
27544
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
27488
27545
|
`).run(id, input.tabId, input.url ?? null, input.title ?? null, input.entries, input.entryCount, input.errorCount ?? 0, input.consoleCount ?? 0, input.navCount ?? 0, input.status, input.startTime, input.endTime ?? null, timestamp);
|
|
27489
27546
|
return getSession(id);
|
|
27490
27547
|
}
|
|
27491
27548
|
function getSession(id) {
|
|
27492
|
-
const
|
|
27493
|
-
let row =
|
|
27549
|
+
const db3 = getDatabase();
|
|
27550
|
+
let row = db3.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
27494
27551
|
if (row)
|
|
27495
27552
|
return sessionFromRow(row);
|
|
27496
27553
|
const fullId = resolvePartialId("sessions", id);
|
|
27497
27554
|
if (fullId) {
|
|
27498
|
-
row =
|
|
27555
|
+
row = db3.query("SELECT * FROM sessions WHERE id = ?").get(fullId);
|
|
27499
27556
|
if (row)
|
|
27500
27557
|
return sessionFromRow(row);
|
|
27501
27558
|
}
|
|
27502
27559
|
return null;
|
|
27503
27560
|
}
|
|
27504
27561
|
function listSessions(limit = 50, offset = 0) {
|
|
27505
|
-
const
|
|
27506
|
-
const rows =
|
|
27562
|
+
const db3 = getDatabase();
|
|
27563
|
+
const rows = db3.query("SELECT * FROM sessions ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
27507
27564
|
return rows.map(sessionFromRow);
|
|
27508
27565
|
}
|
|
27509
27566
|
function deleteSession(id) {
|
|
27510
|
-
const
|
|
27511
|
-
const result =
|
|
27567
|
+
const db3 = getDatabase();
|
|
27568
|
+
const result = db3.query("DELETE FROM sessions WHERE id = ?").run(id);
|
|
27512
27569
|
return result.changes > 0;
|
|
27513
27570
|
}
|
|
27514
27571
|
function searchSessions(query, limit = 20) {
|
|
27515
|
-
const
|
|
27516
|
-
const rows =
|
|
27572
|
+
const db3 = getDatabase();
|
|
27573
|
+
const rows = db3.query("SELECT * FROM sessions WHERE url LIKE ? OR title LIKE ? ORDER BY created_at DESC LIMIT ?").all(`%${query}%`, `%${query}%`, limit);
|
|
27517
27574
|
return rows.map(sessionFromRow);
|
|
27518
27575
|
}
|
|
27519
27576
|
function countSessions() {
|
|
27520
|
-
const
|
|
27521
|
-
const row =
|
|
27577
|
+
const db3 = getDatabase();
|
|
27578
|
+
const row = db3.query("SELECT COUNT(*) as count FROM sessions").get();
|
|
27522
27579
|
return row.count;
|
|
27523
27580
|
}
|
|
27524
27581
|
function sessionFromRow(row) {
|
|
@@ -28046,8 +28103,8 @@ function customRandom(alphabet, defaultSize, getRandom) {
|
|
|
28046
28103
|
function customAlphabet(alphabet, size = 21) {
|
|
28047
28104
|
return customRandom(alphabet, size, random);
|
|
28048
28105
|
}
|
|
28049
|
-
function runMigrations(
|
|
28050
|
-
|
|
28106
|
+
function runMigrations(db3) {
|
|
28107
|
+
db3.run(`
|
|
28051
28108
|
CREATE TABLE IF NOT EXISTS _migrations (
|
|
28052
28109
|
id INTEGER PRIMARY KEY,
|
|
28053
28110
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
@@ -28055,9 +28112,9 @@ function runMigrations(db2) {
|
|
|
28055
28112
|
`);
|
|
28056
28113
|
for (let i = 0;i < MIGRATIONS2.length; i++) {
|
|
28057
28114
|
const migrationId = i + 1;
|
|
28058
|
-
const exists =
|
|
28115
|
+
const exists = db3.query("SELECT id FROM _migrations WHERE id = ?").get(migrationId);
|
|
28059
28116
|
if (!exists) {
|
|
28060
|
-
|
|
28117
|
+
db3.run(MIGRATIONS2[i]);
|
|
28061
28118
|
}
|
|
28062
28119
|
}
|
|
28063
28120
|
}
|
|
@@ -28082,11 +28139,11 @@ function ensureDir2(filePath) {
|
|
|
28082
28139
|
function getDatabase2(path) {
|
|
28083
28140
|
if (path) {
|
|
28084
28141
|
ensureDir2(path);
|
|
28085
|
-
const
|
|
28086
|
-
|
|
28087
|
-
|
|
28088
|
-
runMigrations(
|
|
28089
|
-
return
|
|
28142
|
+
const db3 = new Database4(path);
|
|
28143
|
+
db3.run("PRAGMA journal_mode=WAL");
|
|
28144
|
+
db3.run("PRAGMA foreign_keys=ON");
|
|
28145
|
+
runMigrations(db3);
|
|
28146
|
+
return db3;
|
|
28090
28147
|
}
|
|
28091
28148
|
if (!_db) {
|
|
28092
28149
|
const dbPath = getDbPath2();
|
|
@@ -28154,8 +28211,8 @@ function rowToWorkdir(row) {
|
|
|
28154
28211
|
function getMachineId() {
|
|
28155
28212
|
return process.env["HOSTNAME"] || hostname();
|
|
28156
28213
|
}
|
|
28157
|
-
function addWorkdir(input,
|
|
28158
|
-
const d =
|
|
28214
|
+
function addWorkdir(input, db3) {
|
|
28215
|
+
const d = db3 || getDatabase2();
|
|
28159
28216
|
const project = getProject2(input.project_id, d);
|
|
28160
28217
|
if (!project)
|
|
28161
28218
|
throw new ProjectNotFoundError(input.project_id);
|
|
@@ -28172,18 +28229,18 @@ function addWorkdir(input, db2) {
|
|
|
28172
28229
|
ON CONFLICT(project_id, path) DO UPDATE SET label = excluded.label, machine_id = excluded.machine_id`, [id, input.project_id, input.path, machineId, input.label ?? "main", isPrimary, ts]);
|
|
28173
28230
|
return getWorkdir(input.project_id, input.path, d);
|
|
28174
28231
|
}
|
|
28175
|
-
function getWorkdir(projectId, path,
|
|
28176
|
-
const d =
|
|
28232
|
+
function getWorkdir(projectId, path, db3) {
|
|
28233
|
+
const d = db3 || getDatabase2();
|
|
28177
28234
|
const row = d.query("SELECT * FROM project_workdirs WHERE project_id = ? AND path = ?").get(projectId, path);
|
|
28178
28235
|
return row ? rowToWorkdir(row) : null;
|
|
28179
28236
|
}
|
|
28180
|
-
function listWorkdirs(projectId,
|
|
28181
|
-
const d =
|
|
28237
|
+
function listWorkdirs(projectId, db3) {
|
|
28238
|
+
const d = db3 || getDatabase2();
|
|
28182
28239
|
const rows = d.query("SELECT * FROM project_workdirs WHERE project_id = ? ORDER BY is_primary DESC, created_at ASC").all(projectId);
|
|
28183
28240
|
return rows.map(rowToWorkdir);
|
|
28184
28241
|
}
|
|
28185
|
-
function removeWorkdir(projectId, path,
|
|
28186
|
-
const d =
|
|
28242
|
+
function removeWorkdir(projectId, path, db3) {
|
|
28243
|
+
const d = db3 || getDatabase2();
|
|
28187
28244
|
d.run("DELETE FROM project_workdirs WHERE project_id = ? AND path = ?", [projectId, path]);
|
|
28188
28245
|
}
|
|
28189
28246
|
function generateProjectId() {
|
|
@@ -28201,11 +28258,11 @@ function scaffoldProject(path) {
|
|
|
28201
28258
|
mkdirSync32(join42(path, dir), { recursive: true });
|
|
28202
28259
|
}
|
|
28203
28260
|
}
|
|
28204
|
-
function ensureUniqueSlug(base,
|
|
28261
|
+
function ensureUniqueSlug(base, db3, excludeId) {
|
|
28205
28262
|
let candidate = base;
|
|
28206
28263
|
let suffix = 1;
|
|
28207
28264
|
while (true) {
|
|
28208
|
-
const row =
|
|
28265
|
+
const row = db3.query("SELECT id FROM projects WHERE slug = ?").get(candidate);
|
|
28209
28266
|
if (!row || row.id === excludeId)
|
|
28210
28267
|
return candidate;
|
|
28211
28268
|
suffix++;
|
|
@@ -28221,8 +28278,8 @@ function rowToProject(row) {
|
|
|
28221
28278
|
last_opened_at: row.last_opened_at ?? null
|
|
28222
28279
|
};
|
|
28223
28280
|
}
|
|
28224
|
-
function createProject2(input,
|
|
28225
|
-
const d =
|
|
28281
|
+
function createProject2(input, db3) {
|
|
28282
|
+
const d = db3 || getDatabase2();
|
|
28226
28283
|
const id = generateProjectId();
|
|
28227
28284
|
const ts = now2();
|
|
28228
28285
|
const baseSlug = input.slug || slugify2(input.name);
|
|
@@ -28257,23 +28314,23 @@ function createProject2(input, db2) {
|
|
|
28257
28314
|
}
|
|
28258
28315
|
return getProject2(id, d);
|
|
28259
28316
|
}
|
|
28260
|
-
function getProject2(id,
|
|
28261
|
-
const d =
|
|
28317
|
+
function getProject2(id, db3) {
|
|
28318
|
+
const d = db3 || getDatabase2();
|
|
28262
28319
|
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
28263
28320
|
return row ? rowToProject(row) : null;
|
|
28264
28321
|
}
|
|
28265
|
-
function getProjectBySlug(slug,
|
|
28266
|
-
const d =
|
|
28322
|
+
function getProjectBySlug(slug, db3) {
|
|
28323
|
+
const d = db3 || getDatabase2();
|
|
28267
28324
|
const row = d.query("SELECT * FROM projects WHERE slug = ?").get(slug);
|
|
28268
28325
|
return row ? rowToProject(row) : null;
|
|
28269
28326
|
}
|
|
28270
|
-
function getProjectByPath2(path,
|
|
28271
|
-
const d =
|
|
28327
|
+
function getProjectByPath2(path, db3) {
|
|
28328
|
+
const d = db3 || getDatabase2();
|
|
28272
28329
|
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
28273
28330
|
return row ? rowToProject(row) : null;
|
|
28274
28331
|
}
|
|
28275
|
-
function listProjects2(filter = {},
|
|
28276
|
-
const d =
|
|
28332
|
+
function listProjects2(filter = {}, db3) {
|
|
28333
|
+
const d = db3 || getDatabase2();
|
|
28277
28334
|
const conditions = [];
|
|
28278
28335
|
const params = [];
|
|
28279
28336
|
if (filter.status) {
|
|
@@ -28292,8 +28349,8 @@ function listProjects2(filter = {}, db2) {
|
|
|
28292
28349
|
}
|
|
28293
28350
|
return rows.map(rowToProject);
|
|
28294
28351
|
}
|
|
28295
|
-
function updateProject(id, input,
|
|
28296
|
-
const d =
|
|
28352
|
+
function updateProject(id, input, db3) {
|
|
28353
|
+
const d = db3 || getDatabase2();
|
|
28297
28354
|
const project = getProject2(id, d);
|
|
28298
28355
|
if (!project)
|
|
28299
28356
|
throw new ProjectNotFoundError(id);
|
|
@@ -28342,8 +28399,8 @@ function updateProject(id, input, db2) {
|
|
|
28342
28399
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
28343
28400
|
return getProject2(id, d);
|
|
28344
28401
|
}
|
|
28345
|
-
function setIntegrations(id, integrations,
|
|
28346
|
-
const d =
|
|
28402
|
+
function setIntegrations(id, integrations, db3) {
|
|
28403
|
+
const d = db3 || getDatabase2();
|
|
28347
28404
|
const project = getProject2(id, d);
|
|
28348
28405
|
if (!project)
|
|
28349
28406
|
throw new ProjectNotFoundError(id);
|
|
@@ -28363,16 +28420,16 @@ function setIntegrations(id, integrations, db2) {
|
|
|
28363
28420
|
} catch {}
|
|
28364
28421
|
return getProject2(id, d);
|
|
28365
28422
|
}
|
|
28366
|
-
function archiveProject(id,
|
|
28367
|
-
const d =
|
|
28423
|
+
function archiveProject(id, db3) {
|
|
28424
|
+
const d = db3 || getDatabase2();
|
|
28368
28425
|
const project = getProject2(id, d);
|
|
28369
28426
|
if (!project)
|
|
28370
28427
|
throw new ProjectNotFoundError(id);
|
|
28371
28428
|
d.run("UPDATE projects SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), id]);
|
|
28372
28429
|
return getProject2(id, d);
|
|
28373
28430
|
}
|
|
28374
|
-
function unarchiveProject(id,
|
|
28375
|
-
const d =
|
|
28431
|
+
function unarchiveProject(id, db3) {
|
|
28432
|
+
const d = db3 || getDatabase2();
|
|
28376
28433
|
const project = getProject2(id, d);
|
|
28377
28434
|
if (!project)
|
|
28378
28435
|
throw new ProjectNotFoundError(id);
|
|
@@ -28389,8 +28446,8 @@ function levenshtein(a, b) {
|
|
|
28389
28446
|
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
28390
28447
|
return dp[m][n];
|
|
28391
28448
|
}
|
|
28392
|
-
function resolveProject(idOrSlug,
|
|
28393
|
-
const d =
|
|
28449
|
+
function resolveProject(idOrSlug, db3) {
|
|
28450
|
+
const d = db3 || getDatabase2();
|
|
28394
28451
|
let project = getProject2(idOrSlug, d);
|
|
28395
28452
|
if (project)
|
|
28396
28453
|
return project;
|
|
@@ -28413,22 +28470,22 @@ function resolveProject(idOrSlug, db2) {
|
|
|
28413
28470
|
function syncRowToLog(row) {
|
|
28414
28471
|
return { ...row, direction: row.direction, status: row.status };
|
|
28415
28472
|
}
|
|
28416
|
-
function startSyncLog(projectId, direction,
|
|
28417
|
-
const d =
|
|
28473
|
+
function startSyncLog(projectId, direction, db3) {
|
|
28474
|
+
const d = db3 || getDatabase2();
|
|
28418
28475
|
const id = uuid2();
|
|
28419
28476
|
const ts = now2();
|
|
28420
28477
|
d.run("INSERT INTO sync_log (id, project_id, direction, status, started_at) VALUES (?, ?, ?, 'running', ?)", [id, projectId, direction, ts]);
|
|
28421
28478
|
return d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
|
|
28422
28479
|
}
|
|
28423
|
-
function completeSyncLog(id, result,
|
|
28424
|
-
const d =
|
|
28480
|
+
function completeSyncLog(id, result, db3) {
|
|
28481
|
+
const d = db3 || getDatabase2();
|
|
28425
28482
|
const status = result.error ? "failed" : "completed";
|
|
28426
28483
|
d.run("UPDATE sync_log SET status = ?, files_synced = ?, bytes = ?, error = ?, completed_at = ? WHERE id = ?", [status, result.files_synced ?? 0, result.bytes ?? 0, result.error ?? null, now2(), id]);
|
|
28427
28484
|
const row = d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
|
|
28428
28485
|
return syncRowToLog(row);
|
|
28429
28486
|
}
|
|
28430
|
-
function listSyncLogs(projectId, limit = 20,
|
|
28431
|
-
const d =
|
|
28487
|
+
function listSyncLogs(projectId, limit = 20, db3) {
|
|
28488
|
+
const d = db3 || getDatabase2();
|
|
28432
28489
|
const rows = d.query("SELECT * FROM sync_log WHERE project_id = ? ORDER BY started_at DESC LIMIT ?").all(projectId, limit);
|
|
28433
28490
|
return rows.map(syncRowToLog);
|
|
28434
28491
|
}
|
|
@@ -58796,7 +58853,7 @@ function buildPeriod(days) {
|
|
|
58796
58853
|
return { periodStart, periodEnd };
|
|
58797
58854
|
}
|
|
58798
58855
|
async function collectComplianceData(options) {
|
|
58799
|
-
const
|
|
58856
|
+
const db3 = getDatabase();
|
|
58800
58857
|
const runsInPeriod = listRuns({
|
|
58801
58858
|
projectId: options.projectId,
|
|
58802
58859
|
limit: 1e4
|
|
@@ -58811,13 +58868,13 @@ async function collectComplianceData(options) {
|
|
|
58811
58868
|
scanConditions.push("project_id = ?");
|
|
58812
58869
|
scanParams.push(options.projectId);
|
|
58813
58870
|
}
|
|
58814
|
-
const scanIssues =
|
|
58871
|
+
const scanIssues = db3.query(`SELECT type, severity FROM scan_issues WHERE ${scanConditions.join(" AND ")}`).all(...scanParams);
|
|
58815
58872
|
const injectionProbesRun = scanIssues.filter((i2) => i2.type === "injection" || i2.type === "sql_injection" || i2.type === "xss").length;
|
|
58816
58873
|
const injectionVulnsFound = scanIssues.filter((i2) => (i2.type === "injection" || i2.type === "xss") && (i2.severity === "high" || i2.severity === "critical")).length;
|
|
58817
58874
|
const piiLeaksDetected = scanIssues.filter((i2) => i2.type === "pii" || i2.type === "pii_leak").length;
|
|
58818
58875
|
let goldenAnswerDriftEvents = 0;
|
|
58819
58876
|
try {
|
|
58820
|
-
const driftRows =
|
|
58877
|
+
const driftRows = db3.query(`SELECT COUNT(*) as count FROM golden_check_results WHERE drift_detected = 1 AND created_at >= ? AND created_at <= ?`).get(options.periodStart, options.periodEnd);
|
|
58821
58878
|
goldenAnswerDriftEvents = driftRows?.count ?? 0;
|
|
58822
58879
|
} catch {
|
|
58823
58880
|
goldenAnswerDriftEvents = 0;
|
|
@@ -58829,7 +58886,7 @@ async function collectComplianceData(options) {
|
|
|
58829
58886
|
let flakyScenarioCount = 0;
|
|
58830
58887
|
if (runIds.length > 0) {
|
|
58831
58888
|
const placeholders = runIds.map(() => "?").join(", ");
|
|
58832
|
-
const evalResults =
|
|
58889
|
+
const evalResults = db3.query(`SELECT r.status, r.metadata FROM results r
|
|
58833
58890
|
JOIN scenarios s ON r.scenario_id = s.id
|
|
58834
58891
|
WHERE r.run_id IN (${placeholders}) AND s.scenario_type = 'eval'`).all(...runIds);
|
|
58835
58892
|
evalScenariosRun = evalResults.length;
|
|
@@ -58842,9 +58899,9 @@ async function collectComplianceData(options) {
|
|
|
58842
58899
|
totalEvalScore += er.status === "passed" ? 1 : 0;
|
|
58843
58900
|
}
|
|
58844
58901
|
}
|
|
58845
|
-
const a11yRows =
|
|
58902
|
+
const a11yRows = db3.query(`SELECT COUNT(*) as count FROM scan_issues WHERE type = 'a11y' AND severity = 'critical' AND first_seen_at >= ? AND first_seen_at <= ?`).get(options.periodStart, options.periodEnd);
|
|
58846
58903
|
a11yViolationsCritical = a11yRows?.count ?? 0;
|
|
58847
|
-
const flakyRows =
|
|
58904
|
+
const flakyRows = db3.query(`SELECT COUNT(DISTINCT scenario_id) as count FROM results WHERE run_id IN (${placeholders}) AND status = 'flaky'`).get(...runIds);
|
|
58848
58905
|
flakyScenarioCount = flakyRows?.count ?? 0;
|
|
58849
58906
|
}
|
|
58850
58907
|
const averageEvalScore = evalScenariosRun > 0 ? totalEvalScore / evalScenariosRun : 1;
|
|
@@ -59093,6 +59150,297 @@ var init_openapi_import = __esm(() => {
|
|
|
59093
59150
|
init_api_checks();
|
|
59094
59151
|
});
|
|
59095
59152
|
|
|
59153
|
+
// src/lib/next-route-inventory.ts
|
|
59154
|
+
var exports_next_route_inventory = {};
|
|
59155
|
+
__export(exports_next_route_inventory, {
|
|
59156
|
+
scenarioInputForNextRoute: () => scenarioInputForNextRoute,
|
|
59157
|
+
importNextRouteInventory: () => importNextRouteInventory,
|
|
59158
|
+
discoverNextRouteInventory: () => discoverNextRouteInventory
|
|
59159
|
+
});
|
|
59160
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
|
|
59161
|
+
import { basename as basename3, join as join20, relative as relative4, resolve as resolve3 } from "path";
|
|
59162
|
+
function discoverNextRouteInventory(options) {
|
|
59163
|
+
const rootDir = resolve3(options.rootDir);
|
|
59164
|
+
const appDir = resolveAppDir(rootDir, options.appDir);
|
|
59165
|
+
const includePages = options.includePages !== false;
|
|
59166
|
+
const includeApi = options.includeApi !== false;
|
|
59167
|
+
const files = walkRouteFiles(appDir);
|
|
59168
|
+
const items = files.map((file) => routeItemFromFile(rootDir, appDir, file)).filter((item) => Boolean(item)).filter((item) => item.kind === "page" ? includePages : includeApi).sort((a2, b2) => `${a2.kind}:${a2.routePath}:${a2.file}`.localeCompare(`${b2.kind}:${b2.routePath}:${b2.file}`)).slice(0, options.limit);
|
|
59169
|
+
const categories = {};
|
|
59170
|
+
for (const item of items) {
|
|
59171
|
+
categories[item.category] = (categories[item.category] ?? 0) + 1;
|
|
59172
|
+
}
|
|
59173
|
+
return {
|
|
59174
|
+
rootDir,
|
|
59175
|
+
appDir,
|
|
59176
|
+
total: items.length,
|
|
59177
|
+
pages: items.filter((item) => item.kind === "page").length,
|
|
59178
|
+
apiRoutes: items.filter((item) => item.kind === "api").length,
|
|
59179
|
+
dynamic: items.filter((item) => item.dynamic).length,
|
|
59180
|
+
categories,
|
|
59181
|
+
items
|
|
59182
|
+
};
|
|
59183
|
+
}
|
|
59184
|
+
function scenarioInputForNextRoute(item, projectId) {
|
|
59185
|
+
const label = item.kind === "page" ? "page" : "API route";
|
|
59186
|
+
const methodList = item.methods.length > 0 ? item.methods.join(", ") : "discovered methods";
|
|
59187
|
+
const dynamicStep = item.dynamic ? "Substitute dynamic path parameters with valid fixture values from the target org before opening or calling the route." : undefined;
|
|
59188
|
+
const pageSteps = [
|
|
59189
|
+
dynamicStep,
|
|
59190
|
+
`Open the Next.js ${label} ${item.routePath}.`,
|
|
59191
|
+
"Wait for the route to finish loading and verify it does not show a blank shell, framework error page, or unexpected auth loop.",
|
|
59192
|
+
"Exercise visible primary navigation, tabs, filters, dialogs, forms, and safe buttons on this route.",
|
|
59193
|
+
"Verify the route stays within the expected org/workspace context and does not emit console errors."
|
|
59194
|
+
].filter(Boolean);
|
|
59195
|
+
const apiSteps = [
|
|
59196
|
+
dynamicStep,
|
|
59197
|
+
`Call the ${methodList} handler(s) for ${item.routePath} using safe fixture data.`,
|
|
59198
|
+
"Verify expected authentication, authorization, validation, and tenant isolation behavior.",
|
|
59199
|
+
"For mutating methods, use harmless test payloads and confirm the response does not create cross-org side effects.",
|
|
59200
|
+
"Verify response status, JSON shape, and error messages are stable and regression-safe."
|
|
59201
|
+
].filter(Boolean);
|
|
59202
|
+
return {
|
|
59203
|
+
name: `Next ${label}: ${item.routePath}`,
|
|
59204
|
+
description: `Source-discovered ${label} from ${item.file}. Verify route behavior and regressions for ${item.category}.`,
|
|
59205
|
+
steps: item.kind === "page" ? pageSteps : apiSteps,
|
|
59206
|
+
tags: item.tags,
|
|
59207
|
+
priority: item.priority,
|
|
59208
|
+
targetPath: item.routePath,
|
|
59209
|
+
requiresAuth: item.requiresAuth,
|
|
59210
|
+
assertions: item.kind === "page" ? SAFE_PAGE_ASSERTIONS : [],
|
|
59211
|
+
metadata: {
|
|
59212
|
+
source: "next-route-inventory",
|
|
59213
|
+
routeFile: item.file,
|
|
59214
|
+
routeKind: item.kind,
|
|
59215
|
+
category: item.category,
|
|
59216
|
+
methods: item.methods,
|
|
59217
|
+
dynamic: item.dynamic,
|
|
59218
|
+
groups: item.groups
|
|
59219
|
+
},
|
|
59220
|
+
projectId
|
|
59221
|
+
};
|
|
59222
|
+
}
|
|
59223
|
+
function importNextRouteInventory(options) {
|
|
59224
|
+
const inventory = discoverNextRouteInventory(options);
|
|
59225
|
+
let created = 0;
|
|
59226
|
+
let updated = 0;
|
|
59227
|
+
let deduped = 0;
|
|
59228
|
+
const scenarios = [];
|
|
59229
|
+
const workflows = [];
|
|
59230
|
+
if (options.createScenarios) {
|
|
59231
|
+
for (const item of inventory.items) {
|
|
59232
|
+
const result = upsertScenario(scenarioInputForNextRoute(item, options.projectId));
|
|
59233
|
+
scenarios.push(result.scenario);
|
|
59234
|
+
if (result.action === "created")
|
|
59235
|
+
created++;
|
|
59236
|
+
else if (result.action === "updated")
|
|
59237
|
+
updated++;
|
|
59238
|
+
else
|
|
59239
|
+
deduped++;
|
|
59240
|
+
}
|
|
59241
|
+
}
|
|
59242
|
+
if (options.createWorkflows) {
|
|
59243
|
+
workflows.push(...upsertRouteInventoryWorkflows(inventory, options));
|
|
59244
|
+
}
|
|
59245
|
+
return { inventory, created, updated, deduped, scenarios, workflows };
|
|
59246
|
+
}
|
|
59247
|
+
function upsertRouteInventoryWorkflows(inventory, options) {
|
|
59248
|
+
const workflows = [];
|
|
59249
|
+
const categories = Object.keys(inventory.categories).sort();
|
|
59250
|
+
for (const category of categories) {
|
|
59251
|
+
const kinds = new Set(inventory.items.filter((item) => item.category === category).map((item) => item.kind));
|
|
59252
|
+
for (const kind of kinds) {
|
|
59253
|
+
const name = `Next route inventory ${category} ${kind}`;
|
|
59254
|
+
const scenarioTags = ["next-route", `area:${category}`, `route:${kind}`];
|
|
59255
|
+
const execution = {
|
|
59256
|
+
target: options.workflowTarget ?? "sandbox",
|
|
59257
|
+
provider: options.workflowProvider,
|
|
59258
|
+
sandboxCleanup: "delete",
|
|
59259
|
+
sandboxSyncStrategy: "rsync",
|
|
59260
|
+
...options.workflowExecution
|
|
59261
|
+
};
|
|
59262
|
+
const existing = listTestingWorkflows({ projectId: options.projectId, enabled: undefined }).find((workflow) => workflow.name === name);
|
|
59263
|
+
const input = {
|
|
59264
|
+
name,
|
|
59265
|
+
description: `Source-discovered Next.js ${kind} coverage for ${category} routes.`,
|
|
59266
|
+
projectId: options.projectId,
|
|
59267
|
+
scenarioFilter: { tags: scenarioTags },
|
|
59268
|
+
execution
|
|
59269
|
+
};
|
|
59270
|
+
workflows.push(existing ? updateTestingWorkflow(existing.id, input) : createTestingWorkflow(input));
|
|
59271
|
+
}
|
|
59272
|
+
}
|
|
59273
|
+
return workflows;
|
|
59274
|
+
}
|
|
59275
|
+
function resolveAppDir(rootDir, appDir) {
|
|
59276
|
+
const candidates = appDir ? [resolve3(rootDir, appDir)] : [
|
|
59277
|
+
join20(rootDir, "packages", "web", "app"),
|
|
59278
|
+
join20(rootDir, "app"),
|
|
59279
|
+
rootDir
|
|
59280
|
+
];
|
|
59281
|
+
for (const candidate of candidates) {
|
|
59282
|
+
if (existsSync18(candidate) && statSync4(candidate).isDirectory())
|
|
59283
|
+
return candidate;
|
|
59284
|
+
}
|
|
59285
|
+
throw new Error(`Next.js app directory not found under ${rootDir}`);
|
|
59286
|
+
}
|
|
59287
|
+
function walkRouteFiles(appDir) {
|
|
59288
|
+
const files = [];
|
|
59289
|
+
function walk(dir) {
|
|
59290
|
+
for (const entry of readdirSync6(dir)) {
|
|
59291
|
+
if (WALK_EXCLUDES.has(entry))
|
|
59292
|
+
continue;
|
|
59293
|
+
const fullPath = join20(dir, entry);
|
|
59294
|
+
const stat = statSync4(fullPath);
|
|
59295
|
+
if (stat.isDirectory()) {
|
|
59296
|
+
walk(fullPath);
|
|
59297
|
+
} else if (ROUTE_FILE_NAMES.has(entry)) {
|
|
59298
|
+
files.push(fullPath);
|
|
59299
|
+
}
|
|
59300
|
+
}
|
|
59301
|
+
}
|
|
59302
|
+
walk(appDir);
|
|
59303
|
+
return files;
|
|
59304
|
+
}
|
|
59305
|
+
function routeItemFromFile(rootDir, appDir, file) {
|
|
59306
|
+
const fileName = basename3(file);
|
|
59307
|
+
const kind = fileName.startsWith("page.") ? "page" : "api";
|
|
59308
|
+
const relativeFile = relative4(rootDir, file);
|
|
59309
|
+
const appRelative = relative4(appDir, file).split(/[\\/]/);
|
|
59310
|
+
const routeSegments = appRelative.slice(0, -1);
|
|
59311
|
+
const groups = routeSegments.filter((segment) => segment.startsWith("(") && segment.endsWith(")")).map((segment) => segment.slice(1, -1));
|
|
59312
|
+
const pathSegments = routeSegments.filter((segment) => !segment.startsWith("(")).filter((segment) => !segment.startsWith("@")).map(normalizeRouteSegment).filter(Boolean);
|
|
59313
|
+
const routePath = `/${pathSegments.join("/")}`.replace(/\/+/g, "/");
|
|
59314
|
+
const normalizedRoutePath = routePath === "/" ? "/" : routePath.replace(/\/$/, "");
|
|
59315
|
+
const methods = kind === "api" ? extractRouteMethods(file) : [];
|
|
59316
|
+
const category = classifyRoute(normalizedRoutePath, groups, relativeFile);
|
|
59317
|
+
const dynamic = routeSegments.some((segment) => segment.includes("["));
|
|
59318
|
+
const requiresAuth = inferRequiresAuth(normalizedRoutePath, groups, kind);
|
|
59319
|
+
return {
|
|
59320
|
+
kind,
|
|
59321
|
+
routePath: normalizedRoutePath,
|
|
59322
|
+
file: relativeFile,
|
|
59323
|
+
category,
|
|
59324
|
+
groups,
|
|
59325
|
+
methods,
|
|
59326
|
+
dynamic,
|
|
59327
|
+
requiresAuth,
|
|
59328
|
+
tags: tagsForRoute({ kind, routePath: normalizedRoutePath, category, groups, dynamic, requiresAuth }),
|
|
59329
|
+
priority: priorityForRoute(normalizedRoutePath, category, kind)
|
|
59330
|
+
};
|
|
59331
|
+
}
|
|
59332
|
+
function normalizeRouteSegment(segment) {
|
|
59333
|
+
if (segment.startsWith("[[...") && segment.endsWith("]]")) {
|
|
59334
|
+
return `:${segment.slice(5, -2)}*?`;
|
|
59335
|
+
}
|
|
59336
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
59337
|
+
return `:${segment.slice(4, -1)}*`;
|
|
59338
|
+
}
|
|
59339
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
59340
|
+
return `:${segment.slice(1, -1)}`;
|
|
59341
|
+
}
|
|
59342
|
+
return segment;
|
|
59343
|
+
}
|
|
59344
|
+
function extractRouteMethods(file) {
|
|
59345
|
+
const source = readFileSync9(file, "utf8");
|
|
59346
|
+
const methods = new Set;
|
|
59347
|
+
const pattern = /\b(?:export\s+)?(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b|\bexport\s+const\s+(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\b/g;
|
|
59348
|
+
for (const match of source.matchAll(pattern)) {
|
|
59349
|
+
const method = match[1] ?? match[2];
|
|
59350
|
+
if (method)
|
|
59351
|
+
methods.add(method);
|
|
59352
|
+
}
|
|
59353
|
+
return [...methods].sort();
|
|
59354
|
+
}
|
|
59355
|
+
function classifyRoute(routePath, groups, file) {
|
|
59356
|
+
const haystack = `${routePath} ${groups.join(" ")} ${file}`.toLowerCase();
|
|
59357
|
+
if (haystack.includes("admin"))
|
|
59358
|
+
return "admin";
|
|
59359
|
+
if (haystack.includes("auth") || routePath.startsWith("/cli/device"))
|
|
59360
|
+
return "auth";
|
|
59361
|
+
if (haystack.includes("ai-runtime") || /\/(chat|sessions|memories|knowledge|learning|copilot|guardrails)\b/.test(routePath))
|
|
59362
|
+
return "ai-runtime";
|
|
59363
|
+
if (haystack.includes("commerce") || /\/(billing|shop|agent-wallet|domains|whois-profiles)\b/.test(routePath))
|
|
59364
|
+
return "commerce";
|
|
59365
|
+
if (haystack.includes("communications") || /\/(telephony|emails)\b/.test(routePath))
|
|
59366
|
+
return "communications";
|
|
59367
|
+
if (haystack.includes("crm") || routePath.includes("/contacts"))
|
|
59368
|
+
return "crm";
|
|
59369
|
+
if (haystack.includes("integrations") || routePath.includes("/connectors"))
|
|
59370
|
+
return "integrations";
|
|
59371
|
+
if (haystack.includes("dashboard") || routePath.includes(":orgSlug"))
|
|
59372
|
+
return "dashboard";
|
|
59373
|
+
if (haystack.includes("public") || haystack.includes("pages"))
|
|
59374
|
+
return "public";
|
|
59375
|
+
if (routePath.startsWith("/api/"))
|
|
59376
|
+
return "api";
|
|
59377
|
+
return "app";
|
|
59378
|
+
}
|
|
59379
|
+
function inferRequiresAuth(routePath, groups, kind) {
|
|
59380
|
+
const haystack = `${routePath} ${groups.join(" ")}`.toLowerCase();
|
|
59381
|
+
if (haystack.includes("auth") || haystack.includes("public") || haystack.includes("webhook"))
|
|
59382
|
+
return false;
|
|
59383
|
+
if (routePath.startsWith("/api/v1/auth/"))
|
|
59384
|
+
return false;
|
|
59385
|
+
if (routePath.startsWith("/api/"))
|
|
59386
|
+
return true;
|
|
59387
|
+
return kind === "page" && (haystack.includes("admin") || haystack.includes("dashboard") || routePath.includes(":orgSlug") || routePath.startsWith("/settings"));
|
|
59388
|
+
}
|
|
59389
|
+
function tagsForRoute(input) {
|
|
59390
|
+
const tags = new Set([
|
|
59391
|
+
"next-route",
|
|
59392
|
+
`route:${input.kind}`,
|
|
59393
|
+
`area:${input.category}`,
|
|
59394
|
+
input.category
|
|
59395
|
+
]);
|
|
59396
|
+
for (const group of input.groups)
|
|
59397
|
+
tags.add(`group:${group}`);
|
|
59398
|
+
if (input.dynamic)
|
|
59399
|
+
tags.add("dynamic-route");
|
|
59400
|
+
if (input.requiresAuth)
|
|
59401
|
+
tags.add("auth-required");
|
|
59402
|
+
if (input.routePath.startsWith("/api/"))
|
|
59403
|
+
tags.add("api");
|
|
59404
|
+
return [...tags];
|
|
59405
|
+
}
|
|
59406
|
+
function priorityForRoute(routePath, category, kind) {
|
|
59407
|
+
if (category === "auth")
|
|
59408
|
+
return "critical";
|
|
59409
|
+
if (category === "commerce" || category === "ai-runtime")
|
|
59410
|
+
return "critical";
|
|
59411
|
+
if (category === "admin" || category === "dashboard")
|
|
59412
|
+
return "high";
|
|
59413
|
+
if (kind === "api")
|
|
59414
|
+
return "high";
|
|
59415
|
+
if (routePath === "/" || category === "public")
|
|
59416
|
+
return "medium";
|
|
59417
|
+
return "medium";
|
|
59418
|
+
}
|
|
59419
|
+
var ROUTE_FILE_NAMES, WALK_EXCLUDES, SAFE_PAGE_ASSERTIONS;
|
|
59420
|
+
var init_next_route_inventory = __esm(() => {
|
|
59421
|
+
init_scenarios();
|
|
59422
|
+
init_workflows();
|
|
59423
|
+
ROUTE_FILE_NAMES = new Set([
|
|
59424
|
+
"page.tsx",
|
|
59425
|
+
"page.ts",
|
|
59426
|
+
"page.jsx",
|
|
59427
|
+
"page.js",
|
|
59428
|
+
"page.mdx",
|
|
59429
|
+
"route.ts",
|
|
59430
|
+
"route.js"
|
|
59431
|
+
]);
|
|
59432
|
+
WALK_EXCLUDES = new Set([
|
|
59433
|
+
".git",
|
|
59434
|
+
".next",
|
|
59435
|
+
".turbo",
|
|
59436
|
+
"node_modules",
|
|
59437
|
+
"dist",
|
|
59438
|
+
"build",
|
|
59439
|
+
"coverage"
|
|
59440
|
+
]);
|
|
59441
|
+
SAFE_PAGE_ASSERTIONS = [{ type: "no_console_errors" }];
|
|
59442
|
+
});
|
|
59443
|
+
|
|
59096
59444
|
// src/lib/generator.ts
|
|
59097
59445
|
var exports_generator = {};
|
|
59098
59446
|
__export(exports_generator, {
|
|
@@ -59391,7 +59739,7 @@ async function recordSession(url, options) {
|
|
|
59391
59739
|
await Promise.race([
|
|
59392
59740
|
page.waitForEvent("close").catch(() => {}),
|
|
59393
59741
|
context.waitForEvent("close").catch(() => {}),
|
|
59394
|
-
new Promise((
|
|
59742
|
+
new Promise((resolve4) => setTimeout(resolve4, timeout))
|
|
59395
59743
|
]);
|
|
59396
59744
|
clearInterval(pollInterval);
|
|
59397
59745
|
try {
|
|
@@ -59663,49 +60011,49 @@ __export(exports_agents, {
|
|
|
59663
60011
|
getAgent: () => getAgent
|
|
59664
60012
|
});
|
|
59665
60013
|
function registerAgent(input) {
|
|
59666
|
-
const
|
|
59667
|
-
const existing =
|
|
60014
|
+
const db3 = getDatabase();
|
|
60015
|
+
const existing = db3.query("SELECT * FROM agents WHERE name = ?").get(input.name);
|
|
59668
60016
|
if (existing) {
|
|
59669
|
-
|
|
60017
|
+
db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
|
|
59670
60018
|
return getAgent(existing.id);
|
|
59671
60019
|
}
|
|
59672
60020
|
const id = uuid();
|
|
59673
60021
|
const timestamp = now();
|
|
59674
|
-
|
|
60022
|
+
db3.query(`
|
|
59675
60023
|
INSERT INTO agents (id, name, description, role, metadata, created_at, last_seen_at)
|
|
59676
60024
|
VALUES (?, ?, ?, ?, '{}', ?, ?)
|
|
59677
60025
|
`).run(id, input.name, input.description ?? null, input.role ?? null, timestamp, timestamp);
|
|
59678
60026
|
return getAgent(id);
|
|
59679
60027
|
}
|
|
59680
60028
|
function getAgent(id) {
|
|
59681
|
-
const
|
|
59682
|
-
const row =
|
|
60029
|
+
const db3 = getDatabase();
|
|
60030
|
+
const row = db3.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
59683
60031
|
return row ? agentFromRow(row) : null;
|
|
59684
60032
|
}
|
|
59685
60033
|
function getAgentByName(name) {
|
|
59686
|
-
const
|
|
59687
|
-
const row =
|
|
60034
|
+
const db3 = getDatabase();
|
|
60035
|
+
const row = db3.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
59688
60036
|
return row ? agentFromRow(row) : null;
|
|
59689
60037
|
}
|
|
59690
60038
|
function listAgents() {
|
|
59691
|
-
const
|
|
59692
|
-
const rows =
|
|
60039
|
+
const db3 = getDatabase();
|
|
60040
|
+
const rows = db3.query("SELECT * FROM agents ORDER BY created_at DESC").all();
|
|
59693
60041
|
return rows.map(agentFromRow);
|
|
59694
60042
|
}
|
|
59695
60043
|
function heartbeatAgent(id) {
|
|
59696
|
-
const
|
|
59697
|
-
const affected =
|
|
60044
|
+
const db3 = getDatabase();
|
|
60045
|
+
const affected = db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
|
|
59698
60046
|
if (affected.changes === 0)
|
|
59699
60047
|
return null;
|
|
59700
60048
|
return getAgent(id);
|
|
59701
60049
|
}
|
|
59702
60050
|
function setAgentFocus(id, scenarioId) {
|
|
59703
|
-
const
|
|
60051
|
+
const db3 = getDatabase();
|
|
59704
60052
|
const agent = getAgent(id);
|
|
59705
60053
|
if (!agent)
|
|
59706
60054
|
return null;
|
|
59707
60055
|
const metadata = { ...agent.metadata ?? {}, focus: scenarioId };
|
|
59708
|
-
|
|
60056
|
+
db3.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
|
|
59709
60057
|
return getAgent(id);
|
|
59710
60058
|
}
|
|
59711
60059
|
var init_agents = __esm(() => {
|
|
@@ -76959,7 +77307,7 @@ function createProviderToolFactoryWithOutputSchema({
|
|
|
76959
77307
|
supportsDeferredResults
|
|
76960
77308
|
});
|
|
76961
77309
|
}
|
|
76962
|
-
async function
|
|
77310
|
+
async function resolve4(value) {
|
|
76963
77311
|
if (typeof value === "function") {
|
|
76964
77312
|
value = value();
|
|
76965
77313
|
}
|
|
@@ -78646,7 +78994,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78646
78994
|
try {
|
|
78647
78995
|
const { value } = await getFromApi({
|
|
78648
78996
|
url: `${this.config.baseURL}/config`,
|
|
78649
|
-
headers: await
|
|
78997
|
+
headers: await resolve4(this.config.headers()),
|
|
78650
78998
|
successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
|
|
78651
78999
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
78652
79000
|
errorSchema: exports_external2.any(),
|
|
@@ -78664,7 +79012,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78664
79012
|
const baseUrl = new URL(this.config.baseURL);
|
|
78665
79013
|
const { value } = await getFromApi({
|
|
78666
79014
|
url: `${baseUrl.origin}/v1/credits`,
|
|
78667
|
-
headers: await
|
|
79015
|
+
headers: await resolve4(this.config.headers()),
|
|
78668
79016
|
successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
|
|
78669
79017
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
78670
79018
|
errorSchema: exports_external2.any(),
|
|
@@ -78710,7 +79058,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78710
79058
|
}
|
|
78711
79059
|
const { value } = await getFromApi({
|
|
78712
79060
|
url: `${baseUrl.origin}/v1/report?${searchParams.toString()}`,
|
|
78713
|
-
headers: await
|
|
79061
|
+
headers: await resolve4(this.config.headers()),
|
|
78714
79062
|
successfulResponseHandler: createJsonResponseHandler(gatewaySpendReportResponseSchema),
|
|
78715
79063
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
78716
79064
|
errorSchema: exports_external2.any(),
|
|
@@ -78732,7 +79080,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78732
79080
|
const baseUrl = new URL(this.config.baseURL);
|
|
78733
79081
|
const { value } = await getFromApi({
|
|
78734
79082
|
url: `${baseUrl.origin}/v1/generation?id=${encodeURIComponent(params.id)}`,
|
|
78735
|
-
headers: await
|
|
79083
|
+
headers: await resolve4(this.config.headers()),
|
|
78736
79084
|
successfulResponseHandler: createJsonResponseHandler(gatewayGenerationInfoResponseSchema),
|
|
78737
79085
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
78738
79086
|
errorSchema: exports_external2.any(),
|
|
@@ -78765,7 +79113,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78765
79113
|
async doGenerate(options) {
|
|
78766
79114
|
const { args, warnings } = await this.getArgs(options);
|
|
78767
79115
|
const { abortSignal } = options;
|
|
78768
|
-
const resolvedHeaders = await
|
|
79116
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
78769
79117
|
try {
|
|
78770
79118
|
const {
|
|
78771
79119
|
responseHeaders,
|
|
@@ -78773,7 +79121,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78773
79121
|
rawValue: rawResponse
|
|
78774
79122
|
} = await postJsonToApi({
|
|
78775
79123
|
url: this.getUrl(),
|
|
78776
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await
|
|
79124
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve4(this.config.o11yHeaders)),
|
|
78777
79125
|
body: args,
|
|
78778
79126
|
successfulResponseHandler: createJsonResponseHandler(exports_external2.any()),
|
|
78779
79127
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -78796,11 +79144,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78796
79144
|
async doStream(options) {
|
|
78797
79145
|
const { args, warnings } = await this.getArgs(options);
|
|
78798
79146
|
const { abortSignal } = options;
|
|
78799
|
-
const resolvedHeaders = await
|
|
79147
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
78800
79148
|
try {
|
|
78801
79149
|
const { value: response, responseHeaders } = await postJsonToApi({
|
|
78802
79150
|
url: this.getUrl(),
|
|
78803
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await
|
|
79151
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve4(this.config.o11yHeaders)),
|
|
78804
79152
|
body: args,
|
|
78805
79153
|
successfulResponseHandler: createEventSourceResponseHandler(exports_external2.any()),
|
|
78806
79154
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -78885,7 +79233,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78885
79233
|
providerOptions
|
|
78886
79234
|
}) {
|
|
78887
79235
|
var _a92;
|
|
78888
|
-
const resolvedHeaders = await
|
|
79236
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
78889
79237
|
try {
|
|
78890
79238
|
const {
|
|
78891
79239
|
responseHeaders,
|
|
@@ -78893,7 +79241,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78893
79241
|
rawValue
|
|
78894
79242
|
} = await postJsonToApi({
|
|
78895
79243
|
url: this.getUrl(),
|
|
78896
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
79244
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
78897
79245
|
body: {
|
|
78898
79246
|
values,
|
|
78899
79247
|
...providerOptions ? { providerOptions } : {}
|
|
@@ -78949,7 +79297,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78949
79297
|
abortSignal
|
|
78950
79298
|
}) {
|
|
78951
79299
|
var _a92, _b92, _c2, _d2;
|
|
78952
|
-
const resolvedHeaders = await
|
|
79300
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
78953
79301
|
try {
|
|
78954
79302
|
const {
|
|
78955
79303
|
responseHeaders,
|
|
@@ -78957,7 +79305,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
78957
79305
|
rawValue
|
|
78958
79306
|
} = await postJsonToApi({
|
|
78959
79307
|
url: this.getUrl(),
|
|
78960
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
79308
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
78961
79309
|
body: {
|
|
78962
79310
|
prompt,
|
|
78963
79311
|
n: n2,
|
|
@@ -79032,11 +79380,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
79032
79380
|
abortSignal
|
|
79033
79381
|
}) {
|
|
79034
79382
|
var _a92;
|
|
79035
|
-
const resolvedHeaders = await
|
|
79383
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
79036
79384
|
try {
|
|
79037
79385
|
const { responseHeaders, value: responseBody } = await postJsonToApi({
|
|
79038
79386
|
url: this.getUrl(),
|
|
79039
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
79387
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders), { accept: "text/event-stream" }),
|
|
79040
79388
|
body: {
|
|
79041
79389
|
prompt,
|
|
79042
79390
|
n: n2,
|
|
@@ -79159,7 +79507,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
79159
79507
|
abortSignal,
|
|
79160
79508
|
providerOptions
|
|
79161
79509
|
}) {
|
|
79162
|
-
const resolvedHeaders = await
|
|
79510
|
+
const resolvedHeaders = await resolve4(this.config.headers());
|
|
79163
79511
|
try {
|
|
79164
79512
|
const {
|
|
79165
79513
|
responseHeaders,
|
|
@@ -79167,7 +79515,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
|
|
|
79167
79515
|
rawValue
|
|
79168
79516
|
} = await postJsonToApi({
|
|
79169
79517
|
url: this.getUrl(),
|
|
79170
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
79518
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve4(this.config.o11yHeaders)),
|
|
79171
79519
|
body: {
|
|
79172
79520
|
documents,
|
|
79173
79521
|
query,
|
|
@@ -88585,7 +88933,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
|
|
|
88585
88933
|
const schema = asSchema(inputSchema);
|
|
88586
88934
|
return {
|
|
88587
88935
|
name: "object",
|
|
88588
|
-
responseFormat:
|
|
88936
|
+
responseFormat: resolve4(schema.jsonSchema).then((jsonSchema2) => ({
|
|
88589
88937
|
type: "json",
|
|
88590
88938
|
schema: jsonSchema2,
|
|
88591
88939
|
...name21 != null && { name: name21 },
|
|
@@ -88646,7 +88994,7 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
|
|
|
88646
88994
|
const elementSchema = asSchema(inputElementSchema);
|
|
88647
88995
|
return {
|
|
88648
88996
|
name: "array",
|
|
88649
|
-
responseFormat:
|
|
88997
|
+
responseFormat: resolve4(elementSchema.jsonSchema).then((jsonSchema2) => {
|
|
88650
88998
|
const { $schema, ...itemSchema } = jsonSchema2;
|
|
88651
88999
|
return {
|
|
88652
89000
|
type: "json",
|
|
@@ -91543,9 +91891,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
|
|
|
91543
91891
|
...options
|
|
91544
91892
|
}) {
|
|
91545
91893
|
var _a21, _b16, _c2, _d2, _e2;
|
|
91546
|
-
const resolvedBody = await
|
|
91547
|
-
const resolvedHeaders = await
|
|
91548
|
-
const resolvedCredentials = await
|
|
91894
|
+
const resolvedBody = await resolve4(this.body);
|
|
91895
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
91896
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
91549
91897
|
const baseHeaders = {
|
|
91550
91898
|
...normalizeHeaders(resolvedHeaders),
|
|
91551
91899
|
...normalizeHeaders(options.headers)
|
|
@@ -91593,9 +91941,9 @@ var import_api2, import_api3, __defProp4, __export4 = (target, all) => {
|
|
|
91593
91941
|
}
|
|
91594
91942
|
async reconnectToStream(options) {
|
|
91595
91943
|
var _a21, _b16, _c2, _d2, _e2;
|
|
91596
|
-
const resolvedBody = await
|
|
91597
|
-
const resolvedHeaders = await
|
|
91598
|
-
const resolvedCredentials = await
|
|
91944
|
+
const resolvedBody = await resolve4(this.body);
|
|
91945
|
+
const resolvedHeaders = await resolve4(this.headers);
|
|
91946
|
+
const resolvedCredentials = await resolve4(this.credentials);
|
|
91599
91947
|
const baseHeaders = {
|
|
91600
91948
|
...normalizeHeaders(resolvedHeaders),
|
|
91601
91949
|
...normalizeHeaders(options.headers)
|
|
@@ -93816,7 +94164,7 @@ __export(exports_session_converter, {
|
|
|
93816
94164
|
convertSessionToScenario: () => convertSessionToScenario,
|
|
93817
94165
|
convertSessionFile: () => convertSessionFile
|
|
93818
94166
|
});
|
|
93819
|
-
import { readFileSync as
|
|
94167
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
93820
94168
|
import { extname } from "path";
|
|
93821
94169
|
function parseRrwebSession(events) {
|
|
93822
94170
|
const result = [];
|
|
@@ -94001,7 +94349,7 @@ ${condensed}`;
|
|
|
94001
94349
|
};
|
|
94002
94350
|
}
|
|
94003
94351
|
async function convertSessionFile(filePath, format, options) {
|
|
94004
|
-
const raw =
|
|
94352
|
+
const raw = readFileSync10(filePath, "utf-8");
|
|
94005
94353
|
let parsed;
|
|
94006
94354
|
try {
|
|
94007
94355
|
parsed = JSON.parse(raw);
|
|
@@ -94035,7 +94383,7 @@ function detectSessionFormat(filePath) {
|
|
|
94035
94383
|
if (ext === ".har")
|
|
94036
94384
|
return "har";
|
|
94037
94385
|
try {
|
|
94038
|
-
const content =
|
|
94386
|
+
const content = readFileSync10(filePath, "utf-8").trim();
|
|
94039
94387
|
const parsed = JSON.parse(content);
|
|
94040
94388
|
if (Array.isArray(parsed) && parsed[0]?.type !== undefined && typeof parsed[0]?.timestamp === "number") {
|
|
94041
94389
|
return "rrweb";
|
|
@@ -94089,28 +94437,28 @@ function checkResultFromRow(row) {
|
|
|
94089
94437
|
};
|
|
94090
94438
|
}
|
|
94091
94439
|
function createGoldenAnswer(input) {
|
|
94092
|
-
const
|
|
94440
|
+
const db3 = getDatabase();
|
|
94093
94441
|
const id = uuid();
|
|
94094
94442
|
const short_id = shortUuid();
|
|
94095
94443
|
const timestamp = now();
|
|
94096
|
-
|
|
94444
|
+
db3.query(`
|
|
94097
94445
|
INSERT INTO golden_answers (id, short_id, project_id, question, golden_answer, constraints, endpoint, judge_model, enabled, created_at, updated_at)
|
|
94098
94446
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94099
94447
|
`).run(id, short_id, input.projectId ?? null, input.question, input.goldenAnswer, JSON.stringify(input.constraints ?? []), input.endpoint, input.judgeModel ?? null, input.enabled === false ? 0 : 1, timestamp, timestamp);
|
|
94100
94448
|
return getGoldenAnswer(id);
|
|
94101
94449
|
}
|
|
94102
94450
|
function getGoldenAnswer(id) {
|
|
94103
|
-
const
|
|
94104
|
-
let row =
|
|
94451
|
+
const db3 = getDatabase();
|
|
94452
|
+
let row = db3.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
|
|
94105
94453
|
if (row)
|
|
94106
94454
|
return goldenFromRow(row);
|
|
94107
|
-
row =
|
|
94455
|
+
row = db3.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
|
|
94108
94456
|
if (row)
|
|
94109
94457
|
return goldenFromRow(row);
|
|
94110
94458
|
return null;
|
|
94111
94459
|
}
|
|
94112
94460
|
function listGoldenAnswers(filter2) {
|
|
94113
|
-
const
|
|
94461
|
+
const db3 = getDatabase();
|
|
94114
94462
|
const conditions = [];
|
|
94115
94463
|
const params = [];
|
|
94116
94464
|
if (filter2?.projectId) {
|
|
@@ -94126,11 +94474,11 @@ function listGoldenAnswers(filter2) {
|
|
|
94126
94474
|
sql += " WHERE " + conditions.join(" AND ");
|
|
94127
94475
|
}
|
|
94128
94476
|
sql += " ORDER BY created_at DESC";
|
|
94129
|
-
const rows =
|
|
94477
|
+
const rows = db3.query(sql).all(...params);
|
|
94130
94478
|
return rows.map(goldenFromRow);
|
|
94131
94479
|
}
|
|
94132
94480
|
function updateGoldenAnswer(id, input) {
|
|
94133
|
-
const
|
|
94481
|
+
const db3 = getDatabase();
|
|
94134
94482
|
const timestamp = now();
|
|
94135
94483
|
const sets = ["updated_at = ?"];
|
|
94136
94484
|
const params = [timestamp];
|
|
@@ -94159,26 +94507,26 @@ function updateGoldenAnswer(id, input) {
|
|
|
94159
94507
|
params.push(input.enabled ? 1 : 0);
|
|
94160
94508
|
}
|
|
94161
94509
|
params.push(id);
|
|
94162
|
-
|
|
94510
|
+
db3.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
|
|
94163
94511
|
return getGoldenAnswer(id);
|
|
94164
94512
|
}
|
|
94165
94513
|
function deleteGoldenAnswer(id) {
|
|
94166
|
-
const
|
|
94167
|
-
const result =
|
|
94514
|
+
const db3 = getDatabase();
|
|
94515
|
+
const result = db3.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
|
|
94168
94516
|
return (result.changes ?? 0) > 0;
|
|
94169
94517
|
}
|
|
94170
94518
|
function createGoldenCheckResult(input) {
|
|
94171
|
-
const
|
|
94519
|
+
const db3 = getDatabase();
|
|
94172
94520
|
const id = uuid();
|
|
94173
94521
|
const timestamp = now();
|
|
94174
|
-
|
|
94522
|
+
db3.query(`
|
|
94175
94523
|
INSERT INTO golden_check_results (id, golden_id, response, similarity_score, passed, drift_detected, judge_model, provider, created_at)
|
|
94176
94524
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94177
94525
|
`).run(id, input.goldenId, input.response, input.similarityScore ?? null, input.passed ? 1 : 0, input.driftDetected ? 1 : 0, input.judgeModel ?? null, input.provider ?? null, timestamp);
|
|
94178
94526
|
return listGoldenCheckResults(input.goldenId, { limit: 1 })[0];
|
|
94179
94527
|
}
|
|
94180
94528
|
function listGoldenCheckResults(goldenId, options) {
|
|
94181
|
-
const
|
|
94529
|
+
const db3 = getDatabase();
|
|
94182
94530
|
const params = [goldenId];
|
|
94183
94531
|
let sql = "SELECT * FROM golden_check_results WHERE golden_id = ?";
|
|
94184
94532
|
if (options?.since) {
|
|
@@ -94190,7 +94538,7 @@ function listGoldenCheckResults(goldenId, options) {
|
|
|
94190
94538
|
sql += " LIMIT ?";
|
|
94191
94539
|
params.push(options.limit);
|
|
94192
94540
|
}
|
|
94193
|
-
const rows =
|
|
94541
|
+
const rows = db3.query(sql).all(...params);
|
|
94194
94542
|
return rows.map(checkResultFromRow);
|
|
94195
94543
|
}
|
|
94196
94544
|
function getLatestGoldenCheckResult(goldenId) {
|
|
@@ -94479,7 +94827,7 @@ import chalk6 from "chalk";
|
|
|
94479
94827
|
// package.json
|
|
94480
94828
|
var package_default = {
|
|
94481
94829
|
name: "@hasna/testers",
|
|
94482
|
-
version: "0.0.
|
|
94830
|
+
version: "0.0.49",
|
|
94483
94831
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
94484
94832
|
type: "module",
|
|
94485
94833
|
main: "dist/index.js",
|
|
@@ -94578,9 +94926,9 @@ init_todos_connector();
|
|
|
94578
94926
|
init_browser();
|
|
94579
94927
|
import { render, Box, Text, useInput, useApp } from "ink";
|
|
94580
94928
|
import React, { useState } from "react";
|
|
94581
|
-
import { readFileSync as
|
|
94929
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
94582
94930
|
import { createInterface } from "readline";
|
|
94583
|
-
import { join as
|
|
94931
|
+
import { join as join21, resolve as resolve5 } from "path";
|
|
94584
94932
|
|
|
94585
94933
|
// src/lib/init.ts
|
|
94586
94934
|
init_paths();
|
|
@@ -96613,51 +96961,51 @@ function fromRow3(row) {
|
|
|
96613
96961
|
};
|
|
96614
96962
|
}
|
|
96615
96963
|
function createEnvironment(input) {
|
|
96616
|
-
const
|
|
96964
|
+
const db3 = getDatabase();
|
|
96617
96965
|
const id = uuid();
|
|
96618
96966
|
const timestamp = now();
|
|
96619
96967
|
const meta = JSON.stringify({ variables: input.variables ?? {} });
|
|
96620
|
-
|
|
96968
|
+
db3.query(`
|
|
96621
96969
|
INSERT INTO environments (id, name, url, auth_preset_name, project_id, is_default, metadata, created_at)
|
|
96622
96970
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
96623
96971
|
`).run(id, input.name, input.url, input.authPresetName ?? null, input.projectId ?? null, input.isDefault ? 1 : 0, meta, timestamp);
|
|
96624
96972
|
return getEnvironmentById(id);
|
|
96625
96973
|
}
|
|
96626
96974
|
function getEnvironmentById(id) {
|
|
96627
|
-
const
|
|
96628
|
-
const row =
|
|
96975
|
+
const db3 = getDatabase();
|
|
96976
|
+
const row = db3.query("SELECT * FROM environments WHERE id = ?").get(id);
|
|
96629
96977
|
return row ? fromRow3(row) : null;
|
|
96630
96978
|
}
|
|
96631
96979
|
function getEnvironment(name) {
|
|
96632
|
-
const
|
|
96633
|
-
const row =
|
|
96980
|
+
const db3 = getDatabase();
|
|
96981
|
+
const row = db3.query("SELECT * FROM environments WHERE name = ?").get(name);
|
|
96634
96982
|
return row ? fromRow3(row) : null;
|
|
96635
96983
|
}
|
|
96636
96984
|
function listEnvironments(projectId) {
|
|
96637
|
-
const
|
|
96985
|
+
const db3 = getDatabase();
|
|
96638
96986
|
if (projectId) {
|
|
96639
|
-
const rows2 =
|
|
96987
|
+
const rows2 = db3.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
|
|
96640
96988
|
return rows2.map(fromRow3);
|
|
96641
96989
|
}
|
|
96642
|
-
const rows =
|
|
96990
|
+
const rows = db3.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
|
|
96643
96991
|
return rows.map(fromRow3);
|
|
96644
96992
|
}
|
|
96645
96993
|
function deleteEnvironment(name) {
|
|
96646
|
-
const
|
|
96647
|
-
const result =
|
|
96994
|
+
const db3 = getDatabase();
|
|
96995
|
+
const result = db3.query("DELETE FROM environments WHERE name = ?").run(name);
|
|
96648
96996
|
return result.changes > 0;
|
|
96649
96997
|
}
|
|
96650
96998
|
function setDefaultEnvironment(name) {
|
|
96651
|
-
const
|
|
96652
|
-
|
|
96653
|
-
const result =
|
|
96999
|
+
const db3 = getDatabase();
|
|
97000
|
+
db3.exec("UPDATE environments SET is_default = 0");
|
|
97001
|
+
const result = db3.query("UPDATE environments SET is_default = 1 WHERE name = ?").run(name);
|
|
96654
97002
|
if (result.changes === 0) {
|
|
96655
97003
|
throw new Error(`Environment not found: ${name}`);
|
|
96656
97004
|
}
|
|
96657
97005
|
}
|
|
96658
97006
|
function getDefaultEnvironment() {
|
|
96659
|
-
const
|
|
96660
|
-
const row =
|
|
97007
|
+
const db3 = getDatabase();
|
|
97008
|
+
const row = db3.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
|
|
96661
97009
|
return row ? fromRow3(row) : null;
|
|
96662
97010
|
}
|
|
96663
97011
|
|
|
@@ -96666,7 +97014,7 @@ init_ci();
|
|
|
96666
97014
|
init_assertions();
|
|
96667
97015
|
init_paths();
|
|
96668
97016
|
init_sessions();
|
|
96669
|
-
import { existsSync as
|
|
97017
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15 } from "fs";
|
|
96670
97018
|
|
|
96671
97019
|
// src/lib/repo-discovery.ts
|
|
96672
97020
|
init_paths();
|
|
@@ -97224,12 +97572,12 @@ async function runRepoTests(opts) {
|
|
|
97224
97572
|
specResults.push(result);
|
|
97225
97573
|
const resultId = uuid();
|
|
97226
97574
|
const timestamp = now();
|
|
97227
|
-
const
|
|
97228
|
-
|
|
97575
|
+
const db3 = getDatabase();
|
|
97576
|
+
db3.exec("PRAGMA foreign_keys = OFF");
|
|
97229
97577
|
try {
|
|
97230
97578
|
const reasoning = result.status === "passed" ? "All tests passed" : (result.error ?? "").slice(0, 500) || null;
|
|
97231
97579
|
const errorStr = result.status !== "passed" ? result.error ?? null : null;
|
|
97232
|
-
|
|
97580
|
+
db3.query(`
|
|
97233
97581
|
INSERT INTO results (id, run_id, scenario_id, status, reasoning, error, steps_completed, steps_total, duration_ms, model, tokens_used, cost_cents, metadata, created_at, persona_id, persona_name)
|
|
97234
97582
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, NULL, NULL)
|
|
97235
97583
|
`).run(resultId, run.id, "__repo__", result.status, reasoning, errorStr, result.testResults.filter((t) => t.status === "passed").length, result.testResults.length || 1, result.durationMs, "repo-native", JSON.stringify({
|
|
@@ -97238,7 +97586,7 @@ async function runRepoTests(opts) {
|
|
|
97238
97586
|
testResults: result.testResults
|
|
97239
97587
|
}), timestamp);
|
|
97240
97588
|
} finally {
|
|
97241
|
-
|
|
97589
|
+
db3.exec("PRAGMA foreign_keys = ON");
|
|
97242
97590
|
}
|
|
97243
97591
|
const resultRecord = { id: resultId };
|
|
97244
97592
|
if (result.stdout || result.stderr) {
|
|
@@ -97385,21 +97733,31 @@ function envCredentialRef(value) {
|
|
|
97385
97733
|
return;
|
|
97386
97734
|
return trimmed.startsWith("$") ? trimmed : `$${trimmed}`;
|
|
97387
97735
|
}
|
|
97388
|
-
function
|
|
97389
|
-
if (
|
|
97736
|
+
function assertEnvVarName(key, rawValue) {
|
|
97737
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
97738
|
+
throw new Error(`Invalid sandbox env var name: ${key || rawValue}`);
|
|
97739
|
+
}
|
|
97740
|
+
}
|
|
97741
|
+
function parseSandboxEnv(values, optionalValues = []) {
|
|
97742
|
+
if (!values?.length && !optionalValues?.length)
|
|
97390
97743
|
return;
|
|
97391
97744
|
const env = {};
|
|
97392
|
-
for (const value of values) {
|
|
97745
|
+
for (const value of values ?? []) {
|
|
97393
97746
|
const trimmed = value.trim();
|
|
97394
97747
|
if (!trimmed)
|
|
97395
97748
|
continue;
|
|
97396
97749
|
const separator = trimmed.indexOf("=");
|
|
97397
97750
|
const key = separator >= 0 ? trimmed.slice(0, separator).trim() : trimmed;
|
|
97398
|
-
|
|
97399
|
-
throw new Error(`Invalid sandbox env var name: ${key || value}`);
|
|
97400
|
-
}
|
|
97751
|
+
assertEnvVarName(key, value);
|
|
97401
97752
|
env[key] = separator >= 0 ? trimmed.slice(separator + 1) : `$${key}`;
|
|
97402
97753
|
}
|
|
97754
|
+
for (const value of optionalValues ?? []) {
|
|
97755
|
+
const key = value.trim();
|
|
97756
|
+
if (!key)
|
|
97757
|
+
continue;
|
|
97758
|
+
assertEnvVarName(key, value);
|
|
97759
|
+
env[key] = `$?${key}`;
|
|
97760
|
+
}
|
|
97403
97761
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
97404
97762
|
}
|
|
97405
97763
|
function AddForm({ onComplete }) {
|
|
@@ -97675,18 +98033,18 @@ program2.command("prod-debug <target>").description("Create a safe production de
|
|
|
97675
98033
|
}, config2.prodDebug);
|
|
97676
98034
|
const output = opts.json ? JSON.stringify(plan, null, 2) : formatProdDebugPlan(plan);
|
|
97677
98035
|
if (opts.output) {
|
|
97678
|
-
writeFileSync7(
|
|
98036
|
+
writeFileSync7(resolve5(opts.output), output + `
|
|
97679
98037
|
`);
|
|
97680
98038
|
} else {
|
|
97681
98039
|
log(output);
|
|
97682
98040
|
}
|
|
97683
98041
|
});
|
|
97684
98042
|
var CONFIG_DIR5 = getTestersDir();
|
|
97685
|
-
var CONFIG_PATH4 =
|
|
98043
|
+
var CONFIG_PATH4 = join21(CONFIG_DIR5, "config.json");
|
|
97686
98044
|
function getActiveProject() {
|
|
97687
98045
|
try {
|
|
97688
|
-
if (
|
|
97689
|
-
const raw = JSON.parse(
|
|
98046
|
+
if (existsSync19(CONFIG_PATH4)) {
|
|
98047
|
+
const raw = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
|
|
97690
98048
|
return raw.activeProject ?? undefined;
|
|
97691
98049
|
}
|
|
97692
98050
|
} catch {}
|
|
@@ -97893,7 +98251,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
|
|
|
97893
98251
|
}
|
|
97894
98252
|
if (!opts.yes) {
|
|
97895
98253
|
process.stdout.write(chalk6.yellow(`Delete scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
|
|
97896
|
-
const answer = await new Promise((
|
|
98254
|
+
const answer = await new Promise((resolve6) => {
|
|
97897
98255
|
let buf = "";
|
|
97898
98256
|
process.stdin.setRawMode?.(true);
|
|
97899
98257
|
process.stdin.resume();
|
|
@@ -97903,7 +98261,7 @@ program2.command("delete <id>").description("Delete a scenario").option("-y, --y
|
|
|
97903
98261
|
process.stdin.pause();
|
|
97904
98262
|
process.stdout.write(`
|
|
97905
98263
|
`);
|
|
97906
|
-
|
|
98264
|
+
resolve6(buf);
|
|
97907
98265
|
});
|
|
97908
98266
|
});
|
|
97909
98267
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -97932,7 +98290,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
|
|
|
97932
98290
|
}
|
|
97933
98291
|
if (!opts.yes) {
|
|
97934
98292
|
process.stdout.write(chalk6.yellow(`Remove scenario ${scenario.shortId} "${scenario.name}"? [y/N] `));
|
|
97935
|
-
const answer = await new Promise((
|
|
98293
|
+
const answer = await new Promise((resolve6) => {
|
|
97936
98294
|
let buf = "";
|
|
97937
98295
|
process.stdin.setRawMode?.(true);
|
|
97938
98296
|
process.stdin.resume();
|
|
@@ -97942,7 +98300,7 @@ program2.command("remove <id>").alias("uninstall").description("Remove a scenari
|
|
|
97942
98300
|
process.stdin.pause();
|
|
97943
98301
|
process.stdout.write(`
|
|
97944
98302
|
`);
|
|
97945
|
-
|
|
98303
|
+
resolve6(buf);
|
|
97946
98304
|
});
|
|
97947
98305
|
});
|
|
97948
98306
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -98145,7 +98503,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
98145
98503
|
`);
|
|
98146
98504
|
}
|
|
98147
98505
|
};
|
|
98148
|
-
await new Promise((
|
|
98506
|
+
await new Promise((resolve6) => {
|
|
98149
98507
|
const poll = setInterval(() => {
|
|
98150
98508
|
const run3 = getRun(runId);
|
|
98151
98509
|
if (!run3)
|
|
@@ -98153,7 +98511,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
98153
98511
|
renderTable();
|
|
98154
98512
|
if (DONE_STATUSES.has(run3.status)) {
|
|
98155
98513
|
clearInterval(poll);
|
|
98156
|
-
|
|
98514
|
+
resolve6();
|
|
98157
98515
|
}
|
|
98158
98516
|
}, POLL_INTERVAL);
|
|
98159
98517
|
});
|
|
@@ -98548,15 +98906,15 @@ program2.command("screenshots <id>").description("List screenshots for a run or
|
|
|
98548
98906
|
});
|
|
98549
98907
|
program2.command("import <dir>").description("Import markdown test files as scenarios").action((dir) => {
|
|
98550
98908
|
try {
|
|
98551
|
-
const absDir =
|
|
98552
|
-
const files =
|
|
98909
|
+
const absDir = resolve5(dir);
|
|
98910
|
+
const files = readdirSync7(absDir).filter((f2) => f2.endsWith(".md"));
|
|
98553
98911
|
if (files.length === 0) {
|
|
98554
98912
|
log(chalk6.dim("No .md files found in directory."));
|
|
98555
98913
|
return;
|
|
98556
98914
|
}
|
|
98557
98915
|
let imported = 0;
|
|
98558
98916
|
for (const file2 of files) {
|
|
98559
|
-
const content =
|
|
98917
|
+
const content = readFileSync11(join21(absDir, file2), "utf-8");
|
|
98560
98918
|
const lines = content.split(`
|
|
98561
98919
|
`);
|
|
98562
98920
|
let name21 = file2.replace(/\.md$/, "");
|
|
@@ -98612,11 +98970,11 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
|
|
|
98612
98970
|
const outputPath = opts.output ?? "testers-export.json";
|
|
98613
98971
|
const data = JSON.stringify(scenarios, null, 2);
|
|
98614
98972
|
writeFileSync7(outputPath, data, "utf-8");
|
|
98615
|
-
log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${
|
|
98973
|
+
log(chalk6.green(`Exported ${scenarios.length} scenario(s) to ${resolve5(outputPath)}`));
|
|
98616
98974
|
return;
|
|
98617
98975
|
}
|
|
98618
98976
|
const outputDir = opts.output ?? ".";
|
|
98619
|
-
if (!
|
|
98977
|
+
if (!existsSync19(outputDir)) {
|
|
98620
98978
|
mkdirSync15(outputDir, { recursive: true });
|
|
98621
98979
|
}
|
|
98622
98980
|
for (const s2 of scenarios) {
|
|
@@ -98644,13 +99002,13 @@ program2.command("export [format]").description("Export scenarios as JSON (defau
|
|
|
98644
99002
|
lines.push("");
|
|
98645
99003
|
}
|
|
98646
99004
|
const safeFilename = s2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
|
|
98647
|
-
const filePath =
|
|
99005
|
+
const filePath = join21(outputDir, `${s2.shortId}-${safeFilename}.md`);
|
|
98648
99006
|
writeFileSync7(filePath, lines.join(`
|
|
98649
99007
|
`), "utf-8");
|
|
98650
99008
|
log(chalk6.dim(` ${s2.shortId}: ${s2.name} \u2192 ${filePath}`));
|
|
98651
99009
|
}
|
|
98652
99010
|
log(chalk6.green(`
|
|
98653
|
-
Exported ${scenarios.length} scenario(s) as markdown to ${
|
|
99011
|
+
Exported ${scenarios.length} scenario(s) as markdown to ${resolve5(outputDir)}`));
|
|
98654
99012
|
} catch (error40) {
|
|
98655
99013
|
logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
|
|
98656
99014
|
process.exit(1);
|
|
@@ -98669,7 +99027,7 @@ program2.command("status").description("Show database and auth status").action((
|
|
|
98669
99027
|
try {
|
|
98670
99028
|
const config2 = loadConfig();
|
|
98671
99029
|
const hasApiKey = !!config2.anthropicApiKey || !!process.env["ANTHROPIC_API_KEY"];
|
|
98672
|
-
const dbPath =
|
|
99030
|
+
const dbPath = join21(getTestersDir(), "testers.db");
|
|
98673
99031
|
log("");
|
|
98674
99032
|
log(chalk6.bold(" Open Testers Status"));
|
|
98675
99033
|
log("");
|
|
@@ -98819,13 +99177,13 @@ projectCmd.command("export-open <id>").description("Register a testers project i
|
|
|
98819
99177
|
projectCmd.command("use <name>").description("Set active project (find or create)").option("--json", "Output as JSON", false).action((name21, opts) => {
|
|
98820
99178
|
try {
|
|
98821
99179
|
const project = ensureProject(name21, process.cwd());
|
|
98822
|
-
if (!
|
|
99180
|
+
if (!existsSync19(CONFIG_DIR5)) {
|
|
98823
99181
|
mkdirSync15(CONFIG_DIR5, { recursive: true });
|
|
98824
99182
|
}
|
|
98825
99183
|
let config2 = {};
|
|
98826
|
-
if (
|
|
99184
|
+
if (existsSync19(CONFIG_PATH4)) {
|
|
98827
99185
|
try {
|
|
98828
|
-
config2 = JSON.parse(
|
|
99186
|
+
config2 = JSON.parse(readFileSync11(CONFIG_PATH4, "utf-8"));
|
|
98829
99187
|
} catch {}
|
|
98830
99188
|
}
|
|
98831
99189
|
config2.activeProject = project.id;
|
|
@@ -98843,7 +99201,7 @@ projectCmd.command("use <name>").description("Set active project (find or create
|
|
|
98843
99201
|
var repoCmd = program2.command("repo").description("Discover and run repo-native Playwright tests");
|
|
98844
99202
|
repoCmd.command("discover [path]").alias("scan").description("Discover Playwright tests in a repo").option("--refresh", "Force a fresh scan, ignoring cache", false).option("--json", "Output as JSON", false).option("--base-url <url>", "Override the suggested base URL").action((path, opts) => {
|
|
98845
99203
|
try {
|
|
98846
|
-
const repoPath =
|
|
99204
|
+
const repoPath = resolve5(path ?? process.cwd());
|
|
98847
99205
|
const snapshot = discoverRepo({
|
|
98848
99206
|
repoPath,
|
|
98849
99207
|
refresh: opts.refresh,
|
|
@@ -98909,7 +99267,7 @@ repoCmd.command("discover [path]").alias("scan").description("Discover Playwrigh
|
|
|
98909
99267
|
});
|
|
98910
99268
|
repoCmd.command("prepare [path]").alias("prep").description("Install dependencies and browsers for repo tests").option("--all", "Run all prep steps (install, browsers, build, seed)", false).option("--install", "Install dependencies", false).option("--browsers", "Install Playwright browsers", false).option("--build", "Build the app", false).option("--seed", "Seed the database", false).option("--refresh", "Force fresh discovery scan", false).option("--json", "Output as JSON", false).action((path, opts) => {
|
|
98911
99269
|
try {
|
|
98912
|
-
const repoPath =
|
|
99270
|
+
const repoPath = resolve5(path ?? process.cwd());
|
|
98913
99271
|
const snapshot = discoverRepo({ repoPath, refresh: opts.refresh });
|
|
98914
99272
|
const steps = [];
|
|
98915
99273
|
if (opts.all) {
|
|
@@ -98987,7 +99345,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
|
|
|
98987
99345
|
return acc;
|
|
98988
99346
|
}, []).option("--timeout <ms>", "Timeout per spec file", "300000").option("--url <url>", "Dev server URL").option("--project <id>", "Project ID for result storage").option("--label <text>", "Run label").option("--json", "Output as JSON", false).action(async (path, opts) => {
|
|
98989
99347
|
try {
|
|
98990
|
-
const repoPath =
|
|
99348
|
+
const repoPath = resolve5(path ?? process.cwd());
|
|
98991
99349
|
const snapshot = discoverRepo({
|
|
98992
99350
|
repoPath,
|
|
98993
99351
|
refresh: opts.refresh,
|
|
@@ -99053,7 +99411,7 @@ repoCmd.command("run [path]").description("Run discovered Playwright tests nativ
|
|
|
99053
99411
|
repoCmd.command("cache [path]").description("Manage discovery cache").option("--clear", "Clear discovery cache", false).option("--status", "Show cache status", false).action((path, opts) => {
|
|
99054
99412
|
try {
|
|
99055
99413
|
if (opts.clear) {
|
|
99056
|
-
const repoPath2 = path ?
|
|
99414
|
+
const repoPath2 = path ? resolve5(path) : undefined;
|
|
99057
99415
|
clearDiscoveryCache(repoPath2);
|
|
99058
99416
|
if (repoPath2) {
|
|
99059
99417
|
log(chalk6.green("Discovery cache cleared for this repo."));
|
|
@@ -99063,7 +99421,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
|
|
|
99063
99421
|
return;
|
|
99064
99422
|
}
|
|
99065
99423
|
if (opts.status) {
|
|
99066
|
-
const repoPath2 =
|
|
99424
|
+
const repoPath2 = resolve5(path ?? process.cwd());
|
|
99067
99425
|
const info2 = getDiscoveryCacheInfo(repoPath2);
|
|
99068
99426
|
if (!info2) {
|
|
99069
99427
|
log(chalk6.dim("No discovery cache for this repo."));
|
|
@@ -99077,7 +99435,7 @@ repoCmd.command("cache [path]").description("Manage discovery cache").option("--
|
|
|
99077
99435
|
log("");
|
|
99078
99436
|
return;
|
|
99079
99437
|
}
|
|
99080
|
-
const repoPath =
|
|
99438
|
+
const repoPath = resolve5(path ?? process.cwd());
|
|
99081
99439
|
const info = getDiscoveryCacheInfo(repoPath);
|
|
99082
99440
|
if (!info) {
|
|
99083
99441
|
log(chalk6.dim("No discovery cache. Run 'testers repo discover' to create one."));
|
|
@@ -99166,8 +99524,8 @@ sessionCmd.command("show <id>").description("Show details of a recorded session"
|
|
|
99166
99524
|
});
|
|
99167
99525
|
sessionCmd.command("import <file>").description("Import a session JSON file exported from the Chrome extension").action(async (file2) => {
|
|
99168
99526
|
try {
|
|
99169
|
-
const { readFileSync:
|
|
99170
|
-
const raw =
|
|
99527
|
+
const { readFileSync: readFileSync12 } = await import("fs");
|
|
99528
|
+
const raw = readFileSync12(file2, "utf-8");
|
|
99171
99529
|
const data = JSON.parse(raw);
|
|
99172
99530
|
const items = Array.isArray(data) ? data : [data];
|
|
99173
99531
|
const { createSession: createSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
@@ -99407,7 +99765,7 @@ program2.command("daemon").description("Start the scheduler daemon").option("--i
|
|
|
99407
99765
|
} catch (err) {
|
|
99408
99766
|
logError(chalk6.red(`Daemon error: ${err instanceof Error ? err.message : String(err)}`));
|
|
99409
99767
|
}
|
|
99410
|
-
await new Promise((
|
|
99768
|
+
await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
|
|
99411
99769
|
}
|
|
99412
99770
|
};
|
|
99413
99771
|
process.on("SIGINT", () => {
|
|
@@ -99436,9 +99794,9 @@ program2.command("ci [provider]").description("Print or write a CI workflow (def
|
|
|
99436
99794
|
}
|
|
99437
99795
|
const workflow = generateGitHubActionsWorkflow();
|
|
99438
99796
|
if (opts.output) {
|
|
99439
|
-
const outPath =
|
|
99797
|
+
const outPath = resolve5(opts.output);
|
|
99440
99798
|
const outDir = outPath.replace(/\/[^/]*$/, "");
|
|
99441
|
-
if (outDir && !
|
|
99799
|
+
if (outDir && !existsSync19(outDir)) {
|
|
99442
99800
|
mkdirSync15(outDir, { recursive: true });
|
|
99443
99801
|
}
|
|
99444
99802
|
writeFileSync7(outPath, workflow, "utf-8");
|
|
@@ -99472,11 +99830,11 @@ program2.command("init").description("Initialize a new testing project").option(
|
|
|
99472
99830
|
log(` ${chalk6.dim(s2.shortId)} ${s2.name} ${chalk6.dim(`[${s2.tags.join(", ")}]`)}`);
|
|
99473
99831
|
}
|
|
99474
99832
|
if (opts.ci === "github") {
|
|
99475
|
-
const workflowDir =
|
|
99476
|
-
if (!
|
|
99833
|
+
const workflowDir = join21(process.cwd(), ".github", "workflows");
|
|
99834
|
+
if (!existsSync19(workflowDir)) {
|
|
99477
99835
|
mkdirSync15(workflowDir, { recursive: true });
|
|
99478
99836
|
}
|
|
99479
|
-
const workflowPath =
|
|
99837
|
+
const workflowPath = join21(workflowDir, "testers.yml");
|
|
99480
99838
|
writeFileSync7(workflowPath, generateGitHubActionsWorkflow(), "utf-8");
|
|
99481
99839
|
log(` CI: ${chalk6.green("GitHub Actions workflow written to .github/workflows/testers.yml")}`);
|
|
99482
99840
|
} else if (opts.ci) {
|
|
@@ -99486,7 +99844,7 @@ program2.command("init").description("Initialize a new testing project").option(
|
|
|
99486
99844
|
if (opts.yes)
|
|
99487
99845
|
return;
|
|
99488
99846
|
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
99489
|
-
const ask = (q2) => new Promise((
|
|
99847
|
+
const ask = (q2) => new Promise((resolve6) => rl2.question(q2, resolve6));
|
|
99490
99848
|
try {
|
|
99491
99849
|
const envAnswer = await ask(" Would you like to configure environments? [y/N] ");
|
|
99492
99850
|
if (envAnswer.trim().toLowerCase() === "y") {
|
|
@@ -99670,14 +100028,14 @@ program2.command("quick-qa <url>").alias("quick-check").description("Run a fast
|
|
|
99670
100028
|
wcagLevel
|
|
99671
100029
|
});
|
|
99672
100030
|
if (opts.output) {
|
|
99673
|
-
writeFileSync7(
|
|
100031
|
+
writeFileSync7(resolve5(opts.output), JSON.stringify(result, null, 2));
|
|
99674
100032
|
}
|
|
99675
100033
|
if (opts.json) {
|
|
99676
100034
|
log(JSON.stringify(result, null, 2));
|
|
99677
100035
|
} else {
|
|
99678
100036
|
log(formatQuickQaReport(result));
|
|
99679
100037
|
if (opts.output)
|
|
99680
|
-
log(chalk6.dim(`Wrote JSON results to ${
|
|
100038
|
+
log(chalk6.dim(`Wrote JSON results to ${resolve5(opts.output)}`));
|
|
99681
100039
|
}
|
|
99682
100040
|
process.exit(getQuickQaExitCode(result));
|
|
99683
100041
|
} catch (error40) {
|
|
@@ -99722,7 +100080,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
|
|
|
99722
100080
|
});
|
|
99723
100081
|
if (opts.output && opts.output !== "report.html") {
|
|
99724
100082
|
writeFileSync7(opts.output, content, "utf-8");
|
|
99725
|
-
const absPath2 =
|
|
100083
|
+
const absPath2 = resolve5(opts.output);
|
|
99726
100084
|
log(chalk6.green(`Compliance report written to ${absPath2}`));
|
|
99727
100085
|
} else {
|
|
99728
100086
|
log(content);
|
|
@@ -99736,7 +100094,7 @@ program2.command("report [run-id]").description("Generate HTML test report or co
|
|
|
99736
100094
|
html = generateHtmlReport(runId);
|
|
99737
100095
|
}
|
|
99738
100096
|
writeFileSync7(opts.output, html, "utf-8");
|
|
99739
|
-
const absPath =
|
|
100097
|
+
const absPath = resolve5(opts.output);
|
|
99740
100098
|
log(chalk6.green(`Report generated: ${absPath}`));
|
|
99741
100099
|
if (opts.open) {
|
|
99742
100100
|
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
@@ -100093,6 +100451,62 @@ Imported ${imported} scenarios from API spec:`));
|
|
|
100093
100451
|
process.exit(1);
|
|
100094
100452
|
}
|
|
100095
100453
|
});
|
|
100454
|
+
var inventoryCmd = program2.command("inventory").description("Discover source-derived app route/action inventories");
|
|
100455
|
+
inventoryCmd.command("next [root]").description("Discover Next.js app routes and optionally import route coverage scenarios").option("--app-dir <path>", "Next.js app directory relative to root (default: packages/web/app or app)").option("--project <id>", "Project ID").option("--no-pages", "Do not include page.tsx/page.ts routes").option("--no-api", "Do not include route.ts/route.js API routes").option("--limit <n>", "Limit discovered routes").option("--create-scenarios", "Upsert source-derived route coverage scenarios", false).option("--create-workflows", "Upsert grouped workflows by area and route kind", false).option("--workflow-target <target>", "Workflow execution target: local or sandbox", "sandbox").option("--sandbox-provider <provider>", "Sandbox provider for created workflows", "e2b").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
100456
|
+
acc.push(val);
|
|
100457
|
+
return acc;
|
|
100458
|
+
}, []).option("--timeout <ms>", "Workflow timeout in milliseconds").option("--json", "Output as JSON", false).action(async (root, opts) => {
|
|
100459
|
+
try {
|
|
100460
|
+
const { importNextRouteInventory: importNextRouteInventory2 } = await Promise.resolve().then(() => (init_next_route_inventory(), exports_next_route_inventory));
|
|
100461
|
+
const projectId = resolveProject2(opts.project) ?? undefined;
|
|
100462
|
+
const env = parseSandboxEnv(undefined, opts.sandboxEnvOptional);
|
|
100463
|
+
const result = importNextRouteInventory2({
|
|
100464
|
+
rootDir: root ?? process.cwd(),
|
|
100465
|
+
appDir: opts.appDir,
|
|
100466
|
+
projectId,
|
|
100467
|
+
includePages: opts.pages !== false,
|
|
100468
|
+
includeApi: opts.api !== false,
|
|
100469
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
100470
|
+
createScenarios: opts.createScenarios,
|
|
100471
|
+
createWorkflows: opts.createWorkflows,
|
|
100472
|
+
workflowTarget: opts.workflowTarget,
|
|
100473
|
+
workflowProvider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
|
|
100474
|
+
workflowExecution: {
|
|
100475
|
+
target: opts.workflowTarget,
|
|
100476
|
+
provider: opts.workflowTarget === "sandbox" ? opts.sandboxProvider : undefined,
|
|
100477
|
+
sandboxCleanup: opts.sandboxCleanup,
|
|
100478
|
+
sandboxSyncStrategy: opts.sandboxSync,
|
|
100479
|
+
timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
100480
|
+
env
|
|
100481
|
+
}
|
|
100482
|
+
});
|
|
100483
|
+
if (opts.json) {
|
|
100484
|
+
log(JSON.stringify(result, null, 2));
|
|
100485
|
+
return;
|
|
100486
|
+
}
|
|
100487
|
+
log("");
|
|
100488
|
+
log(chalk6.bold(" Next.js Route Inventory"));
|
|
100489
|
+
log(chalk6.dim(` Root: ${result.inventory.rootDir}`));
|
|
100490
|
+
log(chalk6.dim(` App: ${result.inventory.appDir}`));
|
|
100491
|
+
log("");
|
|
100492
|
+
log(` Routes: ${chalk6.cyan(String(result.inventory.total))} (${result.inventory.pages} pages, ${result.inventory.apiRoutes} API, ${result.inventory.dynamic} dynamic)`);
|
|
100493
|
+
log(` Scenarios: ${chalk6.green(String(result.created))} created, ${chalk6.yellow(String(result.updated))} updated, ${chalk6.dim(String(result.deduped))} deduped`);
|
|
100494
|
+
log(` Workflows: ${result.workflows.length}`);
|
|
100495
|
+
log("");
|
|
100496
|
+
for (const [category, count] of Object.entries(result.inventory.categories).sort()) {
|
|
100497
|
+
log(` ${category.padEnd(18)} ${count}`);
|
|
100498
|
+
}
|
|
100499
|
+
log("");
|
|
100500
|
+
if (!opts.createScenarios)
|
|
100501
|
+
log(chalk6.dim(" Add --create-scenarios to upsert route scenarios."));
|
|
100502
|
+
if (!opts.createWorkflows)
|
|
100503
|
+
log(chalk6.dim(" Add --create-workflows to upsert grouped workflows."));
|
|
100504
|
+
log("");
|
|
100505
|
+
} catch (error40) {
|
|
100506
|
+
logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
|
|
100507
|
+
process.exit(1);
|
|
100508
|
+
}
|
|
100509
|
+
});
|
|
100096
100510
|
program2.command("generate <url>").description("Crawl app and synthesize test scenarios using AI (any provider)").option("--max <n>", "Max scenarios to generate", "10").option("--max-pages <n>", "Max pages to crawl", "10").option("--focus <topic>", "Focus on specific area e.g. 'auth flows', 'checkout'").option("--persona <desc>", "Persona perspective e.g. 'first-time user'").option("--model <model>", "AI model (claude-haiku, gpt-4o-mini, gemini-2.0-flash, etc.)").option("--save", "Persist generated scenarios to DB", false).option("--project <id>", "Project ID").option("--headed", "Run browser in headed mode", false).option("--json", "Output as JSON", false).action(async (url2, opts) => {
|
|
100097
100511
|
try {
|
|
100098
100512
|
const { generateScenarios: generateScenarios2 } = await Promise.resolve().then(() => (init_generator(), exports_generator));
|
|
@@ -100555,11 +100969,11 @@ program2.command("doctor").description("Check system setup and configuration").a
|
|
|
100555
100969
|
log(chalk6.red("\u2717") + " ANTHROPIC_API_KEY is not set (required for AI-powered tests)");
|
|
100556
100970
|
allPassed = false;
|
|
100557
100971
|
}
|
|
100558
|
-
const dbPath =
|
|
100972
|
+
const dbPath = join21(getTestersDir(), "testers.db");
|
|
100559
100973
|
try {
|
|
100560
100974
|
const { Database: Database5 } = await import("bun:sqlite");
|
|
100561
|
-
const
|
|
100562
|
-
|
|
100975
|
+
const db3 = new Database5(dbPath, { create: true });
|
|
100976
|
+
db3.close();
|
|
100563
100977
|
log(chalk6.green("\u2713") + ` Database accessible: ${dbPath}`);
|
|
100564
100978
|
} catch (err) {
|
|
100565
100979
|
log(chalk6.red("\u2717") + ` Database not accessible at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -100609,7 +101023,7 @@ program2.command("serve").description("Start the Open Testers web dashboard").op
|
|
|
100609
101023
|
try {
|
|
100610
101024
|
const port = parseInt(opts.port, 10);
|
|
100611
101025
|
const url2 = `http://localhost:${port}`;
|
|
100612
|
-
const serverBin =
|
|
101026
|
+
const serverBin = join21(resolve5(process.execPath, ".."), "..", "dist", "server", "index.js");
|
|
100613
101027
|
const { join: pathJoin, resolve: pathResolve, dirname: dirname7 } = await import("path");
|
|
100614
101028
|
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
100615
101029
|
const serverPath = pathJoin(dirname7(fileURLToPath2(import.meta.url)), "..", "server", "index.js");
|
|
@@ -101028,6 +101442,9 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101028
101442
|
}, []).option("--max-iterations <n>", "Goal-loop iteration cap", "10").option("--target <target>", "Execution target: local or sandbox", "local").option("--sandbox-provider <provider>", "Sandbox provider: e2b, daytona, or modal").option("--sandbox-image <image>", "Sandbox image/template").option("--sandbox-remote-dir <path>", "Remote working directory for sandbox runs").option("--sandbox-cleanup <mode>", "Sandbox cleanup mode: delete, stop, or keep", "delete").option("--sandbox-sync <strategy>", "Sandbox upload sync strategy: rsync or archive", "rsync").option("--sandbox-setup-command <command>", "Shell command to run before testers in the sandbox").option("--sandbox-package <spec>", "Package spec to execute in the sandbox", "@hasna/testers").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
|
|
101029
101443
|
acc.push(val);
|
|
101030
101444
|
return acc;
|
|
101445
|
+
}, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
101446
|
+
acc.push(val);
|
|
101447
|
+
return acc;
|
|
101031
101448
|
}, []).option("--sandbox-app-source <path>", "Local app source directory to upload into the sandbox").option("--sandbox-app-remote-dir <path>", "Remote app directory inside the sandbox (default: <sandbox-remote-dir>/app)").option("--sandbox-app-start-command <command>", "Shell command to start the app before testers runs").option("--sandbox-app-url <url>", "URL testers should target inside the sandbox after the app starts").option("--sandbox-app-wait-url <url>", "URL to poll before starting testers (defaults to --sandbox-app-url)").option("--sandbox-app-wait-timeout <ms>", "App readiness wait timeout in milliseconds").option("--e2b-template <name>", "Legacy alias for --sandbox-image").option("--timeout <ms>", "Workflow timeout").option("--json", "Output as JSON", false).action((name21, opts) => {
|
|
101032
101449
|
try {
|
|
101033
101450
|
const workflow = createTestingWorkflow({
|
|
@@ -101054,7 +101471,7 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101054
101471
|
sandboxSyncStrategy: opts.sandboxSync,
|
|
101055
101472
|
setupCommand: opts.sandboxSetupCommand,
|
|
101056
101473
|
packageSpec: opts.sandboxPackage,
|
|
101057
|
-
env: parseSandboxEnv(opts.sandboxEnv),
|
|
101474
|
+
env: parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional),
|
|
101058
101475
|
appSourceDir: opts.sandboxAppSource,
|
|
101059
101476
|
appRemoteDir: opts.sandboxAppRemoteDir,
|
|
101060
101477
|
appStartCommand: opts.sandboxAppStartCommand,
|
|
@@ -101073,6 +101490,36 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101073
101490
|
process.exit(1);
|
|
101074
101491
|
}
|
|
101075
101492
|
});
|
|
101493
|
+
workflowCmd.command("update <id>").description("Update a saved testing workflow").option("--sandbox-env <assignment>", "Sandbox env var; KEY forwards host KEY, KEY=value stores value (repeatable)", (val, acc) => {
|
|
101494
|
+
acc.push(val);
|
|
101495
|
+
return acc;
|
|
101496
|
+
}, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
101497
|
+
acc.push(val);
|
|
101498
|
+
return acc;
|
|
101499
|
+
}, []).option("--clear-sandbox-env", "Replace existing sandbox env vars instead of merging", false).option("--json", "Output as JSON", false).action((id, opts) => {
|
|
101500
|
+
try {
|
|
101501
|
+
const workflow = getTestingWorkflow(id);
|
|
101502
|
+
if (!workflow) {
|
|
101503
|
+
logError(chalk6.red(`Workflow not found: ${id}`));
|
|
101504
|
+
process.exit(1);
|
|
101505
|
+
}
|
|
101506
|
+
const envPatch = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
|
|
101507
|
+
const execution = {
|
|
101508
|
+
...workflow.execution,
|
|
101509
|
+
env: opts.clearSandboxEnv ? envPatch : { ...workflow.execution.env ?? {}, ...envPatch ?? {} }
|
|
101510
|
+
};
|
|
101511
|
+
if (Object.keys(execution.env ?? {}).length === 0)
|
|
101512
|
+
delete execution.env;
|
|
101513
|
+
const updated = updateTestingWorkflow(workflow.id, { execution });
|
|
101514
|
+
if (opts.json)
|
|
101515
|
+
log(JSON.stringify(updated, null, 2));
|
|
101516
|
+
else
|
|
101517
|
+
log(chalk6.green(`Workflow updated: ${chalk6.bold(updated.id.slice(0, 8))} \u2014 ${updated.name}`));
|
|
101518
|
+
} catch (error40) {
|
|
101519
|
+
logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
|
|
101520
|
+
process.exit(1);
|
|
101521
|
+
}
|
|
101522
|
+
});
|
|
101076
101523
|
workflowCmd.command("list").description("List saved testing workflows").option("--project <id>", "Project ID").option("--all", "Include disabled workflows", false).option("--json", "Output as JSON", false).action((opts) => {
|
|
101077
101524
|
const workflows = listTestingWorkflows({
|
|
101078
101525
|
projectId: opts.project ? resolveProject2(opts.project) : undefined,
|
|
@@ -101342,7 +101789,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
|
|
|
101342
101789
|
}
|
|
101343
101790
|
if (!opts.yes) {
|
|
101344
101791
|
process.stdout.write(chalk6.yellow(`Delete persona ${persona.shortId} "${persona.name}"? [y/N] `));
|
|
101345
|
-
const answer = await new Promise((
|
|
101792
|
+
const answer = await new Promise((resolve6) => {
|
|
101346
101793
|
let buf = "";
|
|
101347
101794
|
process.stdin.setRawMode?.(true);
|
|
101348
101795
|
process.stdin.resume();
|
|
@@ -101352,7 +101799,7 @@ personaCmd.command("delete <id>").description("Delete a persona").option("-y, --
|
|
|
101352
101799
|
process.stdin.pause();
|
|
101353
101800
|
process.stdout.write(`
|
|
101354
101801
|
`);
|
|
101355
|
-
|
|
101802
|
+
resolve6(buf);
|
|
101356
101803
|
});
|
|
101357
101804
|
});
|
|
101358
101805
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -101513,7 +101960,7 @@ evalCmd.command("rag <url>").description("Run RAG quality evaluation \u2014 fait
|
|
|
101513
101960
|
let ragTestCases = [];
|
|
101514
101961
|
if (opts.docs) {
|
|
101515
101962
|
try {
|
|
101516
|
-
const raw =
|
|
101963
|
+
const raw = readFileSync11(opts.docs, "utf-8");
|
|
101517
101964
|
ragTestCases = JSON.parse(raw);
|
|
101518
101965
|
} catch {
|
|
101519
101966
|
logError(chalk6.red(`Failed to read docs file: ${opts.docs}`));
|
|
@@ -101603,9 +102050,9 @@ Created golden answer check ${chalk6.bold(golden2.shortId)}`));
|
|
|
101603
102050
|
}
|
|
101604
102051
|
const ask = (prompt) => {
|
|
101605
102052
|
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
101606
|
-
return new Promise((
|
|
102053
|
+
return new Promise((resolve6) => rl2.question(prompt, (ans) => {
|
|
101607
102054
|
rl2.close();
|
|
101608
|
-
|
|
102055
|
+
resolve6(ans.trim());
|
|
101609
102056
|
}));
|
|
101610
102057
|
};
|
|
101611
102058
|
const question = await ask("Question (what this endpoint should answer): ");
|
|
@@ -101766,9 +102213,9 @@ program2.command("run-many <url>").description("Run scenarios \xD7 personas matr
|
|
|
101766
102213
|
});
|
|
101767
102214
|
program2.command("run-script <file>").description("Run a hybrid test script (.ts) that exports an array of HybridScenario objects").option("--url <url>", "Base URL to run against").option("--json", "Output as JSON", false).action(async (file2, opts) => {
|
|
101768
102215
|
try {
|
|
101769
|
-
const { resolve:
|
|
102216
|
+
const { resolve: resolve6 } = await import("path");
|
|
101770
102217
|
const { runHybridScenario: runHybridScenario2 } = await Promise.resolve().then(() => (init_hybrid_runner(), exports_hybrid_runner));
|
|
101771
|
-
const scriptPath =
|
|
102218
|
+
const scriptPath = resolve6(process.cwd(), file2);
|
|
101772
102219
|
const mod = await import(scriptPath);
|
|
101773
102220
|
const scenarios = mod.scenarios ?? mod.default ?? [];
|
|
101774
102221
|
if (!Array.isArray(scenarios) || scenarios.length === 0) {
|