@hasna/testers 0.0.42 → 0.0.44
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/README.md +21 -0
- package/dist/cli/index.js +359 -179
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/scenarios.d.ts.map +1 -1
- package/dist/index.js +34 -7
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/workflow-fanout.d.ts +36 -0
- package/dist/lib/workflow-fanout.d.ts.map +1 -0
- package/dist/lib/workflow-runner.d.ts +3 -1
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +38 -10
- package/dist/server/index.js +36 -9
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/db/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,mBAAmB,CAAC;AAQ3B,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAuBhE;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAyB5E;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAIrD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAI7D;AAED,wBAAgB,YAAY,IAAI,OAAO,EAAE,CAIxC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAajE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/db/scenarios.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAC;AA6B3B,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CA+BnE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAmBvD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIrE;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,QAAQ,EAAE,CAoFjE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAwFhG;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CA8B9D;AAED,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAmBhE;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAIvE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,MAAM,MAAM,oBAAoB,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAErG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,oBAAoB,CAuDlG"}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,11 @@ function cleanupValue(value) {
|
|
|
37
37
|
return value;
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
function syncStrategyValue(value) {
|
|
41
|
+
if (value === "archive" || value === "rsync")
|
|
42
|
+
return value;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
40
45
|
function workflowExecutionFromValue(value) {
|
|
41
46
|
const input = isRecord(value) ? value : {};
|
|
42
47
|
const rawTarget = stringValue(input["target"]) ?? "local";
|
|
@@ -51,6 +56,7 @@ function workflowExecutionFromValue(value) {
|
|
|
51
56
|
const sandboxImage = stringValue(input["sandboxImage"]) ?? stringValue(input["sandboxTemplate"]);
|
|
52
57
|
const sandboxRemoteDir = stringValue(input["sandboxRemoteDir"]);
|
|
53
58
|
const sandboxCleanup = cleanupValue(input["sandboxCleanup"]);
|
|
59
|
+
const sandboxSyncStrategy = syncStrategyValue(input["sandboxSyncStrategy"]);
|
|
54
60
|
const setupCommand = stringValue(input["setupCommand"]);
|
|
55
61
|
const packageSpec = stringValue(input["packageSpec"]);
|
|
56
62
|
const timeoutMs = numberValue(input["timeoutMs"]);
|
|
@@ -61,6 +67,7 @@ function workflowExecutionFromValue(value) {
|
|
|
61
67
|
...sandboxImage ? { sandboxImage } : {},
|
|
62
68
|
...sandboxRemoteDir ? { sandboxRemoteDir } : {},
|
|
63
69
|
...sandboxCleanup ? { sandboxCleanup } : {},
|
|
70
|
+
...sandboxSyncStrategy ? { sandboxSyncStrategy } : {},
|
|
64
71
|
...setupCommand ? { setupCommand } : {},
|
|
65
72
|
...packageSpec ? { packageSpec } : {},
|
|
66
73
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -92,6 +99,8 @@ function projectFromRow(row) {
|
|
|
92
99
|
baseUrl: row.base_url ?? null,
|
|
93
100
|
port: row.port ?? null,
|
|
94
101
|
settings: row.settings ? JSON.parse(row.settings) : {},
|
|
102
|
+
scenarioPrefix: row.scenario_prefix ?? "TST",
|
|
103
|
+
scenarioCounter: row.scenario_counter ?? 0,
|
|
95
104
|
createdAt: row.created_at,
|
|
96
105
|
updatedAt: row.updated_at
|
|
97
106
|
};
|
|
@@ -10704,6 +10713,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
|
|
|
10704
10713
|
function getDefaultConfig() {
|
|
10705
10714
|
return {
|
|
10706
10715
|
defaultModel: "claude-haiku-4-5-20251001",
|
|
10716
|
+
defaultImageModel: DEFAULT_IMAGE_MODEL,
|
|
10707
10717
|
models: { ...MODEL_MAP },
|
|
10708
10718
|
browser: {
|
|
10709
10719
|
headless: true,
|
|
@@ -10730,6 +10740,7 @@ function loadConfig() {
|
|
|
10730
10740
|
}
|
|
10731
10741
|
const config = {
|
|
10732
10742
|
defaultModel: fileConfig.defaultModel ?? defaults2.defaultModel,
|
|
10743
|
+
defaultImageModel: fileConfig.defaultImageModel ?? defaults2.defaultImageModel,
|
|
10733
10744
|
models: fileConfig.models ? { ...defaults2.models, ...fileConfig.models } : { ...defaults2.models },
|
|
10734
10745
|
browser: fileConfig.browser ? { ...defaults2.browser, ...fileConfig.browser } : { ...defaults2.browser },
|
|
10735
10746
|
screenshots: fileConfig.screenshots ? { ...defaults2.screenshots, ...fileConfig.screenshots } : { ...defaults2.screenshots },
|
|
@@ -10745,6 +10756,10 @@ function loadConfig() {
|
|
|
10745
10756
|
if (envModel) {
|
|
10746
10757
|
config.defaultModel = envModel;
|
|
10747
10758
|
}
|
|
10759
|
+
const envImageModel = process.env["TESTERS_IMAGE_MODEL"];
|
|
10760
|
+
if (envImageModel) {
|
|
10761
|
+
config.defaultImageModel = envImageModel;
|
|
10762
|
+
}
|
|
10748
10763
|
const envScreenshotsDir = process.env["TESTERS_SCREENSHOTS_DIR"];
|
|
10749
10764
|
if (envScreenshotsDir) {
|
|
10750
10765
|
config.screenshots.dir = envScreenshotsDir;
|
|
@@ -10765,7 +10780,7 @@ function resolveModel(nameOrId) {
|
|
|
10765
10780
|
}
|
|
10766
10781
|
return nameOrId;
|
|
10767
10782
|
}
|
|
10768
|
-
var CONFIG_DIR3, CONFIG_PATH2;
|
|
10783
|
+
var CONFIG_DIR3, CONFIG_PATH2, DEFAULT_IMAGE_MODEL = "gpt-image-2";
|
|
10769
10784
|
var init_config2 = __esm(() => {
|
|
10770
10785
|
init_types();
|
|
10771
10786
|
init_paths();
|
|
@@ -13238,9 +13253,14 @@ function nextShortId(projectId) {
|
|
|
13238
13253
|
if (projectId) {
|
|
13239
13254
|
const project = db2.query("SELECT scenario_prefix, scenario_counter FROM projects WHERE id = ?").get(projectId);
|
|
13240
13255
|
if (project) {
|
|
13241
|
-
|
|
13256
|
+
let next = (project.scenario_counter ?? 0) + 1;
|
|
13257
|
+
let shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
13258
|
+
while (db2.query("SELECT 1 FROM scenarios WHERE short_id = ?").get(shortId)) {
|
|
13259
|
+
next += 1;
|
|
13260
|
+
shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
13261
|
+
}
|
|
13242
13262
|
db2.query("UPDATE projects SET scenario_counter = ? WHERE id = ?").run(next, projectId);
|
|
13243
|
-
return
|
|
13263
|
+
return shortId;
|
|
13244
13264
|
}
|
|
13245
13265
|
}
|
|
13246
13266
|
return shortUuid();
|
|
@@ -13552,14 +13572,19 @@ function getScreenshotsByResult(resultId) {
|
|
|
13552
13572
|
// src/db/projects.ts
|
|
13553
13573
|
init_types();
|
|
13554
13574
|
init_database();
|
|
13575
|
+
function normalizeScenarioPrefix(prefix) {
|
|
13576
|
+
const normalized = (prefix ?? "TST").trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
13577
|
+
return normalized || "TST";
|
|
13578
|
+
}
|
|
13555
13579
|
function createProject(input) {
|
|
13556
13580
|
const db2 = getDatabase();
|
|
13557
13581
|
const id = uuid();
|
|
13558
13582
|
const timestamp = now();
|
|
13583
|
+
const scenarioPrefix = normalizeScenarioPrefix(input.scenarioPrefix);
|
|
13559
13584
|
db2.query(`
|
|
13560
|
-
INSERT INTO projects (id, name, path, description, base_url, port, settings, created_at, updated_at)
|
|
13561
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
13562
|
-
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", timestamp, timestamp);
|
|
13585
|
+
INSERT INTO projects (id, name, path, description, base_url, port, settings, scenario_prefix, scenario_counter, created_at, updated_at)
|
|
13586
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
13587
|
+
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", scenarioPrefix, timestamp, timestamp);
|
|
13563
13588
|
return getProject(id);
|
|
13564
13589
|
}
|
|
13565
13590
|
function getProject(id) {
|
|
@@ -17243,6 +17268,7 @@ function buildSandboxPlan(workflow, execution, runOptions) {
|
|
|
17243
17268
|
remoteDir,
|
|
17244
17269
|
stateRemoteDir,
|
|
17245
17270
|
cleanup: execution.sandboxCleanup ?? "delete",
|
|
17271
|
+
syncStrategy: execution.sandboxSyncStrategy ?? "rsync",
|
|
17246
17272
|
timeoutMs: execution.timeoutMs,
|
|
17247
17273
|
env: execution.env,
|
|
17248
17274
|
command: buildSandboxCommand({
|
|
@@ -17305,7 +17331,8 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17305
17331
|
cleanup: plan.sandbox.cleanup,
|
|
17306
17332
|
upload: {
|
|
17307
17333
|
localDir: bundle.localDir,
|
|
17308
|
-
remoteDir: bundle.remoteDir
|
|
17334
|
+
remoteDir: bundle.remoteDir,
|
|
17335
|
+
syncStrategy: plan.sandbox.syncStrategy
|
|
17309
17336
|
}
|
|
17310
17337
|
});
|
|
17311
17338
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,mBAAmB,CAAC;AAMpE,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAkBhD;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,aAAa,CA6D1C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { runTestingWorkflow, type WorkflowRunOptions, type WorkflowRunnerDependencies } from "./workflow-runner.js";
|
|
2
|
+
import type { TestingWorkflow } from "../types/index.js";
|
|
3
|
+
export interface WorkflowFanoutOptions extends WorkflowRunOptions {
|
|
4
|
+
workflowIds?: string[];
|
|
5
|
+
projectId?: string;
|
|
6
|
+
tags?: string[];
|
|
7
|
+
includeDisabled?: boolean;
|
|
8
|
+
workers?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkflowFanoutItem {
|
|
11
|
+
workflowId: string;
|
|
12
|
+
workflowName: string;
|
|
13
|
+
status: "dry-run" | "passed" | "failed";
|
|
14
|
+
sandboxId?: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
exitCode?: number;
|
|
17
|
+
stdout?: string;
|
|
18
|
+
stderr?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
plan?: Awaited<ReturnType<typeof runTestingWorkflow>>["plan"];
|
|
21
|
+
}
|
|
22
|
+
export interface WorkflowFanoutResult {
|
|
23
|
+
status: "passed" | "failed" | "dry-run";
|
|
24
|
+
workers: number;
|
|
25
|
+
total: number;
|
|
26
|
+
passed: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
items: WorkflowFanoutItem[];
|
|
29
|
+
}
|
|
30
|
+
export interface WorkflowFanoutDependencies extends WorkflowRunnerDependencies {
|
|
31
|
+
runTestingWorkflow?: typeof runTestingWorkflow;
|
|
32
|
+
}
|
|
33
|
+
export declare function normalizeFanoutWorkerCount(value: number | undefined): number;
|
|
34
|
+
export declare function resolveWorkflowFanoutSelection(options: Pick<WorkflowFanoutOptions, "workflowIds" | "projectId" | "tags" | "includeDisabled">): TestingWorkflow[];
|
|
35
|
+
export declare function runWorkflowFanout(options: WorkflowFanoutOptions, dependencies?: WorkflowFanoutDependencies): Promise<WorkflowFanoutResult>;
|
|
36
|
+
//# sourceMappingURL=workflow-fanout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-fanout.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-fanout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC/D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA2B,SAAQ,0BAA0B;IAC5E,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CAChD;AASD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAM5E;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,iBAAiB,CAAC,GAAG,eAAe,EAAE,CA8BhK;AAuBD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,EAC9B,YAAY,GAAE,0BAA+B,GAC5C,OAAO,CAAC,oBAAoB,CAAC,CAyD/B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { runByFilter, type RunOptions } from "./runner.js";
|
|
2
|
-
import type { Result, Run, TestingWorkflow, WorkflowSandboxCleanup } from "../types/index.js";
|
|
2
|
+
import type { Result, Run, TestingWorkflow, WorkflowSandboxCleanup, WorkflowSandboxSyncStrategy } from "../types/index.js";
|
|
3
3
|
export interface WorkflowRunOptions {
|
|
4
4
|
url: string;
|
|
5
5
|
model?: string;
|
|
@@ -16,6 +16,7 @@ export interface WorkflowSandboxPlan {
|
|
|
16
16
|
stateRemoteDir: string;
|
|
17
17
|
command: string;
|
|
18
18
|
cleanup: WorkflowSandboxCleanup;
|
|
19
|
+
syncStrategy: WorkflowSandboxSyncStrategy;
|
|
19
20
|
timeoutMs?: number;
|
|
20
21
|
env?: Record<string, string>;
|
|
21
22
|
}
|
|
@@ -71,6 +72,7 @@ export interface WorkflowSandboxesRuntime {
|
|
|
71
72
|
upload: {
|
|
72
73
|
localDir: string;
|
|
73
74
|
remoteDir: string;
|
|
75
|
+
syncStrategy?: WorkflowSandboxSyncStrategy;
|
|
74
76
|
};
|
|
75
77
|
cleanup?: WorkflowSandboxCleanup;
|
|
76
78
|
onStdout?: (data: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,MAAM,EACN,GAAG,EACH,eAAe,EAEf,sBAAsB,
|
|
1
|
+
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,MAAM,EACN,GAAG,EACH,eAAe,EAEf,sBAAsB,EACtB,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,sBAAsB,CAAC;IAChC,YAAY,EAAE,2BAA2B,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,UAAU,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACxF,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,oBAAoB;IAC5B,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACxB,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACxB,MAAM,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,mBAAmB,CAAC,KAAK,EAAE;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACxC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,EAAE;YACN,QAAQ,EAAE,MAAM,CAAC;YACjB,SAAS,EAAE,MAAM,CAAC;YAClB,YAAY,CAAC,EAAE,2BAA2B,CAAC;SAC5C,CAAC;QACF,OAAO,CAAC,EAAE,sBAAsB,CAAC;QACjC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;KACnC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,OAAO,WAAW,CAAC;IACjC,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACxF,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,KAAK,sBAAsB,CAAC;CACrG;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,kBAAkB,GAAG,eAAe,CAqB5G;AAED,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAC3B,YAAY,GAAE,0BAA+B,GAC5C,OAAO,CAAC;IACT,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,CAAC,EAAE,8BAA8B,CAAC;CAChD,CAAC,CAiBD;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,eAAe,GACpB,sBAAsB,CASxB"}
|
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.44",
|
|
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",
|
|
@@ -98,7 +98,7 @@ var init_package = __esm(() => {
|
|
|
98
98
|
"@hasna/cloud": "^0.1.24",
|
|
99
99
|
"@hasna/contacts": "^0.6.8",
|
|
100
100
|
"@hasna/projects": "^0.1.42",
|
|
101
|
-
"@hasna/sandboxes": "^0.1.
|
|
101
|
+
"@hasna/sandboxes": "^0.1.28",
|
|
102
102
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
103
103
|
ai: "^6.0.175",
|
|
104
104
|
chalk: "^5.4.1",
|
|
@@ -14156,6 +14156,11 @@ function cleanupValue(value) {
|
|
|
14156
14156
|
return value;
|
|
14157
14157
|
return;
|
|
14158
14158
|
}
|
|
14159
|
+
function syncStrategyValue(value) {
|
|
14160
|
+
if (value === "archive" || value === "rsync")
|
|
14161
|
+
return value;
|
|
14162
|
+
return;
|
|
14163
|
+
}
|
|
14159
14164
|
function workflowExecutionFromValue(value) {
|
|
14160
14165
|
const input = isRecord(value) ? value : {};
|
|
14161
14166
|
const rawTarget = stringValue(input["target"]) ?? "local";
|
|
@@ -14170,6 +14175,7 @@ function workflowExecutionFromValue(value) {
|
|
|
14170
14175
|
const sandboxImage = stringValue(input["sandboxImage"]) ?? stringValue(input["sandboxTemplate"]);
|
|
14171
14176
|
const sandboxRemoteDir = stringValue(input["sandboxRemoteDir"]);
|
|
14172
14177
|
const sandboxCleanup = cleanupValue(input["sandboxCleanup"]);
|
|
14178
|
+
const sandboxSyncStrategy = syncStrategyValue(input["sandboxSyncStrategy"]);
|
|
14173
14179
|
const setupCommand = stringValue(input["setupCommand"]);
|
|
14174
14180
|
const packageSpec = stringValue(input["packageSpec"]);
|
|
14175
14181
|
const timeoutMs = numberValue(input["timeoutMs"]);
|
|
@@ -14180,6 +14186,7 @@ function workflowExecutionFromValue(value) {
|
|
|
14180
14186
|
...sandboxImage ? { sandboxImage } : {},
|
|
14181
14187
|
...sandboxRemoteDir ? { sandboxRemoteDir } : {},
|
|
14182
14188
|
...sandboxCleanup ? { sandboxCleanup } : {},
|
|
14189
|
+
...sandboxSyncStrategy ? { sandboxSyncStrategy } : {},
|
|
14183
14190
|
...setupCommand ? { setupCommand } : {},
|
|
14184
14191
|
...packageSpec ? { packageSpec } : {},
|
|
14185
14192
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -14211,6 +14218,8 @@ function projectFromRow(row) {
|
|
|
14211
14218
|
baseUrl: row.base_url ?? null,
|
|
14212
14219
|
port: row.port ?? null,
|
|
14213
14220
|
settings: row.settings ? JSON.parse(row.settings) : {},
|
|
14221
|
+
scenarioPrefix: row.scenario_prefix ?? "TST",
|
|
14222
|
+
scenarioCounter: row.scenario_counter ?? 0,
|
|
14214
14223
|
createdAt: row.created_at,
|
|
14215
14224
|
updatedAt: row.updated_at
|
|
14216
14225
|
};
|
|
@@ -15030,9 +15039,14 @@ function nextShortId(projectId) {
|
|
|
15030
15039
|
if (projectId) {
|
|
15031
15040
|
const project = db2.query("SELECT scenario_prefix, scenario_counter FROM projects WHERE id = ?").get(projectId);
|
|
15032
15041
|
if (project) {
|
|
15033
|
-
|
|
15042
|
+
let next = (project.scenario_counter ?? 0) + 1;
|
|
15043
|
+
let shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
15044
|
+
while (db2.query("SELECT 1 FROM scenarios WHERE short_id = ?").get(shortId)) {
|
|
15045
|
+
next += 1;
|
|
15046
|
+
shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
15047
|
+
}
|
|
15034
15048
|
db2.query("UPDATE projects SET scenario_counter = ? WHERE id = ?").run(next, projectId);
|
|
15035
|
-
return
|
|
15049
|
+
return shortId;
|
|
15036
15050
|
}
|
|
15037
15051
|
}
|
|
15038
15052
|
return shortUuid();
|
|
@@ -15556,14 +15570,19 @@ var init_screenshots = __esm(() => {
|
|
|
15556
15570
|
});
|
|
15557
15571
|
|
|
15558
15572
|
// src/db/projects.ts
|
|
15573
|
+
function normalizeScenarioPrefix(prefix) {
|
|
15574
|
+
const normalized = (prefix ?? "TST").trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
15575
|
+
return normalized || "TST";
|
|
15576
|
+
}
|
|
15559
15577
|
function createProject(input) {
|
|
15560
15578
|
const db2 = getDatabase();
|
|
15561
15579
|
const id = uuid();
|
|
15562
15580
|
const timestamp = now();
|
|
15581
|
+
const scenarioPrefix = normalizeScenarioPrefix(input.scenarioPrefix);
|
|
15563
15582
|
db2.query(`
|
|
15564
|
-
INSERT INTO projects (id, name, path, description, base_url, port, settings, created_at, updated_at)
|
|
15565
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
15566
|
-
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", timestamp, timestamp);
|
|
15583
|
+
INSERT INTO projects (id, name, path, description, base_url, port, settings, scenario_prefix, scenario_counter, created_at, updated_at)
|
|
15584
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
15585
|
+
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", scenarioPrefix, timestamp, timestamp);
|
|
15567
15586
|
return getProject(id);
|
|
15568
15587
|
}
|
|
15569
15588
|
function getProject(id) {
|
|
@@ -16545,13 +16564,15 @@ var exports_config2 = {};
|
|
|
16545
16564
|
__export(exports_config2, {
|
|
16546
16565
|
resolveModel: () => resolveModel,
|
|
16547
16566
|
loadConfig: () => loadConfig,
|
|
16548
|
-
getDefaultConfig: () => getDefaultConfig
|
|
16567
|
+
getDefaultConfig: () => getDefaultConfig,
|
|
16568
|
+
DEFAULT_IMAGE_MODEL: () => DEFAULT_IMAGE_MODEL
|
|
16549
16569
|
});
|
|
16550
16570
|
import { join as join9 } from "path";
|
|
16551
16571
|
import { readFileSync as readFileSync2, existsSync as existsSync7 } from "fs";
|
|
16552
16572
|
function getDefaultConfig() {
|
|
16553
16573
|
return {
|
|
16554
16574
|
defaultModel: "claude-haiku-4-5-20251001",
|
|
16575
|
+
defaultImageModel: DEFAULT_IMAGE_MODEL,
|
|
16555
16576
|
models: { ...MODEL_MAP },
|
|
16556
16577
|
browser: {
|
|
16557
16578
|
headless: true,
|
|
@@ -16578,6 +16599,7 @@ function loadConfig() {
|
|
|
16578
16599
|
}
|
|
16579
16600
|
const config = {
|
|
16580
16601
|
defaultModel: fileConfig.defaultModel ?? defaults2.defaultModel,
|
|
16602
|
+
defaultImageModel: fileConfig.defaultImageModel ?? defaults2.defaultImageModel,
|
|
16581
16603
|
models: fileConfig.models ? { ...defaults2.models, ...fileConfig.models } : { ...defaults2.models },
|
|
16582
16604
|
browser: fileConfig.browser ? { ...defaults2.browser, ...fileConfig.browser } : { ...defaults2.browser },
|
|
16583
16605
|
screenshots: fileConfig.screenshots ? { ...defaults2.screenshots, ...fileConfig.screenshots } : { ...defaults2.screenshots },
|
|
@@ -16593,6 +16615,10 @@ function loadConfig() {
|
|
|
16593
16615
|
if (envModel) {
|
|
16594
16616
|
config.defaultModel = envModel;
|
|
16595
16617
|
}
|
|
16618
|
+
const envImageModel = process.env["TESTERS_IMAGE_MODEL"];
|
|
16619
|
+
if (envImageModel) {
|
|
16620
|
+
config.defaultImageModel = envImageModel;
|
|
16621
|
+
}
|
|
16596
16622
|
const envScreenshotsDir = process.env["TESTERS_SCREENSHOTS_DIR"];
|
|
16597
16623
|
if (envScreenshotsDir) {
|
|
16598
16624
|
config.screenshots.dir = envScreenshotsDir;
|
|
@@ -16613,7 +16639,7 @@ function resolveModel(nameOrId) {
|
|
|
16613
16639
|
}
|
|
16614
16640
|
return nameOrId;
|
|
16615
16641
|
}
|
|
16616
|
-
var CONFIG_DIR3, CONFIG_PATH2;
|
|
16642
|
+
var CONFIG_DIR3, CONFIG_PATH2, DEFAULT_IMAGE_MODEL = "gpt-image-2";
|
|
16617
16643
|
var init_config2 = __esm(() => {
|
|
16618
16644
|
init_types3();
|
|
16619
16645
|
init_paths();
|
|
@@ -23530,6 +23556,7 @@ function buildSandboxPlan(workflow, execution, runOptions) {
|
|
|
23530
23556
|
remoteDir,
|
|
23531
23557
|
stateRemoteDir,
|
|
23532
23558
|
cleanup: execution.sandboxCleanup ?? "delete",
|
|
23559
|
+
syncStrategy: execution.sandboxSyncStrategy ?? "rsync",
|
|
23533
23560
|
timeoutMs: execution.timeoutMs,
|
|
23534
23561
|
env: execution.env,
|
|
23535
23562
|
command: buildSandboxCommand({
|
|
@@ -23592,7 +23619,8 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23592
23619
|
cleanup: plan.sandbox.cleanup,
|
|
23593
23620
|
upload: {
|
|
23594
23621
|
localDir: bundle.localDir,
|
|
23595
|
-
remoteDir: bundle.remoteDir
|
|
23622
|
+
remoteDir: bundle.remoteDir,
|
|
23623
|
+
syncStrategy: plan.sandbox.syncStrategy
|
|
23596
23624
|
}
|
|
23597
23625
|
});
|
|
23598
23626
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
package/dist/server/index.js
CHANGED
|
@@ -4056,6 +4056,11 @@ function cleanupValue(value) {
|
|
|
4056
4056
|
return value;
|
|
4057
4057
|
return;
|
|
4058
4058
|
}
|
|
4059
|
+
function syncStrategyValue(value) {
|
|
4060
|
+
if (value === "archive" || value === "rsync")
|
|
4061
|
+
return value;
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4059
4064
|
function workflowExecutionFromValue(value) {
|
|
4060
4065
|
const input = isRecord(value) ? value : {};
|
|
4061
4066
|
const rawTarget = stringValue(input["target"]) ?? "local";
|
|
@@ -4070,6 +4075,7 @@ function workflowExecutionFromValue(value) {
|
|
|
4070
4075
|
const sandboxImage = stringValue(input["sandboxImage"]) ?? stringValue(input["sandboxTemplate"]);
|
|
4071
4076
|
const sandboxRemoteDir = stringValue(input["sandboxRemoteDir"]);
|
|
4072
4077
|
const sandboxCleanup = cleanupValue(input["sandboxCleanup"]);
|
|
4078
|
+
const sandboxSyncStrategy = syncStrategyValue(input["sandboxSyncStrategy"]);
|
|
4073
4079
|
const setupCommand = stringValue(input["setupCommand"]);
|
|
4074
4080
|
const packageSpec = stringValue(input["packageSpec"]);
|
|
4075
4081
|
const timeoutMs = numberValue(input["timeoutMs"]);
|
|
@@ -4080,6 +4086,7 @@ function workflowExecutionFromValue(value) {
|
|
|
4080
4086
|
...sandboxImage ? { sandboxImage } : {},
|
|
4081
4087
|
...sandboxRemoteDir ? { sandboxRemoteDir } : {},
|
|
4082
4088
|
...sandboxCleanup ? { sandboxCleanup } : {},
|
|
4089
|
+
...sandboxSyncStrategy ? { sandboxSyncStrategy } : {},
|
|
4083
4090
|
...setupCommand ? { setupCommand } : {},
|
|
4084
4091
|
...packageSpec ? { packageSpec } : {},
|
|
4085
4092
|
...timeoutMs !== undefined ? { timeoutMs } : {},
|
|
@@ -4111,6 +4118,8 @@ function projectFromRow(row) {
|
|
|
4111
4118
|
baseUrl: row.base_url ?? null,
|
|
4112
4119
|
port: row.port ?? null,
|
|
4113
4120
|
settings: row.settings ? JSON.parse(row.settings) : {},
|
|
4121
|
+
scenarioPrefix: row.scenario_prefix ?? "TST",
|
|
4122
|
+
scenarioCounter: row.scenario_counter ?? 0,
|
|
4114
4123
|
createdAt: row.created_at,
|
|
4115
4124
|
updatedAt: row.updated_at
|
|
4116
4125
|
};
|
|
@@ -15270,6 +15279,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync6 } from "fs";
|
|
|
15270
15279
|
function getDefaultConfig() {
|
|
15271
15280
|
return {
|
|
15272
15281
|
defaultModel: "claude-haiku-4-5-20251001",
|
|
15282
|
+
defaultImageModel: DEFAULT_IMAGE_MODEL,
|
|
15273
15283
|
models: { ...MODEL_MAP },
|
|
15274
15284
|
browser: {
|
|
15275
15285
|
headless: true,
|
|
@@ -15296,6 +15306,7 @@ function loadConfig() {
|
|
|
15296
15306
|
}
|
|
15297
15307
|
const config = {
|
|
15298
15308
|
defaultModel: fileConfig.defaultModel ?? defaults2.defaultModel,
|
|
15309
|
+
defaultImageModel: fileConfig.defaultImageModel ?? defaults2.defaultImageModel,
|
|
15299
15310
|
models: fileConfig.models ? { ...defaults2.models, ...fileConfig.models } : { ...defaults2.models },
|
|
15300
15311
|
browser: fileConfig.browser ? { ...defaults2.browser, ...fileConfig.browser } : { ...defaults2.browser },
|
|
15301
15312
|
screenshots: fileConfig.screenshots ? { ...defaults2.screenshots, ...fileConfig.screenshots } : { ...defaults2.screenshots },
|
|
@@ -15311,6 +15322,10 @@ function loadConfig() {
|
|
|
15311
15322
|
if (envModel) {
|
|
15312
15323
|
config.defaultModel = envModel;
|
|
15313
15324
|
}
|
|
15325
|
+
const envImageModel = process.env["TESTERS_IMAGE_MODEL"];
|
|
15326
|
+
if (envImageModel) {
|
|
15327
|
+
config.defaultImageModel = envImageModel;
|
|
15328
|
+
}
|
|
15314
15329
|
const envScreenshotsDir = process.env["TESTERS_SCREENSHOTS_DIR"];
|
|
15315
15330
|
if (envScreenshotsDir) {
|
|
15316
15331
|
config.screenshots.dir = envScreenshotsDir;
|
|
@@ -15325,7 +15340,7 @@ function loadConfig() {
|
|
|
15325
15340
|
}
|
|
15326
15341
|
return config;
|
|
15327
15342
|
}
|
|
15328
|
-
var CONFIG_DIR3, CONFIG_PATH2;
|
|
15343
|
+
var CONFIG_DIR3, CONFIG_PATH2, DEFAULT_IMAGE_MODEL = "gpt-image-2";
|
|
15329
15344
|
var init_config2 = __esm(() => {
|
|
15330
15345
|
init_types2();
|
|
15331
15346
|
init_paths();
|
|
@@ -46910,7 +46925,7 @@ import { join as join14 } from "path";
|
|
|
46910
46925
|
// package.json
|
|
46911
46926
|
var package_default = {
|
|
46912
46927
|
name: "@hasna/testers",
|
|
46913
|
-
version: "0.0.
|
|
46928
|
+
version: "0.0.44",
|
|
46914
46929
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
46915
46930
|
type: "module",
|
|
46916
46931
|
main: "dist/index.js",
|
|
@@ -46956,7 +46971,7 @@ var package_default = {
|
|
|
46956
46971
|
"@hasna/cloud": "^0.1.24",
|
|
46957
46972
|
"@hasna/contacts": "^0.6.8",
|
|
46958
46973
|
"@hasna/projects": "^0.1.42",
|
|
46959
|
-
"@hasna/sandboxes": "^0.1.
|
|
46974
|
+
"@hasna/sandboxes": "^0.1.28",
|
|
46960
46975
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
46961
46976
|
ai: "^6.0.175",
|
|
46962
46977
|
chalk: "^5.4.1",
|
|
@@ -47009,9 +47024,14 @@ function nextShortId(projectId) {
|
|
|
47009
47024
|
if (projectId) {
|
|
47010
47025
|
const project = db2.query("SELECT scenario_prefix, scenario_counter FROM projects WHERE id = ?").get(projectId);
|
|
47011
47026
|
if (project) {
|
|
47012
|
-
|
|
47027
|
+
let next = (project.scenario_counter ?? 0) + 1;
|
|
47028
|
+
let shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
47029
|
+
while (db2.query("SELECT 1 FROM scenarios WHERE short_id = ?").get(shortId)) {
|
|
47030
|
+
next += 1;
|
|
47031
|
+
shortId = `${project.scenario_prefix || "TST"}-${next}`;
|
|
47032
|
+
}
|
|
47013
47033
|
db2.query("UPDATE projects SET scenario_counter = ? WHERE id = ?").run(next, projectId);
|
|
47014
|
-
return
|
|
47034
|
+
return shortId;
|
|
47015
47035
|
}
|
|
47016
47036
|
}
|
|
47017
47037
|
return shortUuid();
|
|
@@ -51026,14 +51046,19 @@ async function runApiChecksByFilter(filter) {
|
|
|
51026
51046
|
// src/db/projects.ts
|
|
51027
51047
|
init_types2();
|
|
51028
51048
|
init_database();
|
|
51049
|
+
function normalizeScenarioPrefix(prefix) {
|
|
51050
|
+
const normalized = (prefix ?? "TST").trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
|
51051
|
+
return normalized || "TST";
|
|
51052
|
+
}
|
|
51029
51053
|
function createProject(input) {
|
|
51030
51054
|
const db2 = getDatabase();
|
|
51031
51055
|
const id = uuid();
|
|
51032
51056
|
const timestamp = now();
|
|
51057
|
+
const scenarioPrefix = normalizeScenarioPrefix(input.scenarioPrefix);
|
|
51033
51058
|
db2.query(`
|
|
51034
|
-
INSERT INTO projects (id, name, path, description, base_url, port, settings, created_at, updated_at)
|
|
51035
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
51036
|
-
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", timestamp, timestamp);
|
|
51059
|
+
INSERT INTO projects (id, name, path, description, base_url, port, settings, scenario_prefix, scenario_counter, created_at, updated_at)
|
|
51060
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
51061
|
+
`).run(id, input.name, input.path ?? null, input.description ?? null, input.baseUrl ?? null, input.port ?? null, input.settings ? JSON.stringify(input.settings) : "{}", scenarioPrefix, timestamp, timestamp);
|
|
51037
51062
|
return getProject(id);
|
|
51038
51063
|
}
|
|
51039
51064
|
function updateProject(id, input) {
|
|
@@ -51359,6 +51384,7 @@ function buildSandboxPlan(workflow, execution, runOptions) {
|
|
|
51359
51384
|
remoteDir,
|
|
51360
51385
|
stateRemoteDir,
|
|
51361
51386
|
cleanup: execution.sandboxCleanup ?? "delete",
|
|
51387
|
+
syncStrategy: execution.sandboxSyncStrategy ?? "rsync",
|
|
51362
51388
|
timeoutMs: execution.timeoutMs,
|
|
51363
51389
|
env: execution.env,
|
|
51364
51390
|
command: buildSandboxCommand({
|
|
@@ -51421,7 +51447,8 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51421
51447
|
cleanup: plan.sandbox.cleanup,
|
|
51422
51448
|
upload: {
|
|
51423
51449
|
localDir: bundle.localDir,
|
|
51424
|
-
remoteDir: bundle.remoteDir
|
|
51450
|
+
remoteDir: bundle.remoteDir,
|
|
51451
|
+
syncStrategy: plan.sandbox.syncStrategy
|
|
51425
51452
|
}
|
|
51426
51453
|
});
|
|
51427
51454
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type AuthStrategy = "form-login" | "bearer" | "cookie" | "oauth" | "custo
|
|
|
8
8
|
export type WorkflowExecutionTarget = "local" | "sandbox";
|
|
9
9
|
export type LegacyWorkflowExecutionTarget = WorkflowExecutionTarget | "connector:e2b";
|
|
10
10
|
export type WorkflowSandboxCleanup = "delete" | "stop" | "keep";
|
|
11
|
+
export type WorkflowSandboxSyncStrategy = "archive" | "rsync";
|
|
11
12
|
export type AssertionType = "visible" | "not_visible" | "text_contains" | "text_equals" | "element_count" | "no_console_errors" | "url_contains" | "title_contains" | "no_a11y_violations" | "cookie_exists" | "cookie_value" | "cookie_not_exists" | "local_storage_exists" | "local_storage_value" | "local_storage_not_exists" | "session_storage_value" | "session_storage_not_exists";
|
|
12
13
|
export interface Assertion {
|
|
13
14
|
type: AssertionType;
|
|
@@ -24,6 +25,8 @@ export interface ProjectRow {
|
|
|
24
25
|
base_url: string | null;
|
|
25
26
|
port: number | null;
|
|
26
27
|
settings: string | null;
|
|
28
|
+
scenario_prefix: string | null;
|
|
29
|
+
scenario_counter: number | null;
|
|
27
30
|
created_at: string;
|
|
28
31
|
updated_at: string;
|
|
29
32
|
}
|
|
@@ -155,6 +158,8 @@ export interface Project {
|
|
|
155
158
|
baseUrl: string | null;
|
|
156
159
|
port: number | null;
|
|
157
160
|
settings: Record<string, unknown>;
|
|
161
|
+
scenarioPrefix: string;
|
|
162
|
+
scenarioCounter: number;
|
|
158
163
|
createdAt: string;
|
|
159
164
|
updatedAt: string;
|
|
160
165
|
}
|
|
@@ -165,6 +170,7 @@ export interface CreateProjectInput {
|
|
|
165
170
|
baseUrl?: string;
|
|
166
171
|
port?: number;
|
|
167
172
|
settings?: Record<string, unknown>;
|
|
173
|
+
scenarioPrefix?: string;
|
|
168
174
|
}
|
|
169
175
|
export interface UpdateProjectInput {
|
|
170
176
|
name?: string;
|
|
@@ -388,6 +394,7 @@ export interface WorkflowExecutionConfig {
|
|
|
388
394
|
sandboxImage?: string;
|
|
389
395
|
sandboxRemoteDir?: string;
|
|
390
396
|
sandboxCleanup?: WorkflowSandboxCleanup;
|
|
397
|
+
sandboxSyncStrategy?: WorkflowSandboxSyncStrategy;
|
|
391
398
|
setupCommand?: string;
|
|
392
399
|
packageSpec?: string;
|
|
393
400
|
timeoutMs?: number;
|
|
@@ -402,6 +409,7 @@ export interface WorkflowExecutionInput {
|
|
|
402
409
|
sandboxTemplate?: string;
|
|
403
410
|
sandboxRemoteDir?: string;
|
|
404
411
|
sandboxCleanup?: WorkflowSandboxCleanup;
|
|
412
|
+
sandboxSyncStrategy?: WorkflowSandboxSyncStrategy;
|
|
405
413
|
setupCommand?: string;
|
|
406
414
|
packageSpec?: string;
|
|
407
415
|
timeoutMs?: number;
|
|
@@ -509,6 +517,7 @@ export interface ScreenshotConfig {
|
|
|
509
517
|
}
|
|
510
518
|
export interface TestersConfig {
|
|
511
519
|
defaultModel: string;
|
|
520
|
+
defaultImageModel: string;
|
|
512
521
|
models: Record<ModelPreset, string>;
|
|
513
522
|
browser: BrowserConfig;
|
|
514
523
|
screenshots: ScreenshotConfig;
|