@hasna/testers 0.0.58 → 0.0.60
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 +49 -5
- package/dist/index.js +48 -3
- package/dist/lib/workflow-runner.d.ts.map +1 -1
- package/dist/mcp/index.js +49 -5
- package/dist/server/index.js +49 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -27427,6 +27427,7 @@ function buildSandboxCommand(input) {
|
|
|
27427
27427
|
];
|
|
27428
27428
|
return [
|
|
27429
27429
|
"set -euo pipefail",
|
|
27430
|
+
buildBunBootstrapCommand(),
|
|
27430
27431
|
`mkdir -p ${shellQuote(input.remoteDir)}`,
|
|
27431
27432
|
`mkdir -p ${shellQuote(input.stateRemoteDir)}`,
|
|
27432
27433
|
input.appRemoteDir ? `mkdir -p ${shellQuote(input.appRemoteDir)}` : undefined,
|
|
@@ -27437,6 +27438,17 @@ function buildSandboxCommand(input) {
|
|
|
27437
27438
|
].filter(Boolean).join(`
|
|
27438
27439
|
`);
|
|
27439
27440
|
}
|
|
27441
|
+
function buildBunBootstrapCommand() {
|
|
27442
|
+
return [
|
|
27443
|
+
'export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"',
|
|
27444
|
+
'export PATH="$BUN_INSTALL/bin:$PATH"',
|
|
27445
|
+
"if ! command -v bun >/dev/null 2>&1; then",
|
|
27446
|
+
" curl -fsSL https://bun.sh/install | bash",
|
|
27447
|
+
"fi",
|
|
27448
|
+
"command -v bun >/dev/null 2>&1"
|
|
27449
|
+
].join(`
|
|
27450
|
+
`);
|
|
27451
|
+
}
|
|
27440
27452
|
function buildAppStartCommand(input) {
|
|
27441
27453
|
if (!input.appStartCommand)
|
|
27442
27454
|
return;
|
|
@@ -27474,17 +27486,20 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
27474
27486
|
const sandboxes = await resolveSandboxesRuntime(dependencies);
|
|
27475
27487
|
const createBundle = dependencies.createDatabaseBundle ?? createWorkflowDatabaseBundle;
|
|
27476
27488
|
const bundle = createBundle(plan.workflow, plan);
|
|
27489
|
+
const sandboxTimeoutSeconds = plan.sandbox.timeoutMs === undefined ? undefined : Math.ceil(plan.sandbox.timeoutMs / 1000);
|
|
27490
|
+
let capturedStdout = "";
|
|
27491
|
+
let capturedStderr = "";
|
|
27477
27492
|
try {
|
|
27478
27493
|
const raw = await sandboxes.runCommandInSandbox({
|
|
27479
27494
|
command: plan.sandbox.command,
|
|
27480
27495
|
provider: plan.sandbox.provider,
|
|
27481
27496
|
name: plan.sandbox.name,
|
|
27482
27497
|
image: plan.sandbox.image,
|
|
27483
|
-
sandboxTimeout:
|
|
27484
|
-
commandTimeoutMs:
|
|
27485
|
-
projectId: plan.workflow.projectId ?? undefined,
|
|
27498
|
+
sandboxTimeout: sandboxTimeoutSeconds,
|
|
27499
|
+
commandTimeoutMs: sandboxTimeoutSeconds,
|
|
27486
27500
|
config: {
|
|
27487
27501
|
source: "testers",
|
|
27502
|
+
testersProjectId: plan.workflow.projectId ?? undefined,
|
|
27488
27503
|
workflowId: plan.workflow.id,
|
|
27489
27504
|
workflowName: plan.workflow.name
|
|
27490
27505
|
},
|
|
@@ -27494,6 +27509,12 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
27494
27509
|
localDir: bundle.localDir,
|
|
27495
27510
|
remoteDir: bundle.remoteDir,
|
|
27496
27511
|
syncStrategy: plan.sandbox.syncStrategy
|
|
27512
|
+
},
|
|
27513
|
+
onStdout: (data) => {
|
|
27514
|
+
capturedStdout = appendCapturedSandboxOutput(capturedStdout, data);
|
|
27515
|
+
},
|
|
27516
|
+
onStderr: (data) => {
|
|
27517
|
+
capturedStderr = appendCapturedSandboxOutput(capturedStderr, data);
|
|
27497
27518
|
}
|
|
27498
27519
|
});
|
|
27499
27520
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
|
@@ -27510,10 +27531,33 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
27510
27531
|
stderr,
|
|
27511
27532
|
cleanup: raw.cleanup
|
|
27512
27533
|
};
|
|
27534
|
+
} catch (error) {
|
|
27535
|
+
if (capturedStdout || capturedStderr) {
|
|
27536
|
+
throw buildSandboxStreamError(error, capturedStdout, capturedStderr);
|
|
27537
|
+
}
|
|
27538
|
+
throw error;
|
|
27513
27539
|
} finally {
|
|
27514
27540
|
bundle.cleanup?.();
|
|
27515
27541
|
}
|
|
27516
27542
|
}
|
|
27543
|
+
function appendCapturedSandboxOutput(current, data) {
|
|
27544
|
+
const next = current + data;
|
|
27545
|
+
if (next.length <= MAX_CAPTURED_SANDBOX_OUTPUT)
|
|
27546
|
+
return next;
|
|
27547
|
+
return next.slice(next.length - MAX_CAPTURED_SANDBOX_OUTPUT);
|
|
27548
|
+
}
|
|
27549
|
+
function buildSandboxStreamError(error, stdout, stderr) {
|
|
27550
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27551
|
+
const parts = [`Sandbox workflow execution failed: ${message}`];
|
|
27552
|
+
if (stdout.trim())
|
|
27553
|
+
parts.push(`stdout:
|
|
27554
|
+
${stdout.trimEnd()}`);
|
|
27555
|
+
if (stderr.trim())
|
|
27556
|
+
parts.push(`stderr:
|
|
27557
|
+
${stderr.trimEnd()}`);
|
|
27558
|
+
return new Error(parts.join(`
|
|
27559
|
+
`));
|
|
27560
|
+
}
|
|
27517
27561
|
function resolveSandboxEnv(env) {
|
|
27518
27562
|
if (!env || Object.keys(env).length === 0)
|
|
27519
27563
|
return;
|
|
@@ -27545,7 +27589,7 @@ async function resolveSandboxesRuntime(dependencies) {
|
|
|
27545
27589
|
function shellQuote(value) {
|
|
27546
27590
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
27547
27591
|
}
|
|
27548
|
-
var APP_SOURCE_EXCLUDES;
|
|
27592
|
+
var APP_SOURCE_EXCLUDES, MAX_CAPTURED_SANDBOX_OUTPUT = 120000;
|
|
27549
27593
|
var init_workflow_runner = __esm(() => {
|
|
27550
27594
|
init_database();
|
|
27551
27595
|
init_workflows();
|
|
@@ -95691,7 +95735,7 @@ import chalk6 from "chalk";
|
|
|
95691
95735
|
// package.json
|
|
95692
95736
|
var package_default = {
|
|
95693
95737
|
name: "@hasna/testers",
|
|
95694
|
-
version: "0.0.
|
|
95738
|
+
version: "0.0.60",
|
|
95695
95739
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
95696
95740
|
type: "module",
|
|
95697
95741
|
main: "dist/index.js",
|
package/dist/index.js
CHANGED
|
@@ -17384,6 +17384,7 @@ var APP_SOURCE_EXCLUDES = [
|
|
|
17384
17384
|
".venv",
|
|
17385
17385
|
"__pycache__"
|
|
17386
17386
|
];
|
|
17387
|
+
var MAX_CAPTURED_SANDBOX_OUTPUT = 120000;
|
|
17387
17388
|
function buildWorkflowRunPlan(workflow, options) {
|
|
17388
17389
|
const runOptions = {
|
|
17389
17390
|
url: options.url,
|
|
@@ -17542,6 +17543,7 @@ function buildSandboxCommand(input) {
|
|
|
17542
17543
|
];
|
|
17543
17544
|
return [
|
|
17544
17545
|
"set -euo pipefail",
|
|
17546
|
+
buildBunBootstrapCommand(),
|
|
17545
17547
|
`mkdir -p ${shellQuote(input.remoteDir)}`,
|
|
17546
17548
|
`mkdir -p ${shellQuote(input.stateRemoteDir)}`,
|
|
17547
17549
|
input.appRemoteDir ? `mkdir -p ${shellQuote(input.appRemoteDir)}` : undefined,
|
|
@@ -17552,6 +17554,17 @@ function buildSandboxCommand(input) {
|
|
|
17552
17554
|
].filter(Boolean).join(`
|
|
17553
17555
|
`);
|
|
17554
17556
|
}
|
|
17557
|
+
function buildBunBootstrapCommand() {
|
|
17558
|
+
return [
|
|
17559
|
+
'export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"',
|
|
17560
|
+
'export PATH="$BUN_INSTALL/bin:$PATH"',
|
|
17561
|
+
"if ! command -v bun >/dev/null 2>&1; then",
|
|
17562
|
+
" curl -fsSL https://bun.sh/install | bash",
|
|
17563
|
+
"fi",
|
|
17564
|
+
"command -v bun >/dev/null 2>&1"
|
|
17565
|
+
].join(`
|
|
17566
|
+
`);
|
|
17567
|
+
}
|
|
17555
17568
|
function buildAppStartCommand(input) {
|
|
17556
17569
|
if (!input.appStartCommand)
|
|
17557
17570
|
return;
|
|
@@ -17589,17 +17602,20 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17589
17602
|
const sandboxes = await resolveSandboxesRuntime(dependencies);
|
|
17590
17603
|
const createBundle = dependencies.createDatabaseBundle ?? createWorkflowDatabaseBundle;
|
|
17591
17604
|
const bundle = createBundle(plan.workflow, plan);
|
|
17605
|
+
const sandboxTimeoutSeconds = plan.sandbox.timeoutMs === undefined ? undefined : Math.ceil(plan.sandbox.timeoutMs / 1000);
|
|
17606
|
+
let capturedStdout = "";
|
|
17607
|
+
let capturedStderr = "";
|
|
17592
17608
|
try {
|
|
17593
17609
|
const raw = await sandboxes.runCommandInSandbox({
|
|
17594
17610
|
command: plan.sandbox.command,
|
|
17595
17611
|
provider: plan.sandbox.provider,
|
|
17596
17612
|
name: plan.sandbox.name,
|
|
17597
17613
|
image: plan.sandbox.image,
|
|
17598
|
-
sandboxTimeout:
|
|
17599
|
-
commandTimeoutMs:
|
|
17600
|
-
projectId: plan.workflow.projectId ?? undefined,
|
|
17614
|
+
sandboxTimeout: sandboxTimeoutSeconds,
|
|
17615
|
+
commandTimeoutMs: sandboxTimeoutSeconds,
|
|
17601
17616
|
config: {
|
|
17602
17617
|
source: "testers",
|
|
17618
|
+
testersProjectId: plan.workflow.projectId ?? undefined,
|
|
17603
17619
|
workflowId: plan.workflow.id,
|
|
17604
17620
|
workflowName: plan.workflow.name
|
|
17605
17621
|
},
|
|
@@ -17609,6 +17625,12 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17609
17625
|
localDir: bundle.localDir,
|
|
17610
17626
|
remoteDir: bundle.remoteDir,
|
|
17611
17627
|
syncStrategy: plan.sandbox.syncStrategy
|
|
17628
|
+
},
|
|
17629
|
+
onStdout: (data) => {
|
|
17630
|
+
capturedStdout = appendCapturedSandboxOutput(capturedStdout, data);
|
|
17631
|
+
},
|
|
17632
|
+
onStderr: (data) => {
|
|
17633
|
+
capturedStderr = appendCapturedSandboxOutput(capturedStderr, data);
|
|
17612
17634
|
}
|
|
17613
17635
|
});
|
|
17614
17636
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
|
@@ -17625,10 +17647,33 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
17625
17647
|
stderr,
|
|
17626
17648
|
cleanup: raw.cleanup
|
|
17627
17649
|
};
|
|
17650
|
+
} catch (error) {
|
|
17651
|
+
if (capturedStdout || capturedStderr) {
|
|
17652
|
+
throw buildSandboxStreamError(error, capturedStdout, capturedStderr);
|
|
17653
|
+
}
|
|
17654
|
+
throw error;
|
|
17628
17655
|
} finally {
|
|
17629
17656
|
bundle.cleanup?.();
|
|
17630
17657
|
}
|
|
17631
17658
|
}
|
|
17659
|
+
function appendCapturedSandboxOutput(current, data) {
|
|
17660
|
+
const next = current + data;
|
|
17661
|
+
if (next.length <= MAX_CAPTURED_SANDBOX_OUTPUT)
|
|
17662
|
+
return next;
|
|
17663
|
+
return next.slice(next.length - MAX_CAPTURED_SANDBOX_OUTPUT);
|
|
17664
|
+
}
|
|
17665
|
+
function buildSandboxStreamError(error, stdout, stderr) {
|
|
17666
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17667
|
+
const parts = [`Sandbox workflow execution failed: ${message}`];
|
|
17668
|
+
if (stdout.trim())
|
|
17669
|
+
parts.push(`stdout:
|
|
17670
|
+
${stdout.trimEnd()}`);
|
|
17671
|
+
if (stderr.trim())
|
|
17672
|
+
parts.push(`stderr:
|
|
17673
|
+
${stderr.trimEnd()}`);
|
|
17674
|
+
return new Error(parts.join(`
|
|
17675
|
+
`));
|
|
17676
|
+
}
|
|
17632
17677
|
function resolveSandboxEnv(env2) {
|
|
17633
17678
|
if (!env2 || Object.keys(env2).length === 0)
|
|
17634
17679
|
return;
|
|
@@ -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;AAE3D,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,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,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;
|
|
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.60",
|
|
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",
|
|
@@ -23818,6 +23818,7 @@ function buildSandboxCommand(input) {
|
|
|
23818
23818
|
];
|
|
23819
23819
|
return [
|
|
23820
23820
|
"set -euo pipefail",
|
|
23821
|
+
buildBunBootstrapCommand(),
|
|
23821
23822
|
`mkdir -p ${shellQuote(input.remoteDir)}`,
|
|
23822
23823
|
`mkdir -p ${shellQuote(input.stateRemoteDir)}`,
|
|
23823
23824
|
input.appRemoteDir ? `mkdir -p ${shellQuote(input.appRemoteDir)}` : undefined,
|
|
@@ -23828,6 +23829,17 @@ function buildSandboxCommand(input) {
|
|
|
23828
23829
|
].filter(Boolean).join(`
|
|
23829
23830
|
`);
|
|
23830
23831
|
}
|
|
23832
|
+
function buildBunBootstrapCommand() {
|
|
23833
|
+
return [
|
|
23834
|
+
'export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"',
|
|
23835
|
+
'export PATH="$BUN_INSTALL/bin:$PATH"',
|
|
23836
|
+
"if ! command -v bun >/dev/null 2>&1; then",
|
|
23837
|
+
" curl -fsSL https://bun.sh/install | bash",
|
|
23838
|
+
"fi",
|
|
23839
|
+
"command -v bun >/dev/null 2>&1"
|
|
23840
|
+
].join(`
|
|
23841
|
+
`);
|
|
23842
|
+
}
|
|
23831
23843
|
function buildAppStartCommand(input) {
|
|
23832
23844
|
if (!input.appStartCommand)
|
|
23833
23845
|
return;
|
|
@@ -23865,17 +23877,20 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23865
23877
|
const sandboxes = await resolveSandboxesRuntime(dependencies);
|
|
23866
23878
|
const createBundle = dependencies.createDatabaseBundle ?? createWorkflowDatabaseBundle;
|
|
23867
23879
|
const bundle = createBundle(plan.workflow, plan);
|
|
23880
|
+
const sandboxTimeoutSeconds = plan.sandbox.timeoutMs === undefined ? undefined : Math.ceil(plan.sandbox.timeoutMs / 1000);
|
|
23881
|
+
let capturedStdout = "";
|
|
23882
|
+
let capturedStderr = "";
|
|
23868
23883
|
try {
|
|
23869
23884
|
const raw = await sandboxes.runCommandInSandbox({
|
|
23870
23885
|
command: plan.sandbox.command,
|
|
23871
23886
|
provider: plan.sandbox.provider,
|
|
23872
23887
|
name: plan.sandbox.name,
|
|
23873
23888
|
image: plan.sandbox.image,
|
|
23874
|
-
sandboxTimeout:
|
|
23875
|
-
commandTimeoutMs:
|
|
23876
|
-
projectId: plan.workflow.projectId ?? undefined,
|
|
23889
|
+
sandboxTimeout: sandboxTimeoutSeconds,
|
|
23890
|
+
commandTimeoutMs: sandboxTimeoutSeconds,
|
|
23877
23891
|
config: {
|
|
23878
23892
|
source: "testers",
|
|
23893
|
+
testersProjectId: plan.workflow.projectId ?? undefined,
|
|
23879
23894
|
workflowId: plan.workflow.id,
|
|
23880
23895
|
workflowName: plan.workflow.name
|
|
23881
23896
|
},
|
|
@@ -23885,6 +23900,12 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23885
23900
|
localDir: bundle.localDir,
|
|
23886
23901
|
remoteDir: bundle.remoteDir,
|
|
23887
23902
|
syncStrategy: plan.sandbox.syncStrategy
|
|
23903
|
+
},
|
|
23904
|
+
onStdout: (data) => {
|
|
23905
|
+
capturedStdout = appendCapturedSandboxOutput(capturedStdout, data);
|
|
23906
|
+
},
|
|
23907
|
+
onStderr: (data) => {
|
|
23908
|
+
capturedStderr = appendCapturedSandboxOutput(capturedStderr, data);
|
|
23888
23909
|
}
|
|
23889
23910
|
});
|
|
23890
23911
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
|
@@ -23901,10 +23922,33 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
23901
23922
|
stderr,
|
|
23902
23923
|
cleanup: raw.cleanup
|
|
23903
23924
|
};
|
|
23925
|
+
} catch (error) {
|
|
23926
|
+
if (capturedStdout || capturedStderr) {
|
|
23927
|
+
throw buildSandboxStreamError(error, capturedStdout, capturedStderr);
|
|
23928
|
+
}
|
|
23929
|
+
throw error;
|
|
23904
23930
|
} finally {
|
|
23905
23931
|
bundle.cleanup?.();
|
|
23906
23932
|
}
|
|
23907
23933
|
}
|
|
23934
|
+
function appendCapturedSandboxOutput(current, data) {
|
|
23935
|
+
const next = current + data;
|
|
23936
|
+
if (next.length <= MAX_CAPTURED_SANDBOX_OUTPUT)
|
|
23937
|
+
return next;
|
|
23938
|
+
return next.slice(next.length - MAX_CAPTURED_SANDBOX_OUTPUT);
|
|
23939
|
+
}
|
|
23940
|
+
function buildSandboxStreamError(error, stdout, stderr) {
|
|
23941
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23942
|
+
const parts = [`Sandbox workflow execution failed: ${message}`];
|
|
23943
|
+
if (stdout.trim())
|
|
23944
|
+
parts.push(`stdout:
|
|
23945
|
+
${stdout.trimEnd()}`);
|
|
23946
|
+
if (stderr.trim())
|
|
23947
|
+
parts.push(`stderr:
|
|
23948
|
+
${stderr.trimEnd()}`);
|
|
23949
|
+
return new Error(parts.join(`
|
|
23950
|
+
`));
|
|
23951
|
+
}
|
|
23908
23952
|
function resolveSandboxEnv(env2) {
|
|
23909
23953
|
if (!env2 || Object.keys(env2).length === 0)
|
|
23910
23954
|
return;
|
|
@@ -23936,7 +23980,7 @@ async function resolveSandboxesRuntime(dependencies) {
|
|
|
23936
23980
|
function shellQuote(value) {
|
|
23937
23981
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
23938
23982
|
}
|
|
23939
|
-
var APP_SOURCE_EXCLUDES;
|
|
23983
|
+
var APP_SOURCE_EXCLUDES, MAX_CAPTURED_SANDBOX_OUTPUT = 120000;
|
|
23940
23984
|
var init_workflow_runner = __esm(() => {
|
|
23941
23985
|
init_database();
|
|
23942
23986
|
init_workflows();
|
package/dist/server/index.js
CHANGED
|
@@ -47090,7 +47090,7 @@ import { join as join14 } from "path";
|
|
|
47090
47090
|
// package.json
|
|
47091
47091
|
var package_default = {
|
|
47092
47092
|
name: "@hasna/testers",
|
|
47093
|
-
version: "0.0.
|
|
47093
|
+
version: "0.0.60",
|
|
47094
47094
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
47095
47095
|
type: "module",
|
|
47096
47096
|
main: "dist/index.js",
|
|
@@ -51500,6 +51500,7 @@ var APP_SOURCE_EXCLUDES = [
|
|
|
51500
51500
|
".venv",
|
|
51501
51501
|
"__pycache__"
|
|
51502
51502
|
];
|
|
51503
|
+
var MAX_CAPTURED_SANDBOX_OUTPUT = 120000;
|
|
51503
51504
|
function buildWorkflowRunPlan(workflow, options) {
|
|
51504
51505
|
const runOptions = {
|
|
51505
51506
|
url: options.url,
|
|
@@ -51658,6 +51659,7 @@ function buildSandboxCommand(input) {
|
|
|
51658
51659
|
];
|
|
51659
51660
|
return [
|
|
51660
51661
|
"set -euo pipefail",
|
|
51662
|
+
buildBunBootstrapCommand(),
|
|
51661
51663
|
`mkdir -p ${shellQuote(input.remoteDir)}`,
|
|
51662
51664
|
`mkdir -p ${shellQuote(input.stateRemoteDir)}`,
|
|
51663
51665
|
input.appRemoteDir ? `mkdir -p ${shellQuote(input.appRemoteDir)}` : undefined,
|
|
@@ -51668,6 +51670,17 @@ function buildSandboxCommand(input) {
|
|
|
51668
51670
|
].filter(Boolean).join(`
|
|
51669
51671
|
`);
|
|
51670
51672
|
}
|
|
51673
|
+
function buildBunBootstrapCommand() {
|
|
51674
|
+
return [
|
|
51675
|
+
'export BUN_INSTALL="${BUN_INSTALL:-$HOME/.bun}"',
|
|
51676
|
+
'export PATH="$BUN_INSTALL/bin:$PATH"',
|
|
51677
|
+
"if ! command -v bun >/dev/null 2>&1; then",
|
|
51678
|
+
" curl -fsSL https://bun.sh/install | bash",
|
|
51679
|
+
"fi",
|
|
51680
|
+
"command -v bun >/dev/null 2>&1"
|
|
51681
|
+
].join(`
|
|
51682
|
+
`);
|
|
51683
|
+
}
|
|
51671
51684
|
function buildAppStartCommand(input) {
|
|
51672
51685
|
if (!input.appStartCommand)
|
|
51673
51686
|
return;
|
|
@@ -51705,17 +51718,20 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51705
51718
|
const sandboxes = await resolveSandboxesRuntime(dependencies);
|
|
51706
51719
|
const createBundle = dependencies.createDatabaseBundle ?? createWorkflowDatabaseBundle;
|
|
51707
51720
|
const bundle = createBundle(plan.workflow, plan);
|
|
51721
|
+
const sandboxTimeoutSeconds = plan.sandbox.timeoutMs === undefined ? undefined : Math.ceil(plan.sandbox.timeoutMs / 1000);
|
|
51722
|
+
let capturedStdout = "";
|
|
51723
|
+
let capturedStderr = "";
|
|
51708
51724
|
try {
|
|
51709
51725
|
const raw = await sandboxes.runCommandInSandbox({
|
|
51710
51726
|
command: plan.sandbox.command,
|
|
51711
51727
|
provider: plan.sandbox.provider,
|
|
51712
51728
|
name: plan.sandbox.name,
|
|
51713
51729
|
image: plan.sandbox.image,
|
|
51714
|
-
sandboxTimeout:
|
|
51715
|
-
commandTimeoutMs:
|
|
51716
|
-
projectId: plan.workflow.projectId ?? undefined,
|
|
51730
|
+
sandboxTimeout: sandboxTimeoutSeconds,
|
|
51731
|
+
commandTimeoutMs: sandboxTimeoutSeconds,
|
|
51717
51732
|
config: {
|
|
51718
51733
|
source: "testers",
|
|
51734
|
+
testersProjectId: plan.workflow.projectId ?? undefined,
|
|
51719
51735
|
workflowId: plan.workflow.id,
|
|
51720
51736
|
workflowName: plan.workflow.name
|
|
51721
51737
|
},
|
|
@@ -51725,6 +51741,12 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51725
51741
|
localDir: bundle.localDir,
|
|
51726
51742
|
remoteDir: bundle.remoteDir,
|
|
51727
51743
|
syncStrategy: plan.sandbox.syncStrategy
|
|
51744
|
+
},
|
|
51745
|
+
onStdout: (data) => {
|
|
51746
|
+
capturedStdout = appendCapturedSandboxOutput(capturedStdout, data);
|
|
51747
|
+
},
|
|
51748
|
+
onStderr: (data) => {
|
|
51749
|
+
capturedStderr = appendCapturedSandboxOutput(capturedStderr, data);
|
|
51728
51750
|
}
|
|
51729
51751
|
});
|
|
51730
51752
|
const exitCode = raw.result.exit_code ?? raw.result.exitCode ?? 0;
|
|
@@ -51741,10 +51763,33 @@ async function runViaSandbox(plan, dependencies) {
|
|
|
51741
51763
|
stderr,
|
|
51742
51764
|
cleanup: raw.cleanup
|
|
51743
51765
|
};
|
|
51766
|
+
} catch (error) {
|
|
51767
|
+
if (capturedStdout || capturedStderr) {
|
|
51768
|
+
throw buildSandboxStreamError(error, capturedStdout, capturedStderr);
|
|
51769
|
+
}
|
|
51770
|
+
throw error;
|
|
51744
51771
|
} finally {
|
|
51745
51772
|
bundle.cleanup?.();
|
|
51746
51773
|
}
|
|
51747
51774
|
}
|
|
51775
|
+
function appendCapturedSandboxOutput(current, data) {
|
|
51776
|
+
const next = current + data;
|
|
51777
|
+
if (next.length <= MAX_CAPTURED_SANDBOX_OUTPUT)
|
|
51778
|
+
return next;
|
|
51779
|
+
return next.slice(next.length - MAX_CAPTURED_SANDBOX_OUTPUT);
|
|
51780
|
+
}
|
|
51781
|
+
function buildSandboxStreamError(error, stdout, stderr) {
|
|
51782
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51783
|
+
const parts = [`Sandbox workflow execution failed: ${message}`];
|
|
51784
|
+
if (stdout.trim())
|
|
51785
|
+
parts.push(`stdout:
|
|
51786
|
+
${stdout.trimEnd()}`);
|
|
51787
|
+
if (stderr.trim())
|
|
51788
|
+
parts.push(`stderr:
|
|
51789
|
+
${stderr.trimEnd()}`);
|
|
51790
|
+
return new Error(parts.join(`
|
|
51791
|
+
`));
|
|
51792
|
+
}
|
|
51748
51793
|
function resolveSandboxEnv(env) {
|
|
51749
51794
|
if (!env || Object.keys(env).length === 0)
|
|
51750
51795
|
return;
|