@hasna/testers 0.0.47 → 0.0.48
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 +223 -123
- package/dist/index.js +9 -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;
|
|
@@ -59663,49 +59720,49 @@ __export(exports_agents, {
|
|
|
59663
59720
|
getAgent: () => getAgent
|
|
59664
59721
|
});
|
|
59665
59722
|
function registerAgent(input) {
|
|
59666
|
-
const
|
|
59667
|
-
const existing =
|
|
59723
|
+
const db3 = getDatabase();
|
|
59724
|
+
const existing = db3.query("SELECT * FROM agents WHERE name = ?").get(input.name);
|
|
59668
59725
|
if (existing) {
|
|
59669
|
-
|
|
59726
|
+
db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
|
|
59670
59727
|
return getAgent(existing.id);
|
|
59671
59728
|
}
|
|
59672
59729
|
const id = uuid();
|
|
59673
59730
|
const timestamp = now();
|
|
59674
|
-
|
|
59731
|
+
db3.query(`
|
|
59675
59732
|
INSERT INTO agents (id, name, description, role, metadata, created_at, last_seen_at)
|
|
59676
59733
|
VALUES (?, ?, ?, ?, '{}', ?, ?)
|
|
59677
59734
|
`).run(id, input.name, input.description ?? null, input.role ?? null, timestamp, timestamp);
|
|
59678
59735
|
return getAgent(id);
|
|
59679
59736
|
}
|
|
59680
59737
|
function getAgent(id) {
|
|
59681
|
-
const
|
|
59682
|
-
const row =
|
|
59738
|
+
const db3 = getDatabase();
|
|
59739
|
+
const row = db3.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
59683
59740
|
return row ? agentFromRow(row) : null;
|
|
59684
59741
|
}
|
|
59685
59742
|
function getAgentByName(name) {
|
|
59686
|
-
const
|
|
59687
|
-
const row =
|
|
59743
|
+
const db3 = getDatabase();
|
|
59744
|
+
const row = db3.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
59688
59745
|
return row ? agentFromRow(row) : null;
|
|
59689
59746
|
}
|
|
59690
59747
|
function listAgents() {
|
|
59691
|
-
const
|
|
59692
|
-
const rows =
|
|
59748
|
+
const db3 = getDatabase();
|
|
59749
|
+
const rows = db3.query("SELECT * FROM agents ORDER BY created_at DESC").all();
|
|
59693
59750
|
return rows.map(agentFromRow);
|
|
59694
59751
|
}
|
|
59695
59752
|
function heartbeatAgent(id) {
|
|
59696
|
-
const
|
|
59697
|
-
const affected =
|
|
59753
|
+
const db3 = getDatabase();
|
|
59754
|
+
const affected = db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
|
|
59698
59755
|
if (affected.changes === 0)
|
|
59699
59756
|
return null;
|
|
59700
59757
|
return getAgent(id);
|
|
59701
59758
|
}
|
|
59702
59759
|
function setAgentFocus(id, scenarioId) {
|
|
59703
|
-
const
|
|
59760
|
+
const db3 = getDatabase();
|
|
59704
59761
|
const agent = getAgent(id);
|
|
59705
59762
|
if (!agent)
|
|
59706
59763
|
return null;
|
|
59707
59764
|
const metadata = { ...agent.metadata ?? {}, focus: scenarioId };
|
|
59708
|
-
|
|
59765
|
+
db3.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
|
|
59709
59766
|
return getAgent(id);
|
|
59710
59767
|
}
|
|
59711
59768
|
var init_agents = __esm(() => {
|
|
@@ -94089,28 +94146,28 @@ function checkResultFromRow(row) {
|
|
|
94089
94146
|
};
|
|
94090
94147
|
}
|
|
94091
94148
|
function createGoldenAnswer(input) {
|
|
94092
|
-
const
|
|
94149
|
+
const db3 = getDatabase();
|
|
94093
94150
|
const id = uuid();
|
|
94094
94151
|
const short_id = shortUuid();
|
|
94095
94152
|
const timestamp = now();
|
|
94096
|
-
|
|
94153
|
+
db3.query(`
|
|
94097
94154
|
INSERT INTO golden_answers (id, short_id, project_id, question, golden_answer, constraints, endpoint, judge_model, enabled, created_at, updated_at)
|
|
94098
94155
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94099
94156
|
`).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
94157
|
return getGoldenAnswer(id);
|
|
94101
94158
|
}
|
|
94102
94159
|
function getGoldenAnswer(id) {
|
|
94103
|
-
const
|
|
94104
|
-
let row =
|
|
94160
|
+
const db3 = getDatabase();
|
|
94161
|
+
let row = db3.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
|
|
94105
94162
|
if (row)
|
|
94106
94163
|
return goldenFromRow(row);
|
|
94107
|
-
row =
|
|
94164
|
+
row = db3.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
|
|
94108
94165
|
if (row)
|
|
94109
94166
|
return goldenFromRow(row);
|
|
94110
94167
|
return null;
|
|
94111
94168
|
}
|
|
94112
94169
|
function listGoldenAnswers(filter2) {
|
|
94113
|
-
const
|
|
94170
|
+
const db3 = getDatabase();
|
|
94114
94171
|
const conditions = [];
|
|
94115
94172
|
const params = [];
|
|
94116
94173
|
if (filter2?.projectId) {
|
|
@@ -94126,11 +94183,11 @@ function listGoldenAnswers(filter2) {
|
|
|
94126
94183
|
sql += " WHERE " + conditions.join(" AND ");
|
|
94127
94184
|
}
|
|
94128
94185
|
sql += " ORDER BY created_at DESC";
|
|
94129
|
-
const rows =
|
|
94186
|
+
const rows = db3.query(sql).all(...params);
|
|
94130
94187
|
return rows.map(goldenFromRow);
|
|
94131
94188
|
}
|
|
94132
94189
|
function updateGoldenAnswer(id, input) {
|
|
94133
|
-
const
|
|
94190
|
+
const db3 = getDatabase();
|
|
94134
94191
|
const timestamp = now();
|
|
94135
94192
|
const sets = ["updated_at = ?"];
|
|
94136
94193
|
const params = [timestamp];
|
|
@@ -94159,26 +94216,26 @@ function updateGoldenAnswer(id, input) {
|
|
|
94159
94216
|
params.push(input.enabled ? 1 : 0);
|
|
94160
94217
|
}
|
|
94161
94218
|
params.push(id);
|
|
94162
|
-
|
|
94219
|
+
db3.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
|
|
94163
94220
|
return getGoldenAnswer(id);
|
|
94164
94221
|
}
|
|
94165
94222
|
function deleteGoldenAnswer(id) {
|
|
94166
|
-
const
|
|
94167
|
-
const result =
|
|
94223
|
+
const db3 = getDatabase();
|
|
94224
|
+
const result = db3.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
|
|
94168
94225
|
return (result.changes ?? 0) > 0;
|
|
94169
94226
|
}
|
|
94170
94227
|
function createGoldenCheckResult(input) {
|
|
94171
|
-
const
|
|
94228
|
+
const db3 = getDatabase();
|
|
94172
94229
|
const id = uuid();
|
|
94173
94230
|
const timestamp = now();
|
|
94174
|
-
|
|
94231
|
+
db3.query(`
|
|
94175
94232
|
INSERT INTO golden_check_results (id, golden_id, response, similarity_score, passed, drift_detected, judge_model, provider, created_at)
|
|
94176
94233
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94177
94234
|
`).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
94235
|
return listGoldenCheckResults(input.goldenId, { limit: 1 })[0];
|
|
94179
94236
|
}
|
|
94180
94237
|
function listGoldenCheckResults(goldenId, options) {
|
|
94181
|
-
const
|
|
94238
|
+
const db3 = getDatabase();
|
|
94182
94239
|
const params = [goldenId];
|
|
94183
94240
|
let sql = "SELECT * FROM golden_check_results WHERE golden_id = ?";
|
|
94184
94241
|
if (options?.since) {
|
|
@@ -94190,7 +94247,7 @@ function listGoldenCheckResults(goldenId, options) {
|
|
|
94190
94247
|
sql += " LIMIT ?";
|
|
94191
94248
|
params.push(options.limit);
|
|
94192
94249
|
}
|
|
94193
|
-
const rows =
|
|
94250
|
+
const rows = db3.query(sql).all(...params);
|
|
94194
94251
|
return rows.map(checkResultFromRow);
|
|
94195
94252
|
}
|
|
94196
94253
|
function getLatestGoldenCheckResult(goldenId) {
|
|
@@ -94479,7 +94536,7 @@ import chalk6 from "chalk";
|
|
|
94479
94536
|
// package.json
|
|
94480
94537
|
var package_default = {
|
|
94481
94538
|
name: "@hasna/testers",
|
|
94482
|
-
version: "0.0.
|
|
94539
|
+
version: "0.0.48",
|
|
94483
94540
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
94484
94541
|
type: "module",
|
|
94485
94542
|
main: "dist/index.js",
|
|
@@ -96613,51 +96670,51 @@ function fromRow3(row) {
|
|
|
96613
96670
|
};
|
|
96614
96671
|
}
|
|
96615
96672
|
function createEnvironment(input) {
|
|
96616
|
-
const
|
|
96673
|
+
const db3 = getDatabase();
|
|
96617
96674
|
const id = uuid();
|
|
96618
96675
|
const timestamp = now();
|
|
96619
96676
|
const meta = JSON.stringify({ variables: input.variables ?? {} });
|
|
96620
|
-
|
|
96677
|
+
db3.query(`
|
|
96621
96678
|
INSERT INTO environments (id, name, url, auth_preset_name, project_id, is_default, metadata, created_at)
|
|
96622
96679
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
96623
96680
|
`).run(id, input.name, input.url, input.authPresetName ?? null, input.projectId ?? null, input.isDefault ? 1 : 0, meta, timestamp);
|
|
96624
96681
|
return getEnvironmentById(id);
|
|
96625
96682
|
}
|
|
96626
96683
|
function getEnvironmentById(id) {
|
|
96627
|
-
const
|
|
96628
|
-
const row =
|
|
96684
|
+
const db3 = getDatabase();
|
|
96685
|
+
const row = db3.query("SELECT * FROM environments WHERE id = ?").get(id);
|
|
96629
96686
|
return row ? fromRow3(row) : null;
|
|
96630
96687
|
}
|
|
96631
96688
|
function getEnvironment(name) {
|
|
96632
|
-
const
|
|
96633
|
-
const row =
|
|
96689
|
+
const db3 = getDatabase();
|
|
96690
|
+
const row = db3.query("SELECT * FROM environments WHERE name = ?").get(name);
|
|
96634
96691
|
return row ? fromRow3(row) : null;
|
|
96635
96692
|
}
|
|
96636
96693
|
function listEnvironments(projectId) {
|
|
96637
|
-
const
|
|
96694
|
+
const db3 = getDatabase();
|
|
96638
96695
|
if (projectId) {
|
|
96639
|
-
const rows2 =
|
|
96696
|
+
const rows2 = db3.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
|
|
96640
96697
|
return rows2.map(fromRow3);
|
|
96641
96698
|
}
|
|
96642
|
-
const rows =
|
|
96699
|
+
const rows = db3.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
|
|
96643
96700
|
return rows.map(fromRow3);
|
|
96644
96701
|
}
|
|
96645
96702
|
function deleteEnvironment(name) {
|
|
96646
|
-
const
|
|
96647
|
-
const result =
|
|
96703
|
+
const db3 = getDatabase();
|
|
96704
|
+
const result = db3.query("DELETE FROM environments WHERE name = ?").run(name);
|
|
96648
96705
|
return result.changes > 0;
|
|
96649
96706
|
}
|
|
96650
96707
|
function setDefaultEnvironment(name) {
|
|
96651
|
-
const
|
|
96652
|
-
|
|
96653
|
-
const result =
|
|
96708
|
+
const db3 = getDatabase();
|
|
96709
|
+
db3.exec("UPDATE environments SET is_default = 0");
|
|
96710
|
+
const result = db3.query("UPDATE environments SET is_default = 1 WHERE name = ?").run(name);
|
|
96654
96711
|
if (result.changes === 0) {
|
|
96655
96712
|
throw new Error(`Environment not found: ${name}`);
|
|
96656
96713
|
}
|
|
96657
96714
|
}
|
|
96658
96715
|
function getDefaultEnvironment() {
|
|
96659
|
-
const
|
|
96660
|
-
const row =
|
|
96716
|
+
const db3 = getDatabase();
|
|
96717
|
+
const row = db3.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
|
|
96661
96718
|
return row ? fromRow3(row) : null;
|
|
96662
96719
|
}
|
|
96663
96720
|
|
|
@@ -97224,12 +97281,12 @@ async function runRepoTests(opts) {
|
|
|
97224
97281
|
specResults.push(result);
|
|
97225
97282
|
const resultId = uuid();
|
|
97226
97283
|
const timestamp = now();
|
|
97227
|
-
const
|
|
97228
|
-
|
|
97284
|
+
const db3 = getDatabase();
|
|
97285
|
+
db3.exec("PRAGMA foreign_keys = OFF");
|
|
97229
97286
|
try {
|
|
97230
97287
|
const reasoning = result.status === "passed" ? "All tests passed" : (result.error ?? "").slice(0, 500) || null;
|
|
97231
97288
|
const errorStr = result.status !== "passed" ? result.error ?? null : null;
|
|
97232
|
-
|
|
97289
|
+
db3.query(`
|
|
97233
97290
|
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
97291
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, NULL, NULL)
|
|
97235
97292
|
`).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 +97295,7 @@ async function runRepoTests(opts) {
|
|
|
97238
97295
|
testResults: result.testResults
|
|
97239
97296
|
}), timestamp);
|
|
97240
97297
|
} finally {
|
|
97241
|
-
|
|
97298
|
+
db3.exec("PRAGMA foreign_keys = ON");
|
|
97242
97299
|
}
|
|
97243
97300
|
const resultRecord = { id: resultId };
|
|
97244
97301
|
if (result.stdout || result.stderr) {
|
|
@@ -97385,21 +97442,31 @@ function envCredentialRef(value) {
|
|
|
97385
97442
|
return;
|
|
97386
97443
|
return trimmed.startsWith("$") ? trimmed : `$${trimmed}`;
|
|
97387
97444
|
}
|
|
97388
|
-
function
|
|
97389
|
-
if (
|
|
97445
|
+
function assertEnvVarName(key, rawValue) {
|
|
97446
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
97447
|
+
throw new Error(`Invalid sandbox env var name: ${key || rawValue}`);
|
|
97448
|
+
}
|
|
97449
|
+
}
|
|
97450
|
+
function parseSandboxEnv(values, optionalValues = []) {
|
|
97451
|
+
if (!values?.length && !optionalValues?.length)
|
|
97390
97452
|
return;
|
|
97391
97453
|
const env = {};
|
|
97392
|
-
for (const value of values) {
|
|
97454
|
+
for (const value of values ?? []) {
|
|
97393
97455
|
const trimmed = value.trim();
|
|
97394
97456
|
if (!trimmed)
|
|
97395
97457
|
continue;
|
|
97396
97458
|
const separator = trimmed.indexOf("=");
|
|
97397
97459
|
const key = separator >= 0 ? trimmed.slice(0, separator).trim() : trimmed;
|
|
97398
|
-
|
|
97399
|
-
throw new Error(`Invalid sandbox env var name: ${key || value}`);
|
|
97400
|
-
}
|
|
97460
|
+
assertEnvVarName(key, value);
|
|
97401
97461
|
env[key] = separator >= 0 ? trimmed.slice(separator + 1) : `$${key}`;
|
|
97402
97462
|
}
|
|
97463
|
+
for (const value of optionalValues ?? []) {
|
|
97464
|
+
const key = value.trim();
|
|
97465
|
+
if (!key)
|
|
97466
|
+
continue;
|
|
97467
|
+
assertEnvVarName(key, value);
|
|
97468
|
+
env[key] = `$?${key}`;
|
|
97469
|
+
}
|
|
97403
97470
|
return Object.keys(env).length > 0 ? env : undefined;
|
|
97404
97471
|
}
|
|
97405
97472
|
function AddForm({ onComplete }) {
|
|
@@ -100558,8 +100625,8 @@ program2.command("doctor").description("Check system setup and configuration").a
|
|
|
100558
100625
|
const dbPath = join20(getTestersDir(), "testers.db");
|
|
100559
100626
|
try {
|
|
100560
100627
|
const { Database: Database5 } = await import("bun:sqlite");
|
|
100561
|
-
const
|
|
100562
|
-
|
|
100628
|
+
const db3 = new Database5(dbPath, { create: true });
|
|
100629
|
+
db3.close();
|
|
100563
100630
|
log(chalk6.green("\u2713") + ` Database accessible: ${dbPath}`);
|
|
100564
100631
|
} catch (err) {
|
|
100565
100632
|
log(chalk6.red("\u2717") + ` Database not accessible at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -101028,6 +101095,9 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101028
101095
|
}, []).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
101096
|
acc.push(val);
|
|
101030
101097
|
return acc;
|
|
101098
|
+
}, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
101099
|
+
acc.push(val);
|
|
101100
|
+
return acc;
|
|
101031
101101
|
}, []).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
101102
|
try {
|
|
101033
101103
|
const workflow = createTestingWorkflow({
|
|
@@ -101054,7 +101124,7 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101054
101124
|
sandboxSyncStrategy: opts.sandboxSync,
|
|
101055
101125
|
setupCommand: opts.sandboxSetupCommand,
|
|
101056
101126
|
packageSpec: opts.sandboxPackage,
|
|
101057
|
-
env: parseSandboxEnv(opts.sandboxEnv),
|
|
101127
|
+
env: parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional),
|
|
101058
101128
|
appSourceDir: opts.sandboxAppSource,
|
|
101059
101129
|
appRemoteDir: opts.sandboxAppRemoteDir,
|
|
101060
101130
|
appStartCommand: opts.sandboxAppStartCommand,
|
|
@@ -101073,6 +101143,36 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101073
101143
|
process.exit(1);
|
|
101074
101144
|
}
|
|
101075
101145
|
});
|
|
101146
|
+
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) => {
|
|
101147
|
+
acc.push(val);
|
|
101148
|
+
return acc;
|
|
101149
|
+
}, []).option("--sandbox-env-optional <name>", "Optional sandbox env var; forwards host NAME only when set (repeatable)", (val, acc) => {
|
|
101150
|
+
acc.push(val);
|
|
101151
|
+
return acc;
|
|
101152
|
+
}, []).option("--clear-sandbox-env", "Replace existing sandbox env vars instead of merging", false).option("--json", "Output as JSON", false).action((id, opts) => {
|
|
101153
|
+
try {
|
|
101154
|
+
const workflow = getTestingWorkflow(id);
|
|
101155
|
+
if (!workflow) {
|
|
101156
|
+
logError(chalk6.red(`Workflow not found: ${id}`));
|
|
101157
|
+
process.exit(1);
|
|
101158
|
+
}
|
|
101159
|
+
const envPatch = parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional);
|
|
101160
|
+
const execution = {
|
|
101161
|
+
...workflow.execution,
|
|
101162
|
+
env: opts.clearSandboxEnv ? envPatch : { ...workflow.execution.env ?? {}, ...envPatch ?? {} }
|
|
101163
|
+
};
|
|
101164
|
+
if (Object.keys(execution.env ?? {}).length === 0)
|
|
101165
|
+
delete execution.env;
|
|
101166
|
+
const updated = updateTestingWorkflow(workflow.id, { execution });
|
|
101167
|
+
if (opts.json)
|
|
101168
|
+
log(JSON.stringify(updated, null, 2));
|
|
101169
|
+
else
|
|
101170
|
+
log(chalk6.green(`Workflow updated: ${chalk6.bold(updated.id.slice(0, 8))} \u2014 ${updated.name}`));
|
|
101171
|
+
} catch (error40) {
|
|
101172
|
+
logError(chalk6.red(`Error: ${error40 instanceof Error ? error40.message : String(error40)}`));
|
|
101173
|
+
process.exit(1);
|
|
101174
|
+
}
|
|
101175
|
+
});
|
|
101076
101176
|
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
101177
|
const workflows = listTestingWorkflows({
|
|
101078
101178
|
projectId: opts.project ? resolveProject2(opts.project) : undefined,
|
package/dist/index.js
CHANGED
|
@@ -17223,6 +17223,8 @@ var APP_SOURCE_EXCLUDES = [
|
|
|
17223
17223
|
".next",
|
|
17224
17224
|
".turbo",
|
|
17225
17225
|
".cache",
|
|
17226
|
+
".env",
|
|
17227
|
+
".env.*",
|
|
17226
17228
|
".venv",
|
|
17227
17229
|
"__pycache__"
|
|
17228
17230
|
];
|
|
@@ -17476,6 +17478,13 @@ function resolveSandboxEnv(env2) {
|
|
|
17476
17478
|
return;
|
|
17477
17479
|
const resolved = {};
|
|
17478
17480
|
for (const [key, value] of Object.entries(env2)) {
|
|
17481
|
+
if (value.startsWith("$?")) {
|
|
17482
|
+
const optionalName = value.slice(2).trim();
|
|
17483
|
+
const optionalValue = optionalName ? process.env[optionalName] : undefined;
|
|
17484
|
+
if (optionalValue !== undefined)
|
|
17485
|
+
resolved[key] = optionalValue;
|
|
17486
|
+
continue;
|
|
17487
|
+
}
|
|
17479
17488
|
const resolvedValue = resolveCredential(value);
|
|
17480
17489
|
if (resolvedValue === null) {
|
|
17481
17490
|
throw new Error(`Missing sandbox env value for ${key}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@hasna/testers",
|
|
55
|
-
version: "0.0.
|
|
55
|
+
version: "0.0.48",
|
|
56
56
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
57
57
|
type: "module",
|
|
58
58
|
main: "dist/index.js",
|
|
@@ -23754,6 +23754,13 @@ function resolveSandboxEnv(env2) {
|
|
|
23754
23754
|
return;
|
|
23755
23755
|
const resolved = {};
|
|
23756
23756
|
for (const [key, value] of Object.entries(env2)) {
|
|
23757
|
+
if (value.startsWith("$?")) {
|
|
23758
|
+
const optionalName = value.slice(2).trim();
|
|
23759
|
+
const optionalValue = optionalName ? process.env[optionalName] : undefined;
|
|
23760
|
+
if (optionalValue !== undefined)
|
|
23761
|
+
resolved[key] = optionalValue;
|
|
23762
|
+
continue;
|
|
23763
|
+
}
|
|
23757
23764
|
const resolvedValue = resolveCredential(value);
|
|
23758
23765
|
if (resolvedValue === null) {
|
|
23759
23766
|
throw new Error(`Missing sandbox env value for ${key}`);
|
|
@@ -23787,6 +23794,8 @@ var init_workflow_runner = __esm(() => {
|
|
|
23787
23794
|
".next",
|
|
23788
23795
|
".turbo",
|
|
23789
23796
|
".cache",
|
|
23797
|
+
".env",
|
|
23798
|
+
".env.*",
|
|
23790
23799
|
".venv",
|
|
23791
23800
|
"__pycache__"
|
|
23792
23801
|
];
|
package/dist/server/index.js
CHANGED
|
@@ -46937,7 +46937,7 @@ import { join as join14 } from "path";
|
|
|
46937
46937
|
// package.json
|
|
46938
46938
|
var package_default = {
|
|
46939
46939
|
name: "@hasna/testers",
|
|
46940
|
-
version: "0.0.
|
|
46940
|
+
version: "0.0.48",
|
|
46941
46941
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
46942
46942
|
type: "module",
|
|
46943
46943
|
main: "dist/index.js",
|
|
@@ -51339,6 +51339,8 @@ var APP_SOURCE_EXCLUDES = [
|
|
|
51339
51339
|
".next",
|
|
51340
51340
|
".turbo",
|
|
51341
51341
|
".cache",
|
|
51342
|
+
".env",
|
|
51343
|
+
".env.*",
|
|
51342
51344
|
".venv",
|
|
51343
51345
|
"__pycache__"
|
|
51344
51346
|
];
|
|
@@ -51592,6 +51594,13 @@ function resolveSandboxEnv(env) {
|
|
|
51592
51594
|
return;
|
|
51593
51595
|
const resolved = {};
|
|
51594
51596
|
for (const [key, value] of Object.entries(env)) {
|
|
51597
|
+
if (value.startsWith("$?")) {
|
|
51598
|
+
const optionalName = value.slice(2).trim();
|
|
51599
|
+
const optionalValue = optionalName ? process.env[optionalName] : undefined;
|
|
51600
|
+
if (optionalValue !== undefined)
|
|
51601
|
+
resolved[key] = optionalValue;
|
|
51602
|
+
continue;
|
|
51603
|
+
}
|
|
51595
51604
|
const resolvedValue = resolveCredential(value);
|
|
51596
51605
|
if (resolvedValue === null) {
|
|
51597
51606
|
throw new Error(`Missing sandbox env value for ${key}`);
|