@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.
@@ -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;AAG3B,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAqBhE;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
+ {"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;AAsB3B,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"}
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
- const next = project.scenario_counter + 1;
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 `${project.scenario_prefix}-${next}`;
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;
@@ -1,4 +1,5 @@
1
1
  import type { TestersConfig } from "../types/index.js";
2
+ export declare const DEFAULT_IMAGE_MODEL = "gpt-image-2";
2
3
  /**
3
4
  * Returns the hardcoded default configuration.
4
5
  */
@@ -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;AAOpE;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAiBhD;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,aAAa,CAuD1C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrD"}
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":"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"}
1
+ {"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/lib/workflow-runner.ts"],"names":[],"mappings":"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"}
package/dist/mcp/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@hasna/testers",
55
- version: "0.0.42",
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.27",
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
- const next = project.scenario_counter + 1;
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 `${project.scenario_prefix}-${next}`;
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;
@@ -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.42",
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.27",
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
- const next = project.scenario_counter + 1;
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 `${project.scenario_prefix}-${next}`;
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;
@@ -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;