@hasna/testers 0.0.59 → 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 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,14 +27486,17 @@ 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: plan.sandbox.timeoutMs,
27484
- commandTimeoutMs: plan.sandbox.timeoutMs,
27498
+ sandboxTimeout: sandboxTimeoutSeconds,
27499
+ commandTimeoutMs: sandboxTimeoutSeconds,
27485
27500
  config: {
27486
27501
  source: "testers",
27487
27502
  testersProjectId: plan.workflow.projectId ?? undefined,
@@ -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.59",
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,14 +17602,17 @@ 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: plan.sandbox.timeoutMs,
17599
- commandTimeoutMs: plan.sandbox.timeoutMs,
17614
+ sandboxTimeout: sandboxTimeoutSeconds,
17615
+ commandTimeoutMs: sandboxTimeoutSeconds,
17600
17616
  config: {
17601
17617
  source: "testers",
17602
17618
  testersProjectId: plan.workflow.projectId ?? undefined,
@@ -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":"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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.59",
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,14 +23877,17 @@ 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: plan.sandbox.timeoutMs,
23875
- commandTimeoutMs: plan.sandbox.timeoutMs,
23889
+ sandboxTimeout: sandboxTimeoutSeconds,
23890
+ commandTimeoutMs: sandboxTimeoutSeconds,
23876
23891
  config: {
23877
23892
  source: "testers",
23878
23893
  testersProjectId: plan.workflow.projectId ?? undefined,
@@ -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();
@@ -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.59",
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,14 +51718,17 @@ 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: plan.sandbox.timeoutMs,
51715
- commandTimeoutMs: plan.sandbox.timeoutMs,
51730
+ sandboxTimeout: sandboxTimeoutSeconds,
51731
+ commandTimeoutMs: sandboxTimeoutSeconds,
51716
51732
  config: {
51717
51733
  source: "testers",
51718
51734
  testersProjectId: plan.workflow.projectId ?? undefined,
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/testers",
3
- "version": "0.0.59",
3
+ "version": "0.0.60",
4
4
  "description": "AI-powered QA testing CLI — spawns cheap AI agents to test web apps with headless browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",