@hasna/testers 0.0.46 → 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 +285 -124
- package/dist/index.js +23 -1
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -2
- package/dist/server/index.js +24 -2
- 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();
|
|
@@ -27262,7 +27310,7 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
27262
27310
|
workflowId: plan.workflow.id,
|
|
27263
27311
|
workflowName: plan.workflow.name
|
|
27264
27312
|
},
|
|
27265
|
-
sandboxEnvVars: plan.sandbox.env,
|
|
27313
|
+
sandboxEnvVars: resolveSandboxEnv(plan.sandbox.env),
|
|
27266
27314
|
cleanup: plan.sandbox.cleanup,
|
|
27267
27315
|
upload: {
|
|
27268
27316
|
localDir: bundle.localDir,
|
|
@@ -27288,6 +27336,26 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
27288
27336
|
bundle.cleanup?.();
|
|
27289
27337
|
}
|
|
27290
27338
|
}
|
|
27339
|
+
function resolveSandboxEnv(env) {
|
|
27340
|
+
if (!env || Object.keys(env).length === 0)
|
|
27341
|
+
return;
|
|
27342
|
+
const resolved = {};
|
|
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
|
+
}
|
|
27351
|
+
const resolvedValue = resolveCredential(value);
|
|
27352
|
+
if (resolvedValue === null) {
|
|
27353
|
+
throw new Error(`Missing sandbox env value for ${key}`);
|
|
27354
|
+
}
|
|
27355
|
+
resolved[key] = resolvedValue;
|
|
27356
|
+
}
|
|
27357
|
+
return resolved;
|
|
27358
|
+
}
|
|
27291
27359
|
async function resolveSandboxesRuntime(dependencies) {
|
|
27292
27360
|
if (dependencies.sandboxes)
|
|
27293
27361
|
return dependencies.sandboxes;
|
|
@@ -27305,6 +27373,7 @@ var init_workflow_runner = __esm(() => {
|
|
|
27305
27373
|
init_workflows();
|
|
27306
27374
|
init_personas();
|
|
27307
27375
|
init_runner();
|
|
27376
|
+
init_secrets_resolver();
|
|
27308
27377
|
APP_SOURCE_EXCLUDES = [
|
|
27309
27378
|
"node_modules",
|
|
27310
27379
|
".git",
|
|
@@ -27312,6 +27381,8 @@ var init_workflow_runner = __esm(() => {
|
|
|
27312
27381
|
".next",
|
|
27313
27382
|
".turbo",
|
|
27314
27383
|
".cache",
|
|
27384
|
+
".env",
|
|
27385
|
+
".env.*",
|
|
27315
27386
|
".venv",
|
|
27316
27387
|
"__pycache__"
|
|
27317
27388
|
];
|
|
@@ -27465,46 +27536,46 @@ __export(exports_sessions, {
|
|
|
27465
27536
|
countSessions: () => countSessions
|
|
27466
27537
|
});
|
|
27467
27538
|
function createSession(input) {
|
|
27468
|
-
const
|
|
27539
|
+
const db3 = getDatabase();
|
|
27469
27540
|
const id = input.sessionId ?? uuid();
|
|
27470
27541
|
const timestamp = now();
|
|
27471
|
-
|
|
27542
|
+
db3.query(`
|
|
27472
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)
|
|
27473
27544
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
27474
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);
|
|
27475
27546
|
return getSession(id);
|
|
27476
27547
|
}
|
|
27477
27548
|
function getSession(id) {
|
|
27478
|
-
const
|
|
27479
|
-
let row =
|
|
27549
|
+
const db3 = getDatabase();
|
|
27550
|
+
let row = db3.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
27480
27551
|
if (row)
|
|
27481
27552
|
return sessionFromRow(row);
|
|
27482
27553
|
const fullId = resolvePartialId("sessions", id);
|
|
27483
27554
|
if (fullId) {
|
|
27484
|
-
row =
|
|
27555
|
+
row = db3.query("SELECT * FROM sessions WHERE id = ?").get(fullId);
|
|
27485
27556
|
if (row)
|
|
27486
27557
|
return sessionFromRow(row);
|
|
27487
27558
|
}
|
|
27488
27559
|
return null;
|
|
27489
27560
|
}
|
|
27490
27561
|
function listSessions(limit = 50, offset = 0) {
|
|
27491
|
-
const
|
|
27492
|
-
const rows =
|
|
27562
|
+
const db3 = getDatabase();
|
|
27563
|
+
const rows = db3.query("SELECT * FROM sessions ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
27493
27564
|
return rows.map(sessionFromRow);
|
|
27494
27565
|
}
|
|
27495
27566
|
function deleteSession(id) {
|
|
27496
|
-
const
|
|
27497
|
-
const result =
|
|
27567
|
+
const db3 = getDatabase();
|
|
27568
|
+
const result = db3.query("DELETE FROM sessions WHERE id = ?").run(id);
|
|
27498
27569
|
return result.changes > 0;
|
|
27499
27570
|
}
|
|
27500
27571
|
function searchSessions(query, limit = 20) {
|
|
27501
|
-
const
|
|
27502
|
-
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);
|
|
27503
27574
|
return rows.map(sessionFromRow);
|
|
27504
27575
|
}
|
|
27505
27576
|
function countSessions() {
|
|
27506
|
-
const
|
|
27507
|
-
const row =
|
|
27577
|
+
const db3 = getDatabase();
|
|
27578
|
+
const row = db3.query("SELECT COUNT(*) as count FROM sessions").get();
|
|
27508
27579
|
return row.count;
|
|
27509
27580
|
}
|
|
27510
27581
|
function sessionFromRow(row) {
|
|
@@ -28032,8 +28103,8 @@ function customRandom(alphabet, defaultSize, getRandom) {
|
|
|
28032
28103
|
function customAlphabet(alphabet, size = 21) {
|
|
28033
28104
|
return customRandom(alphabet, size, random);
|
|
28034
28105
|
}
|
|
28035
|
-
function runMigrations(
|
|
28036
|
-
|
|
28106
|
+
function runMigrations(db3) {
|
|
28107
|
+
db3.run(`
|
|
28037
28108
|
CREATE TABLE IF NOT EXISTS _migrations (
|
|
28038
28109
|
id INTEGER PRIMARY KEY,
|
|
28039
28110
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
@@ -28041,9 +28112,9 @@ function runMigrations(db2) {
|
|
|
28041
28112
|
`);
|
|
28042
28113
|
for (let i = 0;i < MIGRATIONS2.length; i++) {
|
|
28043
28114
|
const migrationId = i + 1;
|
|
28044
|
-
const exists =
|
|
28115
|
+
const exists = db3.query("SELECT id FROM _migrations WHERE id = ?").get(migrationId);
|
|
28045
28116
|
if (!exists) {
|
|
28046
|
-
|
|
28117
|
+
db3.run(MIGRATIONS2[i]);
|
|
28047
28118
|
}
|
|
28048
28119
|
}
|
|
28049
28120
|
}
|
|
@@ -28068,11 +28139,11 @@ function ensureDir2(filePath) {
|
|
|
28068
28139
|
function getDatabase2(path) {
|
|
28069
28140
|
if (path) {
|
|
28070
28141
|
ensureDir2(path);
|
|
28071
|
-
const
|
|
28072
|
-
|
|
28073
|
-
|
|
28074
|
-
runMigrations(
|
|
28075
|
-
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;
|
|
28076
28147
|
}
|
|
28077
28148
|
if (!_db) {
|
|
28078
28149
|
const dbPath = getDbPath2();
|
|
@@ -28140,8 +28211,8 @@ function rowToWorkdir(row) {
|
|
|
28140
28211
|
function getMachineId() {
|
|
28141
28212
|
return process.env["HOSTNAME"] || hostname();
|
|
28142
28213
|
}
|
|
28143
|
-
function addWorkdir(input,
|
|
28144
|
-
const d =
|
|
28214
|
+
function addWorkdir(input, db3) {
|
|
28215
|
+
const d = db3 || getDatabase2();
|
|
28145
28216
|
const project = getProject2(input.project_id, d);
|
|
28146
28217
|
if (!project)
|
|
28147
28218
|
throw new ProjectNotFoundError(input.project_id);
|
|
@@ -28158,18 +28229,18 @@ function addWorkdir(input, db2) {
|
|
|
28158
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]);
|
|
28159
28230
|
return getWorkdir(input.project_id, input.path, d);
|
|
28160
28231
|
}
|
|
28161
|
-
function getWorkdir(projectId, path,
|
|
28162
|
-
const d =
|
|
28232
|
+
function getWorkdir(projectId, path, db3) {
|
|
28233
|
+
const d = db3 || getDatabase2();
|
|
28163
28234
|
const row = d.query("SELECT * FROM project_workdirs WHERE project_id = ? AND path = ?").get(projectId, path);
|
|
28164
28235
|
return row ? rowToWorkdir(row) : null;
|
|
28165
28236
|
}
|
|
28166
|
-
function listWorkdirs(projectId,
|
|
28167
|
-
const d =
|
|
28237
|
+
function listWorkdirs(projectId, db3) {
|
|
28238
|
+
const d = db3 || getDatabase2();
|
|
28168
28239
|
const rows = d.query("SELECT * FROM project_workdirs WHERE project_id = ? ORDER BY is_primary DESC, created_at ASC").all(projectId);
|
|
28169
28240
|
return rows.map(rowToWorkdir);
|
|
28170
28241
|
}
|
|
28171
|
-
function removeWorkdir(projectId, path,
|
|
28172
|
-
const d =
|
|
28242
|
+
function removeWorkdir(projectId, path, db3) {
|
|
28243
|
+
const d = db3 || getDatabase2();
|
|
28173
28244
|
d.run("DELETE FROM project_workdirs WHERE project_id = ? AND path = ?", [projectId, path]);
|
|
28174
28245
|
}
|
|
28175
28246
|
function generateProjectId() {
|
|
@@ -28187,11 +28258,11 @@ function scaffoldProject(path) {
|
|
|
28187
28258
|
mkdirSync32(join42(path, dir), { recursive: true });
|
|
28188
28259
|
}
|
|
28189
28260
|
}
|
|
28190
|
-
function ensureUniqueSlug(base,
|
|
28261
|
+
function ensureUniqueSlug(base, db3, excludeId) {
|
|
28191
28262
|
let candidate = base;
|
|
28192
28263
|
let suffix = 1;
|
|
28193
28264
|
while (true) {
|
|
28194
|
-
const row =
|
|
28265
|
+
const row = db3.query("SELECT id FROM projects WHERE slug = ?").get(candidate);
|
|
28195
28266
|
if (!row || row.id === excludeId)
|
|
28196
28267
|
return candidate;
|
|
28197
28268
|
suffix++;
|
|
@@ -28207,8 +28278,8 @@ function rowToProject(row) {
|
|
|
28207
28278
|
last_opened_at: row.last_opened_at ?? null
|
|
28208
28279
|
};
|
|
28209
28280
|
}
|
|
28210
|
-
function createProject2(input,
|
|
28211
|
-
const d =
|
|
28281
|
+
function createProject2(input, db3) {
|
|
28282
|
+
const d = db3 || getDatabase2();
|
|
28212
28283
|
const id = generateProjectId();
|
|
28213
28284
|
const ts = now2();
|
|
28214
28285
|
const baseSlug = input.slug || slugify2(input.name);
|
|
@@ -28243,23 +28314,23 @@ function createProject2(input, db2) {
|
|
|
28243
28314
|
}
|
|
28244
28315
|
return getProject2(id, d);
|
|
28245
28316
|
}
|
|
28246
|
-
function getProject2(id,
|
|
28247
|
-
const d =
|
|
28317
|
+
function getProject2(id, db3) {
|
|
28318
|
+
const d = db3 || getDatabase2();
|
|
28248
28319
|
const row = d.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
28249
28320
|
return row ? rowToProject(row) : null;
|
|
28250
28321
|
}
|
|
28251
|
-
function getProjectBySlug(slug,
|
|
28252
|
-
const d =
|
|
28322
|
+
function getProjectBySlug(slug, db3) {
|
|
28323
|
+
const d = db3 || getDatabase2();
|
|
28253
28324
|
const row = d.query("SELECT * FROM projects WHERE slug = ?").get(slug);
|
|
28254
28325
|
return row ? rowToProject(row) : null;
|
|
28255
28326
|
}
|
|
28256
|
-
function getProjectByPath2(path,
|
|
28257
|
-
const d =
|
|
28327
|
+
function getProjectByPath2(path, db3) {
|
|
28328
|
+
const d = db3 || getDatabase2();
|
|
28258
28329
|
const row = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
28259
28330
|
return row ? rowToProject(row) : null;
|
|
28260
28331
|
}
|
|
28261
|
-
function listProjects2(filter = {},
|
|
28262
|
-
const d =
|
|
28332
|
+
function listProjects2(filter = {}, db3) {
|
|
28333
|
+
const d = db3 || getDatabase2();
|
|
28263
28334
|
const conditions = [];
|
|
28264
28335
|
const params = [];
|
|
28265
28336
|
if (filter.status) {
|
|
@@ -28278,8 +28349,8 @@ function listProjects2(filter = {}, db2) {
|
|
|
28278
28349
|
}
|
|
28279
28350
|
return rows.map(rowToProject);
|
|
28280
28351
|
}
|
|
28281
|
-
function updateProject(id, input,
|
|
28282
|
-
const d =
|
|
28352
|
+
function updateProject(id, input, db3) {
|
|
28353
|
+
const d = db3 || getDatabase2();
|
|
28283
28354
|
const project = getProject2(id, d);
|
|
28284
28355
|
if (!project)
|
|
28285
28356
|
throw new ProjectNotFoundError(id);
|
|
@@ -28328,8 +28399,8 @@ function updateProject(id, input, db2) {
|
|
|
28328
28399
|
d.run(`UPDATE projects SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
28329
28400
|
return getProject2(id, d);
|
|
28330
28401
|
}
|
|
28331
|
-
function setIntegrations(id, integrations,
|
|
28332
|
-
const d =
|
|
28402
|
+
function setIntegrations(id, integrations, db3) {
|
|
28403
|
+
const d = db3 || getDatabase2();
|
|
28333
28404
|
const project = getProject2(id, d);
|
|
28334
28405
|
if (!project)
|
|
28335
28406
|
throw new ProjectNotFoundError(id);
|
|
@@ -28349,16 +28420,16 @@ function setIntegrations(id, integrations, db2) {
|
|
|
28349
28420
|
} catch {}
|
|
28350
28421
|
return getProject2(id, d);
|
|
28351
28422
|
}
|
|
28352
|
-
function archiveProject(id,
|
|
28353
|
-
const d =
|
|
28423
|
+
function archiveProject(id, db3) {
|
|
28424
|
+
const d = db3 || getDatabase2();
|
|
28354
28425
|
const project = getProject2(id, d);
|
|
28355
28426
|
if (!project)
|
|
28356
28427
|
throw new ProjectNotFoundError(id);
|
|
28357
28428
|
d.run("UPDATE projects SET status = 'archived', updated_at = ? WHERE id = ?", [now2(), id]);
|
|
28358
28429
|
return getProject2(id, d);
|
|
28359
28430
|
}
|
|
28360
|
-
function unarchiveProject(id,
|
|
28361
|
-
const d =
|
|
28431
|
+
function unarchiveProject(id, db3) {
|
|
28432
|
+
const d = db3 || getDatabase2();
|
|
28362
28433
|
const project = getProject2(id, d);
|
|
28363
28434
|
if (!project)
|
|
28364
28435
|
throw new ProjectNotFoundError(id);
|
|
@@ -28375,8 +28446,8 @@ function levenshtein(a, b) {
|
|
|
28375
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]);
|
|
28376
28447
|
return dp[m][n];
|
|
28377
28448
|
}
|
|
28378
|
-
function resolveProject(idOrSlug,
|
|
28379
|
-
const d =
|
|
28449
|
+
function resolveProject(idOrSlug, db3) {
|
|
28450
|
+
const d = db3 || getDatabase2();
|
|
28380
28451
|
let project = getProject2(idOrSlug, d);
|
|
28381
28452
|
if (project)
|
|
28382
28453
|
return project;
|
|
@@ -28399,22 +28470,22 @@ function resolveProject(idOrSlug, db2) {
|
|
|
28399
28470
|
function syncRowToLog(row) {
|
|
28400
28471
|
return { ...row, direction: row.direction, status: row.status };
|
|
28401
28472
|
}
|
|
28402
|
-
function startSyncLog(projectId, direction,
|
|
28403
|
-
const d =
|
|
28473
|
+
function startSyncLog(projectId, direction, db3) {
|
|
28474
|
+
const d = db3 || getDatabase2();
|
|
28404
28475
|
const id = uuid2();
|
|
28405
28476
|
const ts = now2();
|
|
28406
28477
|
d.run("INSERT INTO sync_log (id, project_id, direction, status, started_at) VALUES (?, ?, ?, 'running', ?)", [id, projectId, direction, ts]);
|
|
28407
28478
|
return d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
|
|
28408
28479
|
}
|
|
28409
|
-
function completeSyncLog(id, result,
|
|
28410
|
-
const d =
|
|
28480
|
+
function completeSyncLog(id, result, db3) {
|
|
28481
|
+
const d = db3 || getDatabase2();
|
|
28411
28482
|
const status = result.error ? "failed" : "completed";
|
|
28412
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]);
|
|
28413
28484
|
const row = d.query("SELECT * FROM sync_log WHERE id = ?").get(id);
|
|
28414
28485
|
return syncRowToLog(row);
|
|
28415
28486
|
}
|
|
28416
|
-
function listSyncLogs(projectId, limit = 20,
|
|
28417
|
-
const d =
|
|
28487
|
+
function listSyncLogs(projectId, limit = 20, db3) {
|
|
28488
|
+
const d = db3 || getDatabase2();
|
|
28418
28489
|
const rows = d.query("SELECT * FROM sync_log WHERE project_id = ? ORDER BY started_at DESC LIMIT ?").all(projectId, limit);
|
|
28419
28490
|
return rows.map(syncRowToLog);
|
|
28420
28491
|
}
|
|
@@ -58782,7 +58853,7 @@ function buildPeriod(days) {
|
|
|
58782
58853
|
return { periodStart, periodEnd };
|
|
58783
58854
|
}
|
|
58784
58855
|
async function collectComplianceData(options) {
|
|
58785
|
-
const
|
|
58856
|
+
const db3 = getDatabase();
|
|
58786
58857
|
const runsInPeriod = listRuns({
|
|
58787
58858
|
projectId: options.projectId,
|
|
58788
58859
|
limit: 1e4
|
|
@@ -58797,13 +58868,13 @@ async function collectComplianceData(options) {
|
|
|
58797
58868
|
scanConditions.push("project_id = ?");
|
|
58798
58869
|
scanParams.push(options.projectId);
|
|
58799
58870
|
}
|
|
58800
|
-
const scanIssues =
|
|
58871
|
+
const scanIssues = db3.query(`SELECT type, severity FROM scan_issues WHERE ${scanConditions.join(" AND ")}`).all(...scanParams);
|
|
58801
58872
|
const injectionProbesRun = scanIssues.filter((i2) => i2.type === "injection" || i2.type === "sql_injection" || i2.type === "xss").length;
|
|
58802
58873
|
const injectionVulnsFound = scanIssues.filter((i2) => (i2.type === "injection" || i2.type === "xss") && (i2.severity === "high" || i2.severity === "critical")).length;
|
|
58803
58874
|
const piiLeaksDetected = scanIssues.filter((i2) => i2.type === "pii" || i2.type === "pii_leak").length;
|
|
58804
58875
|
let goldenAnswerDriftEvents = 0;
|
|
58805
58876
|
try {
|
|
58806
|
-
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);
|
|
58807
58878
|
goldenAnswerDriftEvents = driftRows?.count ?? 0;
|
|
58808
58879
|
} catch {
|
|
58809
58880
|
goldenAnswerDriftEvents = 0;
|
|
@@ -58815,7 +58886,7 @@ async function collectComplianceData(options) {
|
|
|
58815
58886
|
let flakyScenarioCount = 0;
|
|
58816
58887
|
if (runIds.length > 0) {
|
|
58817
58888
|
const placeholders = runIds.map(() => "?").join(", ");
|
|
58818
|
-
const evalResults =
|
|
58889
|
+
const evalResults = db3.query(`SELECT r.status, r.metadata FROM results r
|
|
58819
58890
|
JOIN scenarios s ON r.scenario_id = s.id
|
|
58820
58891
|
WHERE r.run_id IN (${placeholders}) AND s.scenario_type = 'eval'`).all(...runIds);
|
|
58821
58892
|
evalScenariosRun = evalResults.length;
|
|
@@ -58828,9 +58899,9 @@ async function collectComplianceData(options) {
|
|
|
58828
58899
|
totalEvalScore += er.status === "passed" ? 1 : 0;
|
|
58829
58900
|
}
|
|
58830
58901
|
}
|
|
58831
|
-
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);
|
|
58832
58903
|
a11yViolationsCritical = a11yRows?.count ?? 0;
|
|
58833
|
-
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);
|
|
58834
58905
|
flakyScenarioCount = flakyRows?.count ?? 0;
|
|
58835
58906
|
}
|
|
58836
58907
|
const averageEvalScore = evalScenariosRun > 0 ? totalEvalScore / evalScenariosRun : 1;
|
|
@@ -59649,49 +59720,49 @@ __export(exports_agents, {
|
|
|
59649
59720
|
getAgent: () => getAgent
|
|
59650
59721
|
});
|
|
59651
59722
|
function registerAgent(input) {
|
|
59652
|
-
const
|
|
59653
|
-
const existing =
|
|
59723
|
+
const db3 = getDatabase();
|
|
59724
|
+
const existing = db3.query("SELECT * FROM agents WHERE name = ?").get(input.name);
|
|
59654
59725
|
if (existing) {
|
|
59655
|
-
|
|
59726
|
+
db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), existing.id);
|
|
59656
59727
|
return getAgent(existing.id);
|
|
59657
59728
|
}
|
|
59658
59729
|
const id = uuid();
|
|
59659
59730
|
const timestamp = now();
|
|
59660
|
-
|
|
59731
|
+
db3.query(`
|
|
59661
59732
|
INSERT INTO agents (id, name, description, role, metadata, created_at, last_seen_at)
|
|
59662
59733
|
VALUES (?, ?, ?, ?, '{}', ?, ?)
|
|
59663
59734
|
`).run(id, input.name, input.description ?? null, input.role ?? null, timestamp, timestamp);
|
|
59664
59735
|
return getAgent(id);
|
|
59665
59736
|
}
|
|
59666
59737
|
function getAgent(id) {
|
|
59667
|
-
const
|
|
59668
|
-
const row =
|
|
59738
|
+
const db3 = getDatabase();
|
|
59739
|
+
const row = db3.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
59669
59740
|
return row ? agentFromRow(row) : null;
|
|
59670
59741
|
}
|
|
59671
59742
|
function getAgentByName(name) {
|
|
59672
|
-
const
|
|
59673
|
-
const row =
|
|
59743
|
+
const db3 = getDatabase();
|
|
59744
|
+
const row = db3.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
59674
59745
|
return row ? agentFromRow(row) : null;
|
|
59675
59746
|
}
|
|
59676
59747
|
function listAgents() {
|
|
59677
|
-
const
|
|
59678
|
-
const rows =
|
|
59748
|
+
const db3 = getDatabase();
|
|
59749
|
+
const rows = db3.query("SELECT * FROM agents ORDER BY created_at DESC").all();
|
|
59679
59750
|
return rows.map(agentFromRow);
|
|
59680
59751
|
}
|
|
59681
59752
|
function heartbeatAgent(id) {
|
|
59682
|
-
const
|
|
59683
|
-
const affected =
|
|
59753
|
+
const db3 = getDatabase();
|
|
59754
|
+
const affected = db3.query("UPDATE agents SET last_seen_at = ? WHERE id = ?").run(now(), id);
|
|
59684
59755
|
if (affected.changes === 0)
|
|
59685
59756
|
return null;
|
|
59686
59757
|
return getAgent(id);
|
|
59687
59758
|
}
|
|
59688
59759
|
function setAgentFocus(id, scenarioId) {
|
|
59689
|
-
const
|
|
59760
|
+
const db3 = getDatabase();
|
|
59690
59761
|
const agent = getAgent(id);
|
|
59691
59762
|
if (!agent)
|
|
59692
59763
|
return null;
|
|
59693
59764
|
const metadata = { ...agent.metadata ?? {}, focus: scenarioId };
|
|
59694
|
-
|
|
59765
|
+
db3.query("UPDATE agents SET metadata = ?, last_seen_at = ? WHERE id = ?").run(JSON.stringify(metadata), now(), id);
|
|
59695
59766
|
return getAgent(id);
|
|
59696
59767
|
}
|
|
59697
59768
|
var init_agents = __esm(() => {
|
|
@@ -94075,28 +94146,28 @@ function checkResultFromRow(row) {
|
|
|
94075
94146
|
};
|
|
94076
94147
|
}
|
|
94077
94148
|
function createGoldenAnswer(input) {
|
|
94078
|
-
const
|
|
94149
|
+
const db3 = getDatabase();
|
|
94079
94150
|
const id = uuid();
|
|
94080
94151
|
const short_id = shortUuid();
|
|
94081
94152
|
const timestamp = now();
|
|
94082
|
-
|
|
94153
|
+
db3.query(`
|
|
94083
94154
|
INSERT INTO golden_answers (id, short_id, project_id, question, golden_answer, constraints, endpoint, judge_model, enabled, created_at, updated_at)
|
|
94084
94155
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94085
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);
|
|
94086
94157
|
return getGoldenAnswer(id);
|
|
94087
94158
|
}
|
|
94088
94159
|
function getGoldenAnswer(id) {
|
|
94089
|
-
const
|
|
94090
|
-
let row =
|
|
94160
|
+
const db3 = getDatabase();
|
|
94161
|
+
let row = db3.query("SELECT * FROM golden_answers WHERE id = ?").get(id);
|
|
94091
94162
|
if (row)
|
|
94092
94163
|
return goldenFromRow(row);
|
|
94093
|
-
row =
|
|
94164
|
+
row = db3.query("SELECT * FROM golden_answers WHERE short_id = ?").get(id);
|
|
94094
94165
|
if (row)
|
|
94095
94166
|
return goldenFromRow(row);
|
|
94096
94167
|
return null;
|
|
94097
94168
|
}
|
|
94098
94169
|
function listGoldenAnswers(filter2) {
|
|
94099
|
-
const
|
|
94170
|
+
const db3 = getDatabase();
|
|
94100
94171
|
const conditions = [];
|
|
94101
94172
|
const params = [];
|
|
94102
94173
|
if (filter2?.projectId) {
|
|
@@ -94112,11 +94183,11 @@ function listGoldenAnswers(filter2) {
|
|
|
94112
94183
|
sql += " WHERE " + conditions.join(" AND ");
|
|
94113
94184
|
}
|
|
94114
94185
|
sql += " ORDER BY created_at DESC";
|
|
94115
|
-
const rows =
|
|
94186
|
+
const rows = db3.query(sql).all(...params);
|
|
94116
94187
|
return rows.map(goldenFromRow);
|
|
94117
94188
|
}
|
|
94118
94189
|
function updateGoldenAnswer(id, input) {
|
|
94119
|
-
const
|
|
94190
|
+
const db3 = getDatabase();
|
|
94120
94191
|
const timestamp = now();
|
|
94121
94192
|
const sets = ["updated_at = ?"];
|
|
94122
94193
|
const params = [timestamp];
|
|
@@ -94145,26 +94216,26 @@ function updateGoldenAnswer(id, input) {
|
|
|
94145
94216
|
params.push(input.enabled ? 1 : 0);
|
|
94146
94217
|
}
|
|
94147
94218
|
params.push(id);
|
|
94148
|
-
|
|
94219
|
+
db3.query(`UPDATE golden_answers SET ${sets.join(", ")} WHERE id = ? OR short_id = ?`).run(...params, id);
|
|
94149
94220
|
return getGoldenAnswer(id);
|
|
94150
94221
|
}
|
|
94151
94222
|
function deleteGoldenAnswer(id) {
|
|
94152
|
-
const
|
|
94153
|
-
const result =
|
|
94223
|
+
const db3 = getDatabase();
|
|
94224
|
+
const result = db3.query("DELETE FROM golden_answers WHERE id = ? OR short_id = ?").run(id, id);
|
|
94154
94225
|
return (result.changes ?? 0) > 0;
|
|
94155
94226
|
}
|
|
94156
94227
|
function createGoldenCheckResult(input) {
|
|
94157
|
-
const
|
|
94228
|
+
const db3 = getDatabase();
|
|
94158
94229
|
const id = uuid();
|
|
94159
94230
|
const timestamp = now();
|
|
94160
|
-
|
|
94231
|
+
db3.query(`
|
|
94161
94232
|
INSERT INTO golden_check_results (id, golden_id, response, similarity_score, passed, drift_detected, judge_model, provider, created_at)
|
|
94162
94233
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
94163
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);
|
|
94164
94235
|
return listGoldenCheckResults(input.goldenId, { limit: 1 })[0];
|
|
94165
94236
|
}
|
|
94166
94237
|
function listGoldenCheckResults(goldenId, options) {
|
|
94167
|
-
const
|
|
94238
|
+
const db3 = getDatabase();
|
|
94168
94239
|
const params = [goldenId];
|
|
94169
94240
|
let sql = "SELECT * FROM golden_check_results WHERE golden_id = ?";
|
|
94170
94241
|
if (options?.since) {
|
|
@@ -94176,7 +94247,7 @@ function listGoldenCheckResults(goldenId, options) {
|
|
|
94176
94247
|
sql += " LIMIT ?";
|
|
94177
94248
|
params.push(options.limit);
|
|
94178
94249
|
}
|
|
94179
|
-
const rows =
|
|
94250
|
+
const rows = db3.query(sql).all(...params);
|
|
94180
94251
|
return rows.map(checkResultFromRow);
|
|
94181
94252
|
}
|
|
94182
94253
|
function getLatestGoldenCheckResult(goldenId) {
|
|
@@ -94465,7 +94536,7 @@ import chalk6 from "chalk";
|
|
|
94465
94536
|
// package.json
|
|
94466
94537
|
var package_default = {
|
|
94467
94538
|
name: "@hasna/testers",
|
|
94468
|
-
version: "0.0.
|
|
94539
|
+
version: "0.0.48",
|
|
94469
94540
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
94470
94541
|
type: "module",
|
|
94471
94542
|
main: "dist/index.js",
|
|
@@ -96599,51 +96670,51 @@ function fromRow3(row) {
|
|
|
96599
96670
|
};
|
|
96600
96671
|
}
|
|
96601
96672
|
function createEnvironment(input) {
|
|
96602
|
-
const
|
|
96673
|
+
const db3 = getDatabase();
|
|
96603
96674
|
const id = uuid();
|
|
96604
96675
|
const timestamp = now();
|
|
96605
96676
|
const meta = JSON.stringify({ variables: input.variables ?? {} });
|
|
96606
|
-
|
|
96677
|
+
db3.query(`
|
|
96607
96678
|
INSERT INTO environments (id, name, url, auth_preset_name, project_id, is_default, metadata, created_at)
|
|
96608
96679
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
96609
96680
|
`).run(id, input.name, input.url, input.authPresetName ?? null, input.projectId ?? null, input.isDefault ? 1 : 0, meta, timestamp);
|
|
96610
96681
|
return getEnvironmentById(id);
|
|
96611
96682
|
}
|
|
96612
96683
|
function getEnvironmentById(id) {
|
|
96613
|
-
const
|
|
96614
|
-
const row =
|
|
96684
|
+
const db3 = getDatabase();
|
|
96685
|
+
const row = db3.query("SELECT * FROM environments WHERE id = ?").get(id);
|
|
96615
96686
|
return row ? fromRow3(row) : null;
|
|
96616
96687
|
}
|
|
96617
96688
|
function getEnvironment(name) {
|
|
96618
|
-
const
|
|
96619
|
-
const row =
|
|
96689
|
+
const db3 = getDatabase();
|
|
96690
|
+
const row = db3.query("SELECT * FROM environments WHERE name = ?").get(name);
|
|
96620
96691
|
return row ? fromRow3(row) : null;
|
|
96621
96692
|
}
|
|
96622
96693
|
function listEnvironments(projectId) {
|
|
96623
|
-
const
|
|
96694
|
+
const db3 = getDatabase();
|
|
96624
96695
|
if (projectId) {
|
|
96625
|
-
const rows2 =
|
|
96696
|
+
const rows2 = db3.query("SELECT * FROM environments WHERE project_id = ? ORDER BY is_default DESC, created_at DESC").all(projectId);
|
|
96626
96697
|
return rows2.map(fromRow3);
|
|
96627
96698
|
}
|
|
96628
|
-
const rows =
|
|
96699
|
+
const rows = db3.query("SELECT * FROM environments ORDER BY is_default DESC, created_at DESC").all();
|
|
96629
96700
|
return rows.map(fromRow3);
|
|
96630
96701
|
}
|
|
96631
96702
|
function deleteEnvironment(name) {
|
|
96632
|
-
const
|
|
96633
|
-
const result =
|
|
96703
|
+
const db3 = getDatabase();
|
|
96704
|
+
const result = db3.query("DELETE FROM environments WHERE name = ?").run(name);
|
|
96634
96705
|
return result.changes > 0;
|
|
96635
96706
|
}
|
|
96636
96707
|
function setDefaultEnvironment(name) {
|
|
96637
|
-
const
|
|
96638
|
-
|
|
96639
|
-
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);
|
|
96640
96711
|
if (result.changes === 0) {
|
|
96641
96712
|
throw new Error(`Environment not found: ${name}`);
|
|
96642
96713
|
}
|
|
96643
96714
|
}
|
|
96644
96715
|
function getDefaultEnvironment() {
|
|
96645
|
-
const
|
|
96646
|
-
const row =
|
|
96716
|
+
const db3 = getDatabase();
|
|
96717
|
+
const row = db3.query("SELECT * FROM environments WHERE is_default = 1 LIMIT 1").get();
|
|
96647
96718
|
return row ? fromRow3(row) : null;
|
|
96648
96719
|
}
|
|
96649
96720
|
|
|
@@ -97210,12 +97281,12 @@ async function runRepoTests(opts) {
|
|
|
97210
97281
|
specResults.push(result);
|
|
97211
97282
|
const resultId = uuid();
|
|
97212
97283
|
const timestamp = now();
|
|
97213
|
-
const
|
|
97214
|
-
|
|
97284
|
+
const db3 = getDatabase();
|
|
97285
|
+
db3.exec("PRAGMA foreign_keys = OFF");
|
|
97215
97286
|
try {
|
|
97216
97287
|
const reasoning = result.status === "passed" ? "All tests passed" : (result.error ?? "").slice(0, 500) || null;
|
|
97217
97288
|
const errorStr = result.status !== "passed" ? result.error ?? null : null;
|
|
97218
|
-
|
|
97289
|
+
db3.query(`
|
|
97219
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)
|
|
97220
97291
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, NULL, NULL)
|
|
97221
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({
|
|
@@ -97224,7 +97295,7 @@ async function runRepoTests(opts) {
|
|
|
97224
97295
|
testResults: result.testResults
|
|
97225
97296
|
}), timestamp);
|
|
97226
97297
|
} finally {
|
|
97227
|
-
|
|
97298
|
+
db3.exec("PRAGMA foreign_keys = ON");
|
|
97228
97299
|
}
|
|
97229
97300
|
const resultRecord = { id: resultId };
|
|
97230
97301
|
if (result.stdout || result.stderr) {
|
|
@@ -97365,6 +97436,39 @@ function validateStoredAssertion(value) {
|
|
|
97365
97436
|
}
|
|
97366
97437
|
return describeStoredAssertion(value);
|
|
97367
97438
|
}
|
|
97439
|
+
function envCredentialRef(value) {
|
|
97440
|
+
const trimmed = value?.trim();
|
|
97441
|
+
if (!trimmed)
|
|
97442
|
+
return;
|
|
97443
|
+
return trimmed.startsWith("$") ? trimmed : `$${trimmed}`;
|
|
97444
|
+
}
|
|
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)
|
|
97452
|
+
return;
|
|
97453
|
+
const env = {};
|
|
97454
|
+
for (const value of values ?? []) {
|
|
97455
|
+
const trimmed = value.trim();
|
|
97456
|
+
if (!trimmed)
|
|
97457
|
+
continue;
|
|
97458
|
+
const separator = trimmed.indexOf("=");
|
|
97459
|
+
const key = separator >= 0 ? trimmed.slice(0, separator).trim() : trimmed;
|
|
97460
|
+
assertEnvVarName(key, value);
|
|
97461
|
+
env[key] = separator >= 0 ? trimmed.slice(separator + 1) : `$${key}`;
|
|
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
|
+
}
|
|
97470
|
+
return Object.keys(env).length > 0 ? env : undefined;
|
|
97471
|
+
}
|
|
97368
97472
|
function AddForm({ onComplete }) {
|
|
97369
97473
|
const { exit } = useApp();
|
|
97370
97474
|
const [state, setState] = useState({
|
|
@@ -97669,12 +97773,12 @@ program2.command("add [name]").alias("create").description("Create a new test sc
|
|
|
97669
97773
|
}, []).option("-t, --tag <tag>", "Tag (repeatable)", (val, acc) => {
|
|
97670
97774
|
acc.push(val);
|
|
97671
97775
|
return acc;
|
|
97672
|
-
}, []).option("-p, --priority <level>", "Priority level", "medium").option("-m, --model <model>", "AI model to use").option("--path <path>", "Target path on the URL").option("--auth", "Requires authentication", false).option("--timeout <ms>", "Timeout in milliseconds").option("--project <id>", "Project ID").option("--template <name>", "Seed scenarios from a template (auth, crud, forms, nav, a11y)").option("--assert <assertion>", "Structured assertion (repeatable). Formats: selector:<sel> visible, text:<sel> contains:<text>, no-console-errors, url:contains:<path>, title:contains:<text>, count:<sel> eq:<n>", (val, acc) => {
|
|
97776
|
+
}, []).option("-p, --priority <level>", "Priority level", "medium").option("-m, --model <model>", "AI model to use").option("--path <path>", "Target path on the URL").option("--auth", "Requires authentication", false).option("--auth-preset <name>", "Attach email/password/loginPath from a named auth preset").option("--timeout <ms>", "Timeout in milliseconds").option("--project <id>", "Project ID").option("--template <name>", "Seed scenarios from a template (auth, crud, forms, nav, a11y)").option("--assert <assertion>", "Structured assertion (repeatable). Formats: selector:<sel> visible, text:<sel> contains:<text>, no-console-errors, url:contains:<path>, title:contains:<text>, count:<sel> eq:<n>", (val, acc) => {
|
|
97673
97777
|
acc.push(val);
|
|
97674
97778
|
return acc;
|
|
97675
97779
|
}, []).action(async (name21, opts) => {
|
|
97676
97780
|
try {
|
|
97677
|
-
const hasFlags = opts.description || opts.steps?.length || opts.tag?.length || opts.model || opts.path || opts.auth || opts.timeout || opts.template || opts.assert?.length;
|
|
97781
|
+
const hasFlags = opts.description || opts.steps?.length || opts.tag?.length || opts.model || opts.path || opts.auth || opts.authPreset || opts.timeout || opts.template || opts.assert?.length;
|
|
97678
97782
|
if (!name21 && !hasFlags) {
|
|
97679
97783
|
const projectId2 = resolveProject2(opts.project);
|
|
97680
97784
|
await runInteractiveAdd(projectId2);
|
|
@@ -97699,6 +97803,11 @@ program2.command("add [name]").alias("create").description("Create a new test sc
|
|
|
97699
97803
|
}
|
|
97700
97804
|
const assertions = opts.assert.map(parseAssertionString);
|
|
97701
97805
|
const projectId = resolveProject2(opts.project);
|
|
97806
|
+
const authPreset = opts.authPreset ? getAuthPreset(opts.authPreset) : null;
|
|
97807
|
+
if (opts.authPreset && !authPreset) {
|
|
97808
|
+
logError(chalk6.red(`Auth preset not found: ${opts.authPreset}`));
|
|
97809
|
+
process.exit(1);
|
|
97810
|
+
}
|
|
97702
97811
|
const scenario = createScenario({
|
|
97703
97812
|
name: name21,
|
|
97704
97813
|
description: opts.description || name21,
|
|
@@ -97707,7 +97816,12 @@ program2.command("add [name]").alias("create").description("Create a new test sc
|
|
|
97707
97816
|
priority: opts.priority,
|
|
97708
97817
|
model: opts.model,
|
|
97709
97818
|
targetPath: opts.path,
|
|
97710
|
-
requiresAuth: opts.auth,
|
|
97819
|
+
requiresAuth: opts.auth || Boolean(authPreset),
|
|
97820
|
+
authConfig: authPreset ? {
|
|
97821
|
+
email: authPreset.email,
|
|
97822
|
+
password: authPreset.password,
|
|
97823
|
+
loginPath: authPreset.loginPath
|
|
97824
|
+
} : undefined,
|
|
97711
97825
|
timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
97712
97826
|
assertions: assertions.length > 0 ? assertions : undefined,
|
|
97713
97827
|
projectId
|
|
@@ -99701,12 +99815,22 @@ program2.command("report [run-id]").description("Generate HTML test report or co
|
|
|
99701
99815
|
}
|
|
99702
99816
|
});
|
|
99703
99817
|
var authCmd = program2.command("auth").description("Manage auth presets");
|
|
99704
|
-
authCmd.command("add <name>").description("Create an auth preset").
|
|
99818
|
+
authCmd.command("add <name>").description("Create an auth preset").option("--email <email>", "Login email or credential reference").option("--password <password>", "Login password or credential reference").option("--email-env <name>", "Environment variable name for the login email").option("--password-env <name>", "Environment variable name for the login password").option("--login-path <path>", "Login page path", "/login").action((name21, opts) => {
|
|
99705
99819
|
try {
|
|
99820
|
+
const email3 = opts.email ?? envCredentialRef(opts.emailEnv);
|
|
99821
|
+
const password = opts.password ?? envCredentialRef(opts.passwordEnv);
|
|
99822
|
+
if (!email3) {
|
|
99823
|
+
logError(chalk6.red("Error: provide --email or --email-env"));
|
|
99824
|
+
process.exit(1);
|
|
99825
|
+
}
|
|
99826
|
+
if (!password) {
|
|
99827
|
+
logError(chalk6.red("Error: provide --password or --password-env"));
|
|
99828
|
+
process.exit(1);
|
|
99829
|
+
}
|
|
99706
99830
|
const preset = createAuthPreset({
|
|
99707
99831
|
name: name21,
|
|
99708
|
-
email:
|
|
99709
|
-
password
|
|
99832
|
+
email: email3,
|
|
99833
|
+
password,
|
|
99710
99834
|
loginPath: opts.loginPath
|
|
99711
99835
|
});
|
|
99712
99836
|
log(chalk6.green(`Created auth preset ${chalk6.bold(preset.name)} (${preset.email})`));
|
|
@@ -100501,8 +100625,8 @@ program2.command("doctor").description("Check system setup and configuration").a
|
|
|
100501
100625
|
const dbPath = join20(getTestersDir(), "testers.db");
|
|
100502
100626
|
try {
|
|
100503
100627
|
const { Database: Database5 } = await import("bun:sqlite");
|
|
100504
|
-
const
|
|
100505
|
-
|
|
100628
|
+
const db3 = new Database5(dbPath, { create: true });
|
|
100629
|
+
db3.close();
|
|
100506
100630
|
log(chalk6.green("\u2713") + ` Database accessible: ${dbPath}`);
|
|
100507
100631
|
} catch (err) {
|
|
100508
100632
|
log(chalk6.red("\u2717") + ` Database not accessible at ${dbPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -100968,7 +101092,13 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
100968
101092
|
}, []).option("--priority <level>", "Scenario priority").option("--persona <ids>", "Comma-separated persona IDs").option("--goal <prompt>", "Goal prompt for the agentic testing loop").option("--success <criteria>", "Success criteria (repeatable)", (val, acc) => {
|
|
100969
101093
|
acc.push(val);
|
|
100970
101094
|
return acc;
|
|
100971
|
-
}, []).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-
|
|
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) => {
|
|
101096
|
+
acc.push(val);
|
|
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;
|
|
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) => {
|
|
100972
101102
|
try {
|
|
100973
101103
|
const workflow = createTestingWorkflow({
|
|
100974
101104
|
name: name21,
|
|
@@ -100994,6 +101124,7 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
100994
101124
|
sandboxSyncStrategy: opts.sandboxSync,
|
|
100995
101125
|
setupCommand: opts.sandboxSetupCommand,
|
|
100996
101126
|
packageSpec: opts.sandboxPackage,
|
|
101127
|
+
env: parseSandboxEnv(opts.sandboxEnv, opts.sandboxEnvOptional),
|
|
100997
101128
|
appSourceDir: opts.sandboxAppSource,
|
|
100998
101129
|
appRemoteDir: opts.sandboxAppRemoteDir,
|
|
100999
101130
|
appStartCommand: opts.sandboxAppStartCommand,
|
|
@@ -101012,6 +101143,36 @@ workflowCmd.command("create <name>").description("Save a reusable testing workfl
|
|
|
101012
101143
|
process.exit(1);
|
|
101013
101144
|
}
|
|
101014
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
|
+
});
|
|
101015
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) => {
|
|
101016
101177
|
const workflows = listTestingWorkflows({
|
|
101017
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
|
];
|
|
@@ -17445,7 +17447,7 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17445
17447
|
workflowId: plan.workflow.id,
|
|
17446
17448
|
workflowName: plan.workflow.name
|
|
17447
17449
|
},
|
|
17448
|
-
sandboxEnvVars: plan.sandbox.env,
|
|
17450
|
+
sandboxEnvVars: resolveSandboxEnv(plan.sandbox.env),
|
|
17449
17451
|
cleanup: plan.sandbox.cleanup,
|
|
17450
17452
|
upload: {
|
|
17451
17453
|
localDir: bundle.localDir,
|
|
@@ -17471,6 +17473,26 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17471
17473
|
bundle.cleanup?.();
|
|
17472
17474
|
}
|
|
17473
17475
|
}
|
|
17476
|
+
function resolveSandboxEnv(env2) {
|
|
17477
|
+
if (!env2 || Object.keys(env2).length === 0)
|
|
17478
|
+
return;
|
|
17479
|
+
const resolved = {};
|
|
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
|
+
}
|
|
17488
|
+
const resolvedValue = resolveCredential(value);
|
|
17489
|
+
if (resolvedValue === null) {
|
|
17490
|
+
throw new Error(`Missing sandbox env value for ${key}`);
|
|
17491
|
+
}
|
|
17492
|
+
resolved[key] = resolvedValue;
|
|
17493
|
+
}
|
|
17494
|
+
return resolved;
|
|
17495
|
+
}
|
|
17474
17496
|
async function resolveSandboxesRuntime(dependencies) {
|
|
17475
17497
|
if (dependencies.sandboxes)
|
|
17476
17498
|
return dependencies.sandboxes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
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",
|
|
@@ -23723,7 +23723,7 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23723
23723
|
workflowId: plan.workflow.id,
|
|
23724
23724
|
workflowName: plan.workflow.name
|
|
23725
23725
|
},
|
|
23726
|
-
sandboxEnvVars: plan.sandbox.env,
|
|
23726
|
+
sandboxEnvVars: resolveSandboxEnv(plan.sandbox.env),
|
|
23727
23727
|
cleanup: plan.sandbox.cleanup,
|
|
23728
23728
|
upload: {
|
|
23729
23729
|
localDir: bundle.localDir,
|
|
@@ -23749,6 +23749,26 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23749
23749
|
bundle.cleanup?.();
|
|
23750
23750
|
}
|
|
23751
23751
|
}
|
|
23752
|
+
function resolveSandboxEnv(env2) {
|
|
23753
|
+
if (!env2 || Object.keys(env2).length === 0)
|
|
23754
|
+
return;
|
|
23755
|
+
const resolved = {};
|
|
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
|
+
}
|
|
23764
|
+
const resolvedValue = resolveCredential(value);
|
|
23765
|
+
if (resolvedValue === null) {
|
|
23766
|
+
throw new Error(`Missing sandbox env value for ${key}`);
|
|
23767
|
+
}
|
|
23768
|
+
resolved[key] = resolvedValue;
|
|
23769
|
+
}
|
|
23770
|
+
return resolved;
|
|
23771
|
+
}
|
|
23752
23772
|
async function resolveSandboxesRuntime(dependencies) {
|
|
23753
23773
|
if (dependencies.sandboxes)
|
|
23754
23774
|
return dependencies.sandboxes;
|
|
@@ -23766,6 +23786,7 @@ var init_workflow_runner = __esm(() => {
|
|
|
23766
23786
|
init_workflows();
|
|
23767
23787
|
init_personas();
|
|
23768
23788
|
init_runner();
|
|
23789
|
+
init_secrets_resolver();
|
|
23769
23790
|
APP_SOURCE_EXCLUDES = [
|
|
23770
23791
|
"node_modules",
|
|
23771
23792
|
".git",
|
|
@@ -23773,6 +23794,8 @@ var init_workflow_runner = __esm(() => {
|
|
|
23773
23794
|
".next",
|
|
23774
23795
|
".turbo",
|
|
23775
23796
|
".cache",
|
|
23797
|
+
".env",
|
|
23798
|
+
".env.*",
|
|
23776
23799
|
".venv",
|
|
23777
23800
|
"__pycache__"
|
|
23778
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
|
];
|
|
@@ -51561,7 +51563,7 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51561
51563
|
workflowId: plan.workflow.id,
|
|
51562
51564
|
workflowName: plan.workflow.name
|
|
51563
51565
|
},
|
|
51564
|
-
sandboxEnvVars: plan.sandbox.env,
|
|
51566
|
+
sandboxEnvVars: resolveSandboxEnv(plan.sandbox.env),
|
|
51565
51567
|
cleanup: plan.sandbox.cleanup,
|
|
51566
51568
|
upload: {
|
|
51567
51569
|
localDir: bundle.localDir,
|
|
@@ -51587,6 +51589,26 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51587
51589
|
bundle.cleanup?.();
|
|
51588
51590
|
}
|
|
51589
51591
|
}
|
|
51592
|
+
function resolveSandboxEnv(env) {
|
|
51593
|
+
if (!env || Object.keys(env).length === 0)
|
|
51594
|
+
return;
|
|
51595
|
+
const resolved = {};
|
|
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
|
+
}
|
|
51604
|
+
const resolvedValue = resolveCredential(value);
|
|
51605
|
+
if (resolvedValue === null) {
|
|
51606
|
+
throw new Error(`Missing sandbox env value for ${key}`);
|
|
51607
|
+
}
|
|
51608
|
+
resolved[key] = resolvedValue;
|
|
51609
|
+
}
|
|
51610
|
+
return resolved;
|
|
51611
|
+
}
|
|
51590
51612
|
async function resolveSandboxesRuntime(dependencies) {
|
|
51591
51613
|
if (dependencies.sandboxes)
|
|
51592
51614
|
return dependencies.sandboxes;
|