@qawolf/cli 1.7.2 → 1.7.3

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.
Files changed (2) hide show
  1. package/dist/cli.js +366 -257
  2. package/package.json +1 -2
package/dist/cli.js CHANGED
@@ -8514,7 +8514,7 @@ var require_BufferList = __commonJS((exports, module) => {
8514
8514
  this.head = this.tail = null;
8515
8515
  this.length = 0;
8516
8516
  };
8517
- BufferList.prototype.join = function join34(s3) {
8517
+ BufferList.prototype.join = function join35(s3) {
8518
8518
  if (this.length === 0)
8519
8519
  return "";
8520
8520
  var p3 = this.head;
@@ -23070,7 +23070,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
23070
23070
  this.head = this.tail = null;
23071
23071
  this.length = 0;
23072
23072
  };
23073
- BufferList.prototype.join = function join34(s3) {
23073
+ BufferList.prototype.join = function join35(s3) {
23074
23074
  if (this.length === 0)
23075
23075
  return "";
23076
23076
  var p3 = this.head;
@@ -155283,7 +155283,8 @@ var doctorMessages = {
155283
155283
  playwright: {
155284
155284
  notFound: (path) => toolNotInstalled("Playwright", path),
155285
155285
  launchFailed: "Could not launch Playwright. Try reinstalling the qawolf CLI.",
155286
- versionUnparseable: "Could not parse playwright version output"
155286
+ versionUnparseable: "Could not parse playwright version output",
155287
+ versionMismatch: (installed, pinned) => `Playwright ${installed} is installed but the flow runtime requires ${pinned}; local runs will fail to launch browsers. Run \`qawolf install\` to repair the runtime dependencies.`
155287
155288
  },
155288
155289
  appium: {
155289
155290
  notFound: (path) => toolNotInstalled("Appium", path),
@@ -155395,11 +155396,20 @@ var initMessages = {
155395
155396
  outro: "Run `qawolf auth login`, then `qawolf flows pull`, then `qawolf flows run`.",
155396
155397
  createPackageJsonPrompt: "Create package.json (required to run flows)?",
155397
155398
  skippedCreatingPackageJson: "Skipped package.json",
155398
- skippedAddingTestE2e: "Skipped adding test:e2e to package.json",
155399
+ skippedUpdatingPackageJson: "Skipped updating package.json",
155399
155400
  createdPackageJson: "Created package.json",
155400
- packageJsonInvalidJson: "package.json is not valid JSON — skipped adding `test:e2e`",
155401
- packageJsonHasTestE2e: "package.json already has `test:e2e` — skipped",
155402
- addTestE2ePrompt: "Add `test:e2e: qawolf flows run` to package.json?",
155401
+ packageJsonInvalidJson: "`package.json` contains invalid JSON. Correct the JSON syntax, then run `qawolf init` again.",
155402
+ packageJsonNotAnObject: "`package.json` must contain a JSON object. Update it, then run `qawolf init` again.",
155403
+ packageJsonMalformedSection: (field) => `The \`${field}\` field in \`package.json\` must be a JSON object. Update it, then run \`qawolf init\` again.`,
155404
+ packageJsonHasTestE2e: "package.json already has `test:e2e` — leaving it as is",
155405
+ packageJsonUpToDate: "package.json already configured — nothing to update",
155406
+ typeChanged: (from) => `Changed "type" from "${from}" to "module" — .js files in this package now load as ES modules.`,
155407
+ pkgChanges: {
155408
+ script: "add `test:e2e` script",
155409
+ type: 'set "type": "module"',
155410
+ flowsDep: "add @qawolf/flows dependency"
155411
+ },
155412
+ updatePackageJsonPrompt: (changes) => `Update package.json (${changes.join(", ")})?`,
155403
155413
  updatedPackageJson: "Updated package.json",
155404
155414
  overwritePrompt: (relPath) => `Overwrite ${relPath}?`,
155405
155415
  skippedFile: (relPath) => `Skipped ${relPath}`,
@@ -174616,7 +174626,7 @@ function startUpdateCheck(deps) {
174616
174626
  // package.json
174617
174627
  var package_default = {
174618
174628
  name: "@qawolf/cli",
174619
- version: "1.7.2",
174629
+ version: "1.7.3",
174620
174630
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
174621
174631
  keywords: [
174622
174632
  "automation",
@@ -174676,7 +174686,6 @@ var package_default = {
174676
174686
  "@clack/prompts": "1.5.1",
174677
174687
  "@napi-rs/keyring": "1.3.0",
174678
174688
  "@oxc-node/core": "0.1.0",
174679
- "@playwright/test": "1.62.0",
174680
174689
  "@qawolf/api-contracts": "0.22.0",
174681
174690
  "@qawolf/emails": "1.1.1",
174682
174691
  "@qawolf/flow-targets": "1.0.0",
@@ -174770,7 +174779,7 @@ function withContext(signals, fn) {
174770
174779
  try {
174771
174780
  const result = await fn(ctx);
174772
174781
  if (result !== undefined) {
174773
- ctx.ui.error(result.error);
174782
+ ctx.ui.error(result.error, result.errorBody);
174774
174783
  process.exitCode = result.exitCode ?? 1;
174775
174784
  }
174776
174785
  } catch (err) {
@@ -174803,7 +174812,7 @@ function withAuthContext(signals, fn, deps = {}) {
174803
174812
  apiKeySource: resolved.source
174804
174813
  });
174805
174814
  if (result !== undefined) {
174806
- ctx.ui.error(result.error);
174815
+ ctx.ui.error(result.error, result.errorBody);
174807
174816
  process.exitCode = result.exitCode ?? 1;
174808
174817
  }
174809
174818
  } catch (err) {
@@ -175047,9 +175056,6 @@ function nodeModulesBinCandidates(envDir, name, platform) {
175047
175056
  function appiumCliCandidates(envDir, platform) {
175048
175057
  return nodeModulesBinCandidates(envDir, "appium", platform);
175049
175058
  }
175050
- function playwrightCliCandidates(envDir, platform) {
175051
- return nodeModulesBinCandidates(envDir, "playwright", platform);
175052
- }
175053
175059
 
175054
175060
  // src/domains/doctor/checks/androidAppium.ts
175055
175061
  function firstLine(result) {
@@ -175412,19 +175418,48 @@ async function checkNpmRegistry(deps) {
175412
175418
  return { name: "npm-registry", status: "pass" };
175413
175419
  }
175414
175420
 
175421
+ // src/core/playwrightCli.ts
175422
+ import { join as join13 } from "node:path";
175423
+ function playwrightCliJsPath(envDir) {
175424
+ return join13(envDir, "node_modules", "playwright", "cli.js");
175425
+ }
175426
+ function playwrightCliInvocation(args) {
175427
+ return {
175428
+ cmd: args.execPath,
175429
+ args: [playwrightCliJsPath(args.envDir), ...args.cliArgs],
175430
+ env: { BUN_BE_BUN: "1" }
175431
+ };
175432
+ }
175433
+
175434
+ // src/generated/dependencyVersions.ts
175435
+ var flowsVersion = "0.1.4";
175436
+ var playwrightVersion = "1.62.0";
175437
+ var emailsVersion = "1.1.1";
175438
+ var testkitVersion = "1.1.1";
175439
+ var appiumVersion = "2.11.3";
175440
+ var appiumXcuitestDriverVersion = "9.10.5";
175441
+ var appiumUiautomator2DriverVersion = "3.7.0";
175442
+ var expectWebdriverioVersion = "5.6.5";
175443
+
175415
175444
  // src/domains/doctor/checks/playwright.ts
175416
175445
  async function checkPlaywright(deps) {
175417
- const candidates = deps.envDir ? playwrightCliCandidates(deps.envDir, deps.platform) : [];
175418
- const cliPath = candidates.find(deps.checkExists);
175419
- if (cliPath === undefined) {
175446
+ const { envDir } = deps;
175447
+ const cliJsPath = envDir === undefined ? undefined : playwrightCliJsPath(envDir);
175448
+ if (envDir === undefined || cliJsPath === undefined || !deps.checkExists(cliJsPath)) {
175420
175449
  return {
175421
175450
  name: "playwright",
175422
175451
  status: "fail",
175423
- detail: doctorMessages.playwright.notFound(candidates[0])
175452
+ detail: doctorMessages.playwright.notFound(cliJsPath)
175424
175453
  };
175425
175454
  }
175426
- const result = await deps.spawn(cliPath, ["--version"], {
175427
- platform: deps.platform
175455
+ const invocation = playwrightCliInvocation({
175456
+ envDir,
175457
+ execPath: deps.execPath,
175458
+ cliArgs: ["--version"]
175459
+ });
175460
+ const result = await deps.spawn(invocation.cmd, invocation.args, {
175461
+ platform: deps.platform,
175462
+ env: invocation.env
175428
175463
  });
175429
175464
  if (result.exitCode < 0) {
175430
175465
  return {
@@ -175446,6 +175481,14 @@ async function checkPlaywright(deps) {
175446
175481
  detail: doctorMessages.playwright.versionUnparseable
175447
175482
  };
175448
175483
  }
175484
+ if (version2 !== playwrightVersion) {
175485
+ return {
175486
+ name: "playwright",
175487
+ status: "fail",
175488
+ version: version2,
175489
+ detail: doctorMessages.playwright.versionMismatch(version2, playwrightVersion)
175490
+ };
175491
+ }
175449
175492
  return { name: "playwright", status: "pass", version: version2 };
175450
175493
  }
175451
175494
 
@@ -175466,6 +175509,7 @@ async function runChecks(deps) {
175466
175509
  }),
175467
175510
  checkPlaywright({
175468
175511
  spawn: deps.spawn,
175512
+ execPath: deps.execPath,
175469
175513
  envDir: deps.envDir,
175470
175514
  platform: deps.platform,
175471
175515
  checkExists: deps.checkExists
@@ -175525,11 +175569,11 @@ function renderResults(ui, results) {
175525
175569
  }
175526
175570
 
175527
175571
  // src/domains/flows/ensureDeps.ts
175528
- import { dirname as dirname4, join as join13 } from "node:path";
175572
+ import { dirname as dirname4, join as join14 } from "node:path";
175529
175573
  function findEnvDir(flowPath, fs2) {
175530
175574
  let dir = dirname4(flowPath);
175531
175575
  while (true) {
175532
- if (fs2.existsSync(join13(dir, "package.json")))
175576
+ if (fs2.existsSync(join14(dir, "package.json")))
175533
175577
  return dir;
175534
175578
  const parent = dirname4(dir);
175535
175579
  if (parent === dir)
@@ -175555,7 +175599,7 @@ function resolveProjectDirSafe(files, fs2) {
175555
175599
  }
175556
175600
 
175557
175601
  // src/domains/flows/expand.ts
175558
- import { join as join14, resolve as resolve3 } from "node:path";
175602
+ import { join as join15, resolve as resolve3 } from "node:path";
175559
175603
 
175560
175604
  // node_modules/tinyglobby/dist/index.mjs
175561
175605
  import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
@@ -176446,11 +176490,11 @@ function makePeekFlowMeta(fs2) {
176446
176490
  };
176447
176491
  }
176448
176492
  async function resolveGlobRoots(cwd, fs2) {
176449
- const qawolfPath = join14(cwd, ".qawolf");
176493
+ const qawolfPath = join15(cwd, ".qawolf");
176450
176494
  let envDirs = [];
176451
176495
  try {
176452
176496
  const entries = await fs2.readdirWithTypes(qawolfPath);
176453
- envDirs = entries.filter((e) => e.isDirectory()).map((e) => join14(qawolfPath, e.name));
176497
+ envDirs = entries.filter((e) => e.isDirectory()).map((e) => join15(qawolfPath, e.name));
176454
176498
  } catch {}
176455
176499
  return [cwd, ...envDirs];
176456
176500
  }
@@ -176475,21 +176519,11 @@ async function expandPatterns(patterns, cwd, logger, fs2) {
176475
176519
  }
176476
176520
 
176477
176521
  // src/domains/runtimeEnv/clearRuntimeEnv.ts
176478
- import { join as join16 } from "node:path";
176522
+ import { join as join17 } from "node:path";
176479
176523
 
176480
176524
  // src/domains/runtimeEnv/managedEnvDir.ts
176481
176525
  import { createHash } from "node:crypto";
176482
- import { join as join15, resolve as resolve4 } from "node:path";
176483
-
176484
- // src/generated/dependencyVersions.ts
176485
- var flowsVersion = "0.1.4";
176486
- var playwrightVersion = "1.62.0";
176487
- var emailsVersion = "1.1.1";
176488
- var testkitVersion = "1.1.1";
176489
- var appiumVersion = "2.11.3";
176490
- var appiumXcuitestDriverVersion = "9.10.5";
176491
- var appiumUiautomator2DriverVersion = "3.7.0";
176492
- var expectWebdriverioVersion = "5.6.5";
176526
+ import { join as join16, resolve as resolve4 } from "node:path";
176493
176527
 
176494
176528
  // src/domains/runtimeEnv/pinnedPackages.ts
176495
176529
  var pinnedPackages = [
@@ -176522,19 +176556,19 @@ function managedEnvBaseDir() {
176522
176556
  const override = process.env["QAWOLF_RUNTIME_DIR"]?.trim();
176523
176557
  if (override)
176524
176558
  return resolve4(override);
176525
- return join15(getDataDir(), "runtime");
176559
+ return join16(getDataDir(), "runtime");
176526
176560
  }
176527
176561
  function runStagingRoot() {
176528
176562
  return `${managedEnvBaseDir()}-runs`;
176529
176563
  }
176530
176564
  function managedEnvDir() {
176531
- return join15(managedEnvBaseDir(), managedEnvHash());
176565
+ return join16(managedEnvBaseDir(), managedEnvHash());
176532
176566
  }
176533
176567
  async function scaffoldManagedEnv(dir, fs2) {
176534
176568
  await fs2.mkdir(dir, { recursive: true });
176535
176569
  const dependencies = Object.fromEntries(pinnedPackages.map(({ name, version: version2 }) => [name, version2]));
176536
- await fs2.writeFile(join15(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
176537
- await fs2.writeFile(join15(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
176570
+ await fs2.writeFile(join16(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
176571
+ await fs2.writeFile(join16(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
176538
176572
  `);
176539
176573
  }
176540
176574
 
@@ -176556,14 +176590,14 @@ async function isManagedRuntimeBase(dir, fs2) {
176556
176590
  for (const entry of entries) {
176557
176591
  if (!entry.isDirectory())
176558
176592
  return false;
176559
- if (!await hasRuntimeSentinel(join16(dir, entry.name), fs2))
176593
+ if (!await hasRuntimeSentinel(join17(dir, entry.name), fs2))
176560
176594
  return false;
176561
176595
  }
176562
176596
  return true;
176563
176597
  }
176564
176598
  async function hasRuntimeSentinel(hashDir, fs2) {
176565
176599
  try {
176566
- const pkg = JSON.parse(await fs2.readFile(join16(hashDir, "package.json")));
176600
+ const pkg = JSON.parse(await fs2.readFile(join17(hashDir, "package.json")));
176567
176601
  return pkg.name === runtimeSentinel;
176568
176602
  } catch {
176569
176603
  return false;
@@ -176637,10 +176671,10 @@ function spawnNpmInstall(cwd, spawn2 = spawn) {
176637
176671
  }
176638
176672
 
176639
176673
  // src/domains/runtimeEnv/resolvePinned.ts
176640
- import { join as join17 } from "node:path";
176674
+ import { join as join18 } from "node:path";
176641
176675
  function readInstalledVersion(dir, pkgName, fs2) {
176642
176676
  try {
176643
- const pkgPath = join17(dir, "node_modules", ...pkgName.split("/"), "package.json");
176677
+ const pkgPath = join18(dir, "node_modules", ...pkgName.split("/"), "package.json");
176644
176678
  const raw = JSON.parse(fs2.readFileSync(pkgPath));
176645
176679
  const { version: version2 } = raw;
176646
176680
  return typeof version2 === "string" ? version2 : undefined;
@@ -176649,8 +176683,18 @@ function readInstalledVersion(dir, pkgName, fs2) {
176649
176683
  }
176650
176684
  }
176651
176685
  var shims = [
176652
- { name: "playwright", candidates: playwrightCliCandidates },
176653
- { name: "appium", candidates: appiumCliCandidates }
176686
+ {
176687
+ name: "playwright",
176688
+ display: "node_modules/playwright/cli.js",
176689
+ candidates: (dir, _platform) => [
176690
+ playwrightCliJsPath(dir)
176691
+ ]
176692
+ },
176693
+ {
176694
+ name: "appium",
176695
+ display: "node_modules/.bin/appium",
176696
+ candidates: appiumCliCandidates
176697
+ }
176654
176698
  ];
176655
176699
  function pinnedResolutionFailures(dir, fs2, platform) {
176656
176700
  const failures = [];
@@ -176660,16 +176704,16 @@ function pinnedResolutionFailures(dir, fs2, platform) {
176660
176704
  failures.push({ kind: "package", name, pinned: version2, installed });
176661
176705
  }
176662
176706
  }
176663
- for (const { name, candidates } of shims) {
176707
+ for (const { name, display, candidates } of shims) {
176664
176708
  if (!candidates(dir, platform).some((p2) => fs2.existsSync(p2))) {
176665
- failures.push({ kind: "shim", name });
176709
+ failures.push({ kind: "shim", name, display });
176666
176710
  }
176667
176711
  }
176668
176712
  return failures;
176669
176713
  }
176670
176714
  function describePinnedFailure(failure) {
176671
176715
  if (failure.kind === "shim") {
176672
- return `node_modules/.bin/${failure.name} (missing)`;
176716
+ return `${failure.display} (missing)`;
176673
176717
  }
176674
176718
  return failure.installed === undefined ? `${failure.name} (missing, pinned ${failure.pinned})` : `${failure.name} ${failure.installed} (pinned ${failure.pinned})`;
176675
176719
  }
@@ -176678,10 +176722,10 @@ function allPinnedResolved(dir, fs2, platform) {
176678
176722
  }
176679
176723
 
176680
176724
  // src/domains/runtimeEnv/shimDeps.ts
176681
- import { join as join19 } from "node:path";
176725
+ import { join as join20 } from "node:path";
176682
176726
 
176683
176727
  // src/shell/resolveExport.ts
176684
- import { join as join18 } from "node:path";
176728
+ import { join as join19 } from "node:path";
176685
176729
  function pickPath(entry, preference) {
176686
176730
  if (typeof entry === "string")
176687
176731
  return entry;
@@ -176713,28 +176757,28 @@ function resolveFromEnvDir(envDir, specifier, preference = "esm", fs2 = makeDefa
176713
176757
  const pkgName = parts.slice(0, pkgPartCount).join("/");
176714
176758
  const rest = parts.slice(pkgPartCount);
176715
176759
  const subpath = rest.length > 0 ? "./" + rest.join("/") : ".";
176716
- const pkgDir = join18(envDir, "node_modules", ...pkgName.split("/"));
176760
+ const pkgDir = join19(envDir, "node_modules", ...pkgName.split("/"));
176717
176761
  let pkg;
176718
176762
  try {
176719
- pkg = JSON.parse(fs2.readFileSync(join18(pkgDir, "package.json")));
176763
+ pkg = JSON.parse(fs2.readFileSync(join19(pkgDir, "package.json")));
176720
176764
  } catch (err) {
176721
176765
  if (isNoEntError(err))
176722
- throw new Error(`Package '${pkgName}' not found in ${join18(envDir, "node_modules")}`, { cause: err });
176766
+ throw new Error(`Package '${pkgName}' not found in ${join19(envDir, "node_modules")}`, { cause: err });
176723
176767
  throw err;
176724
176768
  }
176725
176769
  const exportsMap = normalizeExports(pkg.exports);
176726
176770
  const entry = exportsMap?.[subpath];
176727
176771
  const relative3 = pickPath(entry, preference) ?? (subpath === "." ? pkg.module ?? pkg.main : undefined);
176728
176772
  if (!relative3) {
176729
- throw new Error(`No entry for "${subpath}" in exports of ${join18(pkgDir, "package.json")}`);
176773
+ throw new Error(`No entry for "${subpath}" in exports of ${join19(pkgDir, "package.json")}`);
176730
176774
  }
176731
- return join18(pkgDir, relative3);
176775
+ return join19(pkgDir, relative3);
176732
176776
  }
176733
176777
 
176734
176778
  // src/domains/runtimeEnv/shimDeps.ts
176735
176779
  function readShimMarker(shimDir, fs2) {
176736
176780
  try {
176737
- const pkg = JSON.parse(fs2.readFileSync(join19(shimDir, "package.json")));
176781
+ const pkg = JSON.parse(fs2.readFileSync(join20(shimDir, "package.json")));
176738
176782
  if (pkg._qawolf_format) {
176739
176783
  return {
176740
176784
  _qawolf_version: pkg._qawolf_version ?? "",
@@ -176750,11 +176794,11 @@ function resolveBunBuild(bunBuild) {
176750
176794
  return bunBuild === false ? undefined : { build: bunBuild };
176751
176795
  }
176752
176796
  async function removeStaleShims(flowsDir, flowsDeps, fs2) {
176753
- const shimsDir = join19(flowsDir, "node_modules");
176797
+ const shimsDir = join20(flowsDir, "node_modules");
176754
176798
  if (!fs2.existsSync(shimsDir))
176755
176799
  return;
176756
176800
  for (const dep of flowsDeps) {
176757
- const shimDepDir = join19(shimsDir, ...dep.split("/"));
176801
+ const shimDepDir = join20(shimsDir, ...dep.split("/"));
176758
176802
  if (readShimMarker(shimDepDir, fs2)) {
176759
176803
  await fs2.rm(shimDepDir, { recursive: true, force: true });
176760
176804
  }
@@ -176763,17 +176807,17 @@ async function removeStaleShims(flowsDir, flowsDeps, fs2) {
176763
176807
  async function buildDepShim(args) {
176764
176808
  const { envDir, flowsDir, dep, fs: fs2, build: build2 } = args;
176765
176809
  const depParts = dep.split("/");
176766
- const depDir = join19(envDir, "node_modules", ...depParts);
176810
+ const depDir = join20(envDir, "node_modules", ...depParts);
176767
176811
  if (!fs2.existsSync(depDir))
176768
176812
  return;
176769
176813
  let depVersion;
176770
176814
  try {
176771
- const pkg = JSON.parse(fs2.readFileSync(join19(depDir, "package.json")));
176815
+ const pkg = JSON.parse(fs2.readFileSync(join20(depDir, "package.json")));
176772
176816
  depVersion = pkg.version ?? "unknown";
176773
176817
  } catch {
176774
176818
  depVersion = "unknown";
176775
176819
  }
176776
- const shimDir = join19(flowsDir, "node_modules", ...depParts);
176820
+ const shimDir = join20(flowsDir, "node_modules", ...depParts);
176777
176821
  if (fs2.existsSync(shimDir)) {
176778
176822
  const marker = readShimMarker(shimDir, fs2);
176779
176823
  if (!marker)
@@ -176800,20 +176844,20 @@ async function buildDepShim(args) {
176800
176844
  }
176801
176845
  const shimCode = await output.text();
176802
176846
  await fs2.mkdir(shimDir, { recursive: true });
176803
- await fs2.writeFile(join19(shimDir, "package.json"), JSON.stringify({
176847
+ await fs2.writeFile(join20(shimDir, "package.json"), JSON.stringify({
176804
176848
  name: dep,
176805
176849
  _qawolf_version: depVersion,
176806
176850
  _qawolf_format: "bun-build-v1"
176807
176851
  }));
176808
- await fs2.writeFile(join19(shimDir, "index.js"), shimCode);
176852
+ await fs2.writeFile(join20(shimDir, "index.js"), shimCode);
176809
176853
  }
176810
176854
  async function shimFlowsDeps(envDir, fs2 = makeDefaultFs(), bunBuild) {
176811
- const flowsDir = join19(envDir, "node_modules", "@qawolf", "flows");
176855
+ const flowsDir = join20(envDir, "node_modules", "@qawolf", "flows");
176812
176856
  if (!fs2.existsSync(flowsDir))
176813
176857
  return;
176814
176858
  let flowsDeps;
176815
176859
  try {
176816
- const flowsPkg = JSON.parse(fs2.readFileSync(join19(flowsDir, "package.json")));
176860
+ const flowsPkg = JSON.parse(fs2.readFileSync(join20(flowsDir, "package.json")));
176817
176861
  flowsDeps = Object.keys(flowsPkg.dependencies ?? {});
176818
176862
  } catch {
176819
176863
  return;
@@ -176937,6 +176981,7 @@ async function handleDoctor(ctx, opts) {
176937
176981
  apiKey: resolved?.key,
176938
176982
  fetch: globalThis.fetch,
176939
176983
  spawn: defaultSpawn,
176984
+ execPath: process.execPath,
176940
176985
  apiBaseUrl: ctx.apiBaseUrl,
176941
176986
  enginesNode: package_default.engines.node,
176942
176987
  processVersion: process.version,
@@ -177079,11 +177124,11 @@ async function flowsListRemote(ctx, pattern, options) {
177079
177124
  }
177080
177125
 
177081
177126
  // src/domains/flows/pull/handler.ts
177082
- import { dirname as dirname7, join as join24, resolve as resolve8 } from "node:path";
177127
+ import { dirname as dirname7, join as join25, resolve as resolve8 } from "node:path";
177083
177128
 
177084
177129
  // src/shell/manifest/io.ts
177085
177130
  import { createHash as createHash2 } from "node:crypto";
177086
- import { join as join20 } from "node:path";
177131
+ import { join as join21 } from "node:path";
177087
177132
  var manifestFilename = ".manifest.json";
177088
177133
  var flowEntrySchema = exports_external.object({
177089
177134
  path: exports_external.string(),
@@ -177102,7 +177147,7 @@ var manifestSchema = exports_external.object({
177102
177147
  async function readManifest(envDir, fs2 = makeDefaultFs()) {
177103
177148
  let raw;
177104
177149
  try {
177105
- raw = await fs2.readFile(join20(envDir, manifestFilename));
177150
+ raw = await fs2.readFile(join21(envDir, manifestFilename));
177106
177151
  } catch (err) {
177107
177152
  if (isNoEntError(err))
177108
177153
  return "missing";
@@ -177130,7 +177175,7 @@ async function readManifest(envDir, fs2 = makeDefaultFs()) {
177130
177175
  }
177131
177176
  async function writeManifest(envDir, manifest, fs2 = makeDefaultFs()) {
177132
177177
  const body = JSON.stringify(manifest, undefined, 2);
177133
- await fs2.writeFile(join20(envDir, manifestFilename), `${body}
177178
+ await fs2.writeFile(join21(envDir, manifestFilename), `${body}
177134
177179
  `);
177135
177180
  }
177136
177181
  function hashFile(absPath, fs2 = makeDefaultFs()) {
@@ -177265,7 +177310,7 @@ async function checkSafety(args) {
177265
177310
  }
177266
177311
 
177267
177312
  // src/domains/flows/pull/bundle.ts
177268
- import { join as join21, relative as relative4 } from "node:path";
177313
+ import { join as join22, relative as relative4 } from "node:path";
177269
177314
  async function flattenSingleWrapper(dir, fs2 = makeDefaultFs()) {
177270
177315
  const entries = await fs2.readdir(dir);
177271
177316
  if (entries.length !== 1)
@@ -177273,12 +177318,12 @@ async function flattenSingleWrapper(dir, fs2 = makeDefaultFs()) {
177273
177318
  const innerName = entries[0];
177274
177319
  if (!innerName)
177275
177320
  return;
177276
- const inner = join21(dir, innerName);
177321
+ const inner = join22(dir, innerName);
177277
177322
  const innerStat = await fs2.stat(inner);
177278
177323
  if (!innerStat.isDirectory())
177279
177324
  return;
177280
177325
  for (const e of await fs2.readdir(inner)) {
177281
- await fs2.rename(join21(inner, e), join21(dir, e));
177326
+ await fs2.rename(join22(inner, e), join22(dir, e));
177282
177327
  }
177283
177328
  const leftovers = await fs2.readdir(inner);
177284
177329
  if (leftovers.length > 0) {
@@ -177297,7 +177342,7 @@ async function buildManifest(args, fs2 = makeDefaultFs()) {
177297
177342
  const flowPaths = await walkForFlows(args.bundleDir, fs2);
177298
177343
  const flows = await Promise.all(flowPaths.map(async (rel) => ({
177299
177344
  path: rel,
177300
- contentHash: await hashFile(join21(args.bundleDir, rel), fs2)
177345
+ contentHash: await hashFile(join22(args.bundleDir, rel), fs2)
177301
177346
  })));
177302
177347
  return {
177303
177348
  envId: args.envId,
@@ -177318,7 +177363,7 @@ async function walkForFlows(root, fs2) {
177318
177363
  async function walk(current, root, out, fs2) {
177319
177364
  const entries = await fs2.readdirWithTypes(current);
177320
177365
  for (const e of entries) {
177321
- const abs = join21(current, e.name);
177366
+ const abs = join22(current, e.name);
177322
177367
  if (e.isDirectory()) {
177323
177368
  await walk(abs, root, out, fs2);
177324
177369
  } else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
@@ -177331,11 +177376,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs2 = makeDefaultFs()) {
177331
177376
  const sample = flowPaths[0];
177332
177377
  if (!sample)
177333
177378
  return;
177334
- return (await fs2.stat(join21(bundleDir, sample))).mtime.toISOString();
177379
+ return (await fs2.stat(join22(bundleDir, sample))).mtime.toISOString();
177335
177380
  }
177336
177381
 
177337
177382
  // src/domains/flows/pull/applyTeamStorageRewrite.ts
177338
- import { join as join22, relative as relative5 } from "node:path";
177383
+ import { join as join23, relative as relative5 } from "node:path";
177339
177384
 
177340
177385
  // src/domains/flows/pull/rewriteTeamStorage.ts
177341
177386
  var runnerMountPrefix = "/home/wolf/team-storage/";
@@ -177371,7 +177416,7 @@ function isFlowFile(name) {
177371
177416
  async function walk2(dir, out, fs2) {
177372
177417
  const entries = await fs2.readdirWithTypes(dir);
177373
177418
  for (const e of entries) {
177374
- const abs = join22(dir, e.name);
177419
+ const abs = join23(dir, e.name);
177375
177420
  if (e.isDirectory()) {
177376
177421
  await walk2(abs, out, fs2);
177377
177422
  } else if (e.isFile() && isSourceFile(e.name)) {
@@ -177399,7 +177444,7 @@ async function applyTeamStorageRewrite(rootDir, fs2 = makeDefaultFs()) {
177399
177444
  }
177400
177445
 
177401
177446
  // src/domains/flows/pull/envVars.ts
177402
- import { join as join23 } from "node:path";
177447
+ import { join as join24 } from "node:path";
177403
177448
 
177404
177449
  // src/core/dotenv.ts
177405
177450
  var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -177464,7 +177509,7 @@ function unquote(escaped) {
177464
177509
  async function writeEnvFile(envDir, vars, fs2 = makeDefaultFs()) {
177465
177510
  if (Object.keys(vars).length === 0)
177466
177511
  return;
177467
- await fs2.writeFile(join23(envDir, ".env"), serializeDotenv(vars), {
177512
+ await fs2.writeFile(join24(envDir, ".env"), serializeDotenv(vars), {
177468
177513
  mode: 384
177469
177514
  });
177470
177515
  }
@@ -180837,8 +180882,8 @@ async function handleFlowsPull(ctx, opts, deps) {
180837
180882
  ctx.ui.error(validation.error);
180838
180883
  return { error: validation.error };
180839
180884
  }
180840
- const destAbs = resolve8(opts.out ?? join24(".qawolf", opts.env));
180841
- const assetsAbs = join24(dirname7(destAbs), "assets");
180885
+ const destAbs = resolve8(opts.out ?? join25(".qawolf", opts.env));
180886
+ const assetsAbs = join25(dirname7(destAbs), "assets");
180842
180887
  await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
180843
180888
  const yes = opts.yes ?? false;
180844
180889
  let archive;
@@ -180904,7 +180949,7 @@ async function handleFlowsPull(ctx, opts, deps) {
180904
180949
  assetDownloadedCount: assetResult.downloadedCount,
180905
180950
  assetReusedCount: assetResult.reusedCount,
180906
180951
  assetSkippedCount: assetResult.skippedCount,
180907
- manifestPath: join24(result.envDir, manifestFilename)
180952
+ manifestPath: join25(result.envDir, manifestFilename)
180908
180953
  }, "");
180909
180954
  }
180910
180955
  } finally {
@@ -181090,6 +181135,72 @@ function buildPatternArgs(pattern) {
181090
181135
  return pattern ? [pattern] : [];
181091
181136
  }
181092
181137
 
181138
+ // src/shell/reporter/createCompositeReporter.ts
181139
+ function callEach(reporters, pick2, event) {
181140
+ for (const r2 of reporters) {
181141
+ try {
181142
+ pick2(r2)?.(event);
181143
+ } catch (err) {
181144
+ process.stderr.write(`[reporter error] ${String(err)}
181145
+ `);
181146
+ }
181147
+ }
181148
+ }
181149
+ function createCompositeReporter(reporters) {
181150
+ return {
181151
+ onFlowStart: (e) => callEach(reporters, (r2) => r2.onFlowStart, e),
181152
+ onFlowPass: (e) => callEach(reporters, (r2) => r2.onFlowPass, e),
181153
+ onFlowFail: (e) => callEach(reporters, (r2) => r2.onFlowFail, e),
181154
+ onTestStart: (e) => callEach(reporters, (r2) => r2.onTestStart, e),
181155
+ onTestResult: (e) => callEach(reporters, (r2) => r2.onTestResult, e),
181156
+ onScreenshot: (e) => callEach(reporters, (r2) => r2.onScreenshot, e),
181157
+ onRunComplete: (e) => callEach(reporters, (r2) => r2.onRunComplete, e)
181158
+ };
181159
+ }
181160
+
181161
+ // src/shell/reporter/formatErrorWithCause.ts
181162
+ function filterStack(stack) {
181163
+ const cwd = process.cwd();
181164
+ return stack.split(`
181165
+ `).filter((line) => {
181166
+ if (!/^\s+at /.test(line))
181167
+ return true;
181168
+ return !line.includes("node_modules") && !line.includes("dist/cli.js");
181169
+ }).map((line) => {
181170
+ if (!/^\s+at /.test(line))
181171
+ return line;
181172
+ return line.replace(`file://${cwd}/`, "").replace(`${cwd}/`, "");
181173
+ }).join(`
181174
+ `);
181175
+ }
181176
+ function renderCause(cause) {
181177
+ if (cause instanceof Error)
181178
+ return filterStack(cause.stack ?? cause.message);
181179
+ if (typeof cause === "object" && cause !== null) {
181180
+ const obj = cause;
181181
+ if (typeof obj["message"] === "string")
181182
+ return obj["message"];
181183
+ try {
181184
+ return JSON.stringify(cause);
181185
+ } catch {
181186
+ return String(cause);
181187
+ }
181188
+ }
181189
+ return String(cause);
181190
+ }
181191
+ function formatErrorWithCause(err) {
181192
+ const parts = [String(err)];
181193
+ let cause = err.cause;
181194
+ while (cause !== undefined && cause !== null) {
181195
+ parts.push(`Caused by: ${renderCause(cause)}`);
181196
+ if (!(cause instanceof Error))
181197
+ break;
181198
+ cause = cause.cause;
181199
+ }
181200
+ return parts.join(`
181201
+ `);
181202
+ }
181203
+
181093
181204
  // src/domains/runner/runAndroidFlowUtils.ts
181094
181205
  import { parseExecutionTarget as parseExecutionTarget3 } from "@qawolf/flow-targets";
181095
181206
 
@@ -181437,9 +181548,16 @@ async function flowsRun(ctx, files, flags, deps) {
181437
181548
  return { error: runnerMessages.androidWorkersUnsupported, exitCode: 2 };
181438
181549
  }
181439
181550
  const { webOptions, androidOptions } = buildRunOptions(flags);
181551
+ const failureDetails = [];
181552
+ const reporter = ctx.outputMode === "json" ? createCompositeReporter([
181553
+ deps.reporter,
181554
+ {
181555
+ onFlowFail: ({ err }) => failureDetails.push(formatErrorWithCause(err))
181556
+ }
181557
+ ]) : deps.reporter;
181440
181558
  const result = await executeFlows({
181441
181559
  ctx,
181442
- deps,
181560
+ deps: { ...deps, reporter },
181443
181561
  flags,
181444
181562
  flows,
181445
181563
  webFlows,
@@ -181467,7 +181585,13 @@ async function flowsRun(ctx, files, flags, deps) {
181467
181585
  deps.reporter.onRunComplete?.({ summary });
181468
181586
  ctx.ui.gap();
181469
181587
  if (counts.flowsFailed > 0) {
181470
- return { error: runnerMessages.flowsFailed(counts.flowsFailed) };
181588
+ const error51 = runnerMessages.flowsFailed(counts.flowsFailed);
181589
+ if (ctx.outputMode === "json" && failureDetails.length > 0) {
181590
+ return { error: error51, errorBody: failureDetails.join(`
181591
+
181592
+ `) };
181593
+ }
181594
+ return { error: error51 };
181471
181595
  }
181472
181596
  }
181473
181597
 
@@ -181518,15 +181642,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
181518
181642
 
181519
181643
  // src/domains/runtimeEnv/prepareRunDir.ts
181520
181644
  import { createHash as createHash4 } from "node:crypto";
181521
- import { join as join31, resolve as resolve9 } from "node:path";
181645
+ import { join as join32, resolve as resolve9 } from "node:path";
181522
181646
 
181523
181647
  // src/domains/runtimeEnv/execSubpathImports.ts
181524
- import { join as join25 } from "node:path";
181648
+ import { join as join26 } from "node:path";
181525
181649
  var flowSubpathImports = { "#playwright": "playwright" };
181526
181650
  var packageJsonSchema = exports_external.looseObject({ imports: exports_external.record(exports_external.string(), exports_external.unknown()).catch({}) }).catch({ imports: {} });
181527
181651
  async function writeExecSubpathImports(args) {
181528
181652
  const { execDir, fs: fs3 } = args;
181529
- const pkgPath = join25(execDir, "package.json");
181653
+ const pkgPath = join26(execDir, "package.json");
181530
181654
  const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs3));
181531
181655
  const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
181532
181656
  await fs3.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
@@ -181548,10 +181672,10 @@ async function readPackageJson(pkgPath, fs3) {
181548
181672
  }
181549
181673
 
181550
181674
  // src/domains/runtimeEnv/innerHop.ts
181551
- import { join as join27 } from "node:path";
181675
+ import { join as join28 } from "node:path";
181552
181676
 
181553
181677
  // src/domains/runtimeEnv/linkPinnedPackages.ts
181554
- import { dirname as dirname8, join as join26 } from "node:path";
181678
+ import { dirname as dirname8, join as join27 } from "node:path";
181555
181679
 
181556
181680
  // src/domains/runtimeEnv/symlinkDir.ts
181557
181681
  import { symlink } from "node:fs/promises";
@@ -181563,16 +181687,16 @@ async function createDirSymlink(source, target) {
181563
181687
  // src/domains/runtimeEnv/linkPinnedPackages.ts
181564
181688
  async function linkPinnedPackages(args) {
181565
181689
  const { depsRoot, nodeModulesDir, fs: fs3 } = args;
181566
- const managedModules = join26(depsRoot, "node_modules");
181690
+ const managedModules = join27(depsRoot, "node_modules");
181567
181691
  await fs3.mkdir(nodeModulesDir, { recursive: true });
181568
181692
  for (const { name } of pinnedPackages) {
181569
181693
  const segments = name.split("/");
181570
- const target = join26(nodeModulesDir, ...segments);
181694
+ const target = join27(nodeModulesDir, ...segments);
181571
181695
  if (segments.length > 1) {
181572
181696
  await fs3.mkdir(dirname8(target), { recursive: true });
181573
181697
  }
181574
181698
  await fs3.rm(target, { recursive: true, force: true });
181575
- await createDirSymlink(join26(managedModules, ...segments), target);
181699
+ await createDirSymlink(join27(managedModules, ...segments), target);
181576
181700
  }
181577
181701
  }
181578
181702
 
@@ -181581,14 +181705,14 @@ async function populateInnerHop(args) {
181581
181705
  const { depsRoot, execDir, fs: fs3 } = args;
181582
181706
  await linkPinnedPackages({
181583
181707
  depsRoot,
181584
- nodeModulesDir: join27(execDir, "node_modules"),
181708
+ nodeModulesDir: join28(execDir, "node_modules"),
181585
181709
  fs: fs3
181586
181710
  });
181587
181711
  }
181588
181712
 
181589
181713
  // src/domains/runtimeEnv/outerHop.ts
181590
181714
  import { lstat } from "node:fs/promises";
181591
- import { dirname as dirname9, join as join28 } from "node:path";
181715
+ import { dirname as dirname9, join as join29 } from "node:path";
181592
181716
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
181593
181717
  async function populateOuterHop(args) {
181594
181718
  const { projectDir, runDir, fs: fs3 } = args;
@@ -181598,7 +181722,7 @@ async function populateOuterHop(args) {
181598
181722
  const depNames = Object.keys(installableDeps);
181599
181723
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
181600
181724
  if (satisfying !== undefined) {
181601
- await createDirSymlink(satisfying, join28(runDir, "node_modules"));
181725
+ await createDirSymlink(satisfying, join29(runDir, "node_modules"));
181602
181726
  return { mode: "symlink", nodeModulesDir: satisfying };
181603
181727
  }
181604
181728
  if (depNames.length === 0)
@@ -181607,7 +181731,7 @@ async function populateOuterHop(args) {
181607
181731
  await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
181608
181732
  await linkPinnedPackages({
181609
181733
  depsRoot: args.depsRoot,
181610
- nodeModulesDir: join28(runDir, "node_modules"),
181734
+ nodeModulesDir: join29(runDir, "node_modules"),
181611
181735
  fs: fs3
181612
181736
  });
181613
181737
  return { mode: "install", depCount: depNames.length, rejected };
@@ -181616,10 +181740,10 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
181616
181740
  const rejected = [];
181617
181741
  let dir = startDir;
181618
181742
  while (true) {
181619
- const candidate = join28(dir, "node_modules");
181743
+ const candidate = join29(dir, "node_modules");
181620
181744
  const stats = await lstatSafe(candidate);
181621
181745
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
181622
- const missing = depNames.filter((name) => !fs3.existsSync(join28(candidate, name, "package.json")));
181746
+ const missing = depNames.filter((name) => !fs3.existsSync(join29(candidate, name, "package.json")));
181623
181747
  if (missing.length === 0)
181624
181748
  return { satisfying: candidate, rejected };
181625
181749
  rejected.push({ dir: candidate, missing });
@@ -181640,7 +181764,7 @@ async function lstatSafe(path3) {
181640
181764
  async function readInstallableDeps(projectDir, fs3) {
181641
181765
  let content;
181642
181766
  try {
181643
- content = await fs3.readFile(join28(projectDir, "package.json"));
181767
+ content = await fs3.readFile(join29(projectDir, "package.json"));
181644
181768
  } catch {
181645
181769
  return {};
181646
181770
  }
@@ -181658,8 +181782,8 @@ async function readInstallableDeps(projectDir, fs3) {
181658
181782
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
181659
181783
  }
181660
181784
  async function installOuterDeps(runDir, deps, fs3, install) {
181661
- await fs3.writeFile(join28(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
181662
- await fs3.writeFile(join28(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
181785
+ await fs3.writeFile(join29(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
181786
+ await fs3.writeFile(join29(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
181663
181787
  `);
181664
181788
  const result = await install(runDir);
181665
181789
  if (result.exitCode !== 0) {
@@ -181669,14 +181793,14 @@ async function installOuterDeps(runDir, deps, fs3, install) {
181669
181793
 
181670
181794
  // src/domains/runtimeEnv/stageFlowFiles.ts
181671
181795
  import { createHash as createHash3 } from "node:crypto";
181672
- import { basename as basename4, dirname as dirname10, join as join30, sep as sep4 } from "node:path";
181796
+ import { basename as basename4, dirname as dirname10, join as join31, sep as sep4 } from "node:path";
181673
181797
 
181674
181798
  // src/shell/copyDir.ts
181675
181799
  import { cp, readdir as readdir2 } from "node:fs/promises";
181676
- import { basename as basename3, join as join29 } from "node:path";
181800
+ import { basename as basename3, join as join30 } from "node:path";
181677
181801
  async function copyDirExcluding(source, destination, excludedNames) {
181678
181802
  const entries = await readdir2(source, { withFileTypes: true });
181679
- await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join29(source, entry.name), join29(destination, entry.name), {
181803
+ await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join30(source, entry.name), join30(destination, entry.name), {
181680
181804
  recursive: true,
181681
181805
  filter: (path3) => !excludedNames.has(basename3(path3))
181682
181806
  })));
@@ -181693,14 +181817,14 @@ async function stageFlowFiles(args) {
181693
181817
  if (files.length > 1) {
181694
181818
  return Promise.all(files.map((f3) => stageOneFile(f3, execDir, fs3)));
181695
181819
  }
181696
- await Promise.all(files.map((f3) => fs3.copyFile(f3, join30(execDir, basename4(f3)))));
181697
- return files.map((f3) => join30(execDir, basename4(f3)));
181820
+ await Promise.all(files.map((f3) => fs3.copyFile(f3, join31(execDir, basename4(f3)))));
181821
+ return files.map((f3) => join31(execDir, basename4(f3)));
181698
181822
  }
181699
181823
  async function stageOneFile(file2, execDir, fs3) {
181700
181824
  const dirHash = createHash3("sha256").update(dirname10(file2)).digest("hex").slice(0, 8);
181701
- const subDir = join30(execDir, dirHash);
181825
+ const subDir = join31(execDir, dirHash);
181702
181826
  await fs3.mkdir(subDir, { recursive: true });
181703
- const dest = join30(subDir, basename4(file2));
181827
+ const dest = join31(subDir, basename4(file2));
181704
181828
  await fs3.copyFile(file2, dest);
181705
181829
  return dest;
181706
181830
  }
@@ -181708,7 +181832,7 @@ function remapPath(file2, projectDir, execDir) {
181708
181832
  if (file2 === projectDir)
181709
181833
  return execDir;
181710
181834
  if (file2.startsWith(projectDir + sep4)) {
181711
- return join30(execDir, file2.slice(projectDir.length + 1));
181835
+ return join31(execDir, file2.slice(projectDir.length + 1));
181712
181836
  }
181713
181837
  return;
181714
181838
  }
@@ -181718,10 +181842,10 @@ async function prepareRunDir(args) {
181718
181842
  const { files, projectDir, depsRoot, runRoot } = args;
181719
181843
  const fs3 = args.fs ?? makeDefaultFs();
181720
181844
  const runId = buildRunId(projectDir, files);
181721
- const runDir = join31(runRoot, runId);
181845
+ const runDir = join32(runRoot, runId);
181722
181846
  await fs3.rm(runDir, { recursive: true, force: true });
181723
181847
  await fs3.mkdir(runDir, { recursive: true });
181724
- const execDir = join31(runDir, "exec");
181848
+ const execDir = join32(runDir, "exec");
181725
181849
  await fs3.mkdir(execDir, { recursive: true });
181726
181850
  await populateInnerHop({ depsRoot, execDir, fs: fs3 });
181727
181851
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs3 });
@@ -181752,17 +181876,17 @@ function buildRunId(projectDir, files) {
181752
181876
  }
181753
181877
 
181754
181878
  // src/shell/testkit.ts
181755
- import { join as join32 } from "node:path";
181879
+ import { join as join33 } from "node:path";
181756
181880
  function notAvailableLocally(name) {
181757
181881
  throw new Error(runnerMessages.notAvailableLocally(name));
181758
181882
  }
181759
181883
  async function loadSdkDeps(cwd) {
181760
- const testkitDir = join32(cwd, "node_modules", "@qawolf", "testkit");
181884
+ const testkitDir = join33(cwd, "node_modules", "@qawolf", "testkit");
181761
181885
  try {
181762
181886
  const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
181763
181887
  if (typeof clientMod.createTestkitClient !== "function")
181764
181888
  throw new Error("createTestkitClient is not a function");
181765
- const scopeMod = await importFromPath(join32(testkitDir, "dist", "clientScope.js"));
181889
+ const scopeMod = await importFromPath(join33(testkitDir, "dist", "clientScope.js"));
181766
181890
  if (typeof scopeMod.configureTestkitClient !== "function")
181767
181891
  throw new Error("configureTestkitClient is not a function");
181768
181892
  return {
@@ -181820,9 +181944,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
181820
181944
  }
181821
181945
 
181822
181946
  // src/commands/flows/envVarDeps.ts
181823
- import { join as join33 } from "node:path";
181947
+ import { join as join34 } from "node:path";
181824
181948
  function envFilePath(envDir) {
181825
- return join33(envDir, ".env");
181949
+ return join34(envDir, ".env");
181826
181950
  }
181827
181951
  async function readEnv(envDir, fs3) {
181828
181952
  try {
@@ -182319,17 +182443,21 @@ async function findFlowStamp(flowAbsPath) {
182319
182443
 
182320
182444
  // src/domains/install/browsers.ts
182321
182445
  async function installBrowserList(ctx, browsers, deps) {
182322
- const candidates = playwrightCliCandidates(deps.envDir, deps.platform);
182323
- const cliPath = candidates.find(deps.checkExists);
182324
- if (!cliPath) {
182325
- throw new Error(installMessages.playwrightNotFound(candidates[0] ?? deps.envDir));
182446
+ const cliJsPath = playwrightCliJsPath(deps.envDir);
182447
+ if (!deps.checkExists(cliJsPath)) {
182448
+ throw new Error(installMessages.playwrightNotFound(cliJsPath));
182326
182449
  }
182327
182450
  await ctx.ui.withProgress(browsers.map((browser) => ({
182328
182451
  message: installMessages.installingBrowser(browser),
182329
182452
  task: async () => {
182330
- const args = buildArgs(browser, deps.platform, deps.browserDeps);
182331
- const result = await deps.spawn(cliPath, args, {
182332
- platform: deps.platform
182453
+ const invocation = playwrightCliInvocation({
182454
+ envDir: deps.envDir,
182455
+ execPath: deps.execPath,
182456
+ cliArgs: buildArgs(browser, deps.platform, deps.browserDeps)
182457
+ });
182458
+ const result = await deps.spawn(invocation.cmd, invocation.args, {
182459
+ platform: deps.platform,
182460
+ env: invocation.env
182333
182461
  });
182334
182462
  if (result.exitCode !== 0) {
182335
182463
  throw new Error(formatError2(browser, result));
@@ -182347,6 +182475,7 @@ async function installBrowsers(ctx, pattern, deps) {
182347
182475
  }
182348
182476
  await installBrowserList(ctx, browsers, {
182349
182477
  spawn: deps.spawn,
182478
+ execPath: deps.execPath,
182350
182479
  platform: deps.platform,
182351
182480
  browserDeps: deps.browserDeps,
182352
182481
  envDir: await deps.resolveDepsRoot(files),
@@ -183221,14 +183350,14 @@ async function runWebFlow({
183221
183350
 
183222
183351
  // src/shell/embeddedWorkerCli.ts
183223
183352
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
183224
- import { basename as basename6, join as join34 } from "node:path";
183353
+ import { basename as basename6, join as join35 } from "node:path";
183225
183354
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
183226
183355
  function extractEmbeddedWorkerCli() {
183227
183356
  const assetPath = process.env[embeddedCliPathEnv];
183228
183357
  if (assetPath === undefined || assetPath === "")
183229
183358
  return;
183230
- const dir = join34(getDataDir(), "worker");
183231
- const dest = join34(dir, basename6(assetPath));
183359
+ const dir = join35(getDataDir(), "worker");
183360
+ const dest = join35(dir, basename6(assetPath));
183232
183361
  if (existsSync3(dest))
183233
183362
  return dest;
183234
183363
  mkdirSync3(dir, { recursive: true });
@@ -183387,51 +183516,6 @@ import { dirname as dirname12 } from "node:path";
183387
183516
 
183388
183517
  // src/shell/reporter/createConsoleReporter.ts
183389
183518
  import { styleText as styleText3 } from "node:util";
183390
-
183391
- // src/shell/reporter/formatErrorWithCause.ts
183392
- function filterStack(stack) {
183393
- const cwd = process.cwd();
183394
- return stack.split(`
183395
- `).filter((line) => {
183396
- if (!/^\s+at /.test(line))
183397
- return true;
183398
- return !line.includes("node_modules") && !line.includes("dist/cli.js");
183399
- }).map((line) => {
183400
- if (!/^\s+at /.test(line))
183401
- return line;
183402
- return line.replace(`file://${cwd}/`, "").replace(`${cwd}/`, "");
183403
- }).join(`
183404
- `);
183405
- }
183406
- function renderCause(cause) {
183407
- if (cause instanceof Error)
183408
- return filterStack(cause.stack ?? cause.message);
183409
- if (typeof cause === "object" && cause !== null) {
183410
- const obj = cause;
183411
- if (typeof obj["message"] === "string")
183412
- return obj["message"];
183413
- try {
183414
- return JSON.stringify(cause);
183415
- } catch {
183416
- return String(cause);
183417
- }
183418
- }
183419
- return String(cause);
183420
- }
183421
- function formatErrorWithCause(err) {
183422
- const parts = [String(err)];
183423
- let cause = err.cause;
183424
- while (cause !== undefined && cause !== null) {
183425
- parts.push(`Caused by: ${renderCause(cause)}`);
183426
- if (!(cause instanceof Error))
183427
- break;
183428
- cause = cause.cause;
183429
- }
183430
- return parts.join(`
183431
- `);
183432
- }
183433
-
183434
- // src/shell/reporter/createConsoleReporter.ts
183435
183519
  function fmtDuration(ms2) {
183436
183520
  return `(${(ms2 / 1000).toFixed(2)}s)`;
183437
183521
  }
@@ -183510,30 +183594,8 @@ ${styleText3("dim", "─".repeat(width))}
183510
183594
  }
183511
183595
  };
183512
183596
  }
183513
- // src/shell/reporter/createCompositeReporter.ts
183514
- function callEach(reporters, pick2, event) {
183515
- for (const r2 of reporters) {
183516
- try {
183517
- pick2(r2)?.(event);
183518
- } catch (err) {
183519
- process.stderr.write(`[reporter error] ${String(err)}
183520
- `);
183521
- }
183522
- }
183523
- }
183524
- function createCompositeReporter(reporters) {
183525
- return {
183526
- onFlowStart: (e) => callEach(reporters, (r2) => r2.onFlowStart, e),
183527
- onFlowPass: (e) => callEach(reporters, (r2) => r2.onFlowPass, e),
183528
- onFlowFail: (e) => callEach(reporters, (r2) => r2.onFlowFail, e),
183529
- onTestStart: (e) => callEach(reporters, (r2) => r2.onTestStart, e),
183530
- onTestResult: (e) => callEach(reporters, (r2) => r2.onTestResult, e),
183531
- onScreenshot: (e) => callEach(reporters, (r2) => r2.onScreenshot, e),
183532
- onRunComplete: (e) => callEach(reporters, (r2) => r2.onRunComplete, e)
183533
- };
183534
- }
183535
183597
  // src/shell/reporter/createJUnitReporter.ts
183536
- import { join as join35 } from "node:path";
183598
+ import { join as join36 } from "node:path";
183537
183599
 
183538
183600
  // src/shell/reporter/junitXml.ts
183539
183601
  function escapeXml(str) {
@@ -183572,7 +183634,7 @@ ${suites}`;
183572
183634
 
183573
183635
  // src/shell/reporter/createJUnitReporter.ts
183574
183636
  function resolveJUnitOutputPath(junit, outputDir) {
183575
- return typeof junit === "string" && junit.trim() !== "" ? junit : join35(outputDir, "junit-report.xml");
183637
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join36(outputDir, "junit-report.xml");
183576
183638
  }
183577
183639
  function createJUnitReporter(deps) {
183578
183640
  const flows = [];
@@ -183634,6 +183696,7 @@ function buildFlowsRunDeps(args) {
183634
183696
  peekFlowMeta: makePeekFlowMeta(ctx.fs),
183635
183697
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
183636
183698
  spawn: defaultSpawn,
183699
+ execPath: process.execPath,
183637
183700
  platform: process.platform,
183638
183701
  browserDeps: flags.browserDeps,
183639
183702
  envDir: resolvedDir,
@@ -183661,11 +183724,11 @@ function buildFlowsRunDeps(args) {
183661
183724
 
183662
183725
  // src/commands/flows/loadEnvFile.ts
183663
183726
  import { readFile } from "node:fs/promises";
183664
- import { join as join36 } from "node:path";
183727
+ import { join as join37 } from "node:path";
183665
183728
  async function loadEnvFile(envDir) {
183666
183729
  let content;
183667
183730
  try {
183668
- content = await readFile(join36(envDir, ".env"), "utf8");
183731
+ content = await readFile(join37(envDir, ".env"), "utf8");
183669
183732
  } catch (err) {
183670
183733
  if (isNoEntError(err))
183671
183734
  return;
@@ -183766,7 +183829,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
183766
183829
  }
183767
183830
 
183768
183831
  // src/commands/flows/hybridRunDefaults.ts
183769
- import { join as join37, resolve as resolve10 } from "node:path";
183832
+ import { join as join38, resolve as resolve10 } from "node:path";
183770
183833
  function makeDefaultHybridDeps(fs9) {
183771
183834
  return {
183772
183835
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -183785,7 +183848,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
183785
183848
  if (validation !== "ok") {
183786
183849
  return { error: validation.error, exitCode: 2 };
183787
183850
  }
183788
- const envDir = resolve10(join37(".qawolf", flags.env));
183851
+ const envDir = resolve10(join38(".qawolf", flags.env));
183789
183852
  const patternArgs = buildPatternArgs(pattern);
183790
183853
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
183791
183854
  let files = await globFlows();
@@ -183931,7 +183994,100 @@ function registerFlowsCommand(program2, signals) {
183931
183994
  }
183932
183995
 
183933
183996
  // src/domains/init/init.ts
183934
- import { dirname as dirname13, join as join38, relative as relative7 } from "node:path";
183997
+ import { dirname as dirname13, join as join40, relative as relative7 } from "node:path";
183998
+
183999
+ // src/domains/init/ensurePackageJson.ts
184000
+ import { join as join39 } from "node:path";
184001
+ async function ensurePackageJson(ctx, yes, deps) {
184002
+ const pkgPath = join39(deps.cwd, "package.json");
184003
+ if (!await deps.fs.pathExists(pkgPath)) {
184004
+ const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
184005
+ if (!confirmed2.ok || !confirmed2.value) {
184006
+ ctx.ui.info(initMessages.skippedCreatingPackageJson);
184007
+ return;
184008
+ }
184009
+ const pkg2 = {
184010
+ private: true,
184011
+ type: "module",
184012
+ dependencies: { "@qawolf/flows": flowsVersion },
184013
+ scripts: { "test:e2e": "qawolf flows run" }
184014
+ };
184015
+ await deps.fs.writeFile(pkgPath, JSON.stringify(pkg2, undefined, 2) + `
184016
+ `);
184017
+ ctx.ui.step(initMessages.createdPackageJson);
184018
+ return;
184019
+ }
184020
+ const raw = await deps.fs.readFile(pkgPath);
184021
+ let parsed;
184022
+ try {
184023
+ parsed = JSON.parse(raw);
184024
+ } catch {
184025
+ ctx.ui.warn(initMessages.packageJsonInvalidJson);
184026
+ return;
184027
+ }
184028
+ if (!isRecord3(parsed)) {
184029
+ ctx.ui.warn(initMessages.packageJsonNotAnObject);
184030
+ return;
184031
+ }
184032
+ const pkg = parsed;
184033
+ const scripts = readSection(ctx, pkg, "scripts");
184034
+ const dependencies = readSection(ctx, pkg, "dependencies");
184035
+ const devDependencies = readSection(ctx, pkg, "devDependencies");
184036
+ const needScript = scripts !== undefined && !scripts["test:e2e"];
184037
+ const priorType = pkg["type"];
184038
+ const needType = priorType !== "module";
184039
+ const needFlowsDep = dependencies !== undefined && !dependencies["@qawolf/flows"] && !devDependencies?.["@qawolf/flows"];
184040
+ const changes = [];
184041
+ if (needScript)
184042
+ changes.push(initMessages.pkgChanges.script);
184043
+ if (needType)
184044
+ changes.push(initMessages.pkgChanges.type);
184045
+ if (needFlowsDep)
184046
+ changes.push(initMessages.pkgChanges.flowsDep);
184047
+ if (changes.length === 0) {
184048
+ ctx.ui.info(initMessages.packageJsonUpToDate);
184049
+ return;
184050
+ }
184051
+ if (scripts !== undefined && scripts["test:e2e"]) {
184052
+ ctx.ui.warn(initMessages.packageJsonHasTestE2e);
184053
+ }
184054
+ const confirmed = await ctx.ui.confirm(initMessages.updatePackageJsonPrompt(changes), { yes, destructive: true });
184055
+ if (!confirmed.ok || !confirmed.value) {
184056
+ ctx.ui.info(initMessages.skippedUpdatingPackageJson);
184057
+ return;
184058
+ }
184059
+ if (needScript && scripts !== undefined) {
184060
+ scripts["test:e2e"] = "qawolf flows run";
184061
+ pkg["scripts"] = scripts;
184062
+ }
184063
+ if (needType)
184064
+ pkg["type"] = "module";
184065
+ if (needFlowsDep && dependencies !== undefined) {
184066
+ dependencies["@qawolf/flows"] = flowsVersion;
184067
+ pkg["dependencies"] = dependencies;
184068
+ }
184069
+ const trailingNewline = raw.endsWith(`
184070
+ `) ? `
184071
+ ` : "";
184072
+ await deps.fs.writeFile(pkgPath, JSON.stringify(pkg, undefined, 2) + trailingNewline);
184073
+ ctx.ui.step(initMessages.updatedPackageJson);
184074
+ if (needType && priorType !== undefined) {
184075
+ const label = typeof priorType === "string" ? priorType : JSON.stringify(priorType);
184076
+ ctx.ui.warn(initMessages.typeChanged(label));
184077
+ }
184078
+ }
184079
+ function isRecord3(value) {
184080
+ return typeof value === "object" && value !== null && !Array.isArray(value);
184081
+ }
184082
+ function readSection(ctx, pkg, key) {
184083
+ const value = pkg[key];
184084
+ if (value === undefined)
184085
+ return {};
184086
+ if (isRecord3(value))
184087
+ return value;
184088
+ ctx.ui.warn(initMessages.packageJsonMalformedSection(key));
184089
+ return;
184090
+ }
183935
184091
 
183936
184092
  // src/domains/init/templates.ts
183937
184093
  var qawolfConfigTs = `export default {
@@ -183969,9 +184125,9 @@ function makeDefaultInitDeps() {
183969
184125
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
183970
184126
  ctx.ui.gap();
183971
184127
  ctx.ui.intro(initMessages.title);
183972
- const configPath = join38(deps.cwd, "qawolf.config.ts");
183973
- const flowPath = join38(deps.cwd, "src", "flows", "example.flow.ts");
183974
- const gitignorePath = join38(deps.cwd, ".qawolf", ".gitignore");
184128
+ const configPath = join40(deps.cwd, "qawolf.config.ts");
184129
+ const flowPath = join40(deps.cwd, "src", "flows", "example.flow.ts");
184130
+ const gitignorePath = join40(deps.cwd, ".qawolf", ".gitignore");
183975
184131
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
183976
184132
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
183977
184133
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -183994,54 +184150,6 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
183994
184150
  await deps.fs.writeFile(filePath, content);
183995
184151
  ctx.ui.step(initMessages.createdFile(relPath));
183996
184152
  }
183997
- async function ensurePackageJson(ctx, yes, deps) {
183998
- const pkgPath = join38(deps.cwd, "package.json");
183999
- if (!await deps.fs.pathExists(pkgPath)) {
184000
- const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
184001
- if (!confirmed2.ok || !confirmed2.value) {
184002
- ctx.ui.info(initMessages.skippedCreatingPackageJson);
184003
- return;
184004
- }
184005
- const pkg2 = {
184006
- private: true,
184007
- type: "module",
184008
- dependencies: { "@qawolf/flows": flowsVersion },
184009
- scripts: { "test:e2e": "qawolf flows run" }
184010
- };
184011
- await deps.fs.writeFile(pkgPath, JSON.stringify(pkg2, undefined, 2) + `
184012
- `);
184013
- ctx.ui.step(initMessages.createdPackageJson);
184014
- return;
184015
- }
184016
- const raw = await deps.fs.readFile(pkgPath);
184017
- let pkg;
184018
- try {
184019
- pkg = JSON.parse(raw);
184020
- } catch {
184021
- ctx.ui.warn(initMessages.packageJsonInvalidJson);
184022
- return;
184023
- }
184024
- const scripts = pkg["scripts"] ?? {};
184025
- if (scripts["test:e2e"]) {
184026
- ctx.ui.warn(initMessages.packageJsonHasTestE2e);
184027
- return;
184028
- }
184029
- const confirmed = await ctx.ui.confirm(initMessages.addTestE2ePrompt, {
184030
- yes,
184031
- destructive: true
184032
- });
184033
- if (!confirmed.ok || !confirmed.value) {
184034
- ctx.ui.info(initMessages.skippedAddingTestE2e);
184035
- return;
184036
- }
184037
- scripts["test:e2e"] = "qawolf flows run";
184038
- pkg["scripts"] = scripts;
184039
- const trailingNewline = raw.endsWith(`
184040
- `) ? `
184041
- ` : "";
184042
- await deps.fs.writeFile(pkgPath, JSON.stringify(pkg, undefined, 2) + trailingNewline);
184043
- ctx.ui.step(initMessages.updatedPackageJson);
184044
- }
184045
184153
 
184046
184154
  // src/commands/init/index.ts
184047
184155
  function registerInitCommand(program2, signals) {
@@ -184057,7 +184165,7 @@ Examples:
184057
184165
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
184058
184166
 
184059
184167
  // src/domains/install/android/avd.ts
184060
- import { join as join39 } from "node:path";
184168
+ import { join as join41 } from "node:path";
184061
184169
  async function installAvds(ctx, specs, deps) {
184062
184170
  const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
184063
184171
  const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
@@ -184067,7 +184175,7 @@ async function installAvds(ctx, specs, deps) {
184067
184175
  if (versionResult.exitCode !== 0) {
184068
184176
  throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
184069
184177
  }
184070
- const licenseFile = join39(deps.androidHome, "licenses", "android-sdk-license");
184178
+ const licenseFile = join41(deps.androidHome, "licenses", "android-sdk-license");
184071
184179
  if (deps.checkExists(licenseFile)) {
184072
184180
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
184073
184181
  } else {
@@ -184082,7 +184190,7 @@ async function installAvds(ctx, specs, deps) {
184082
184190
  for (const spec of specs) {
184083
184191
  if (installedImages.has(spec.systemImage))
184084
184192
  continue;
184085
- const systemImg = join39(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
184193
+ const systemImg = join41(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
184086
184194
  if (deps.checkExists(systemImg)) {
184087
184195
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
184088
184196
  installedImages.add(spec.systemImage);
@@ -184236,6 +184344,7 @@ async function handleInstallBrowsers(ctx, pattern, options) {
184236
184344
  return installBrowsers(ctx, pattern, {
184237
184345
  cwd,
184238
184346
  spawn: defaultSpawn,
184347
+ execPath: process.execPath,
184239
184348
  platform: process.platform,
184240
184349
  browserDeps,
184241
184350
  expandPatterns: (patterns, dir) => expandPatterns(patterns, dir ?? cwd, undefined, fs9),
@@ -184726,4 +184835,4 @@ createProgram({ signals }).parseAsync().catch(() => {
184726
184835
  process.exitCode = 1;
184727
184836
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
184728
184837
 
184729
- //# debugId=F77E4418A62E284264756E2164756E21
184838
+ //# debugId=C0B1B96CCF6C4A9564756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -60,7 +60,6 @@
60
60
  "@clack/prompts": "1.5.1",
61
61
  "@napi-rs/keyring": "1.3.0",
62
62
  "@oxc-node/core": "0.1.0",
63
- "@playwright/test": "1.62.0",
64
63
  "@qawolf/api-contracts": "0.22.0",
65
64
  "@qawolf/emails": "1.1.1",
66
65
  "@qawolf/flow-targets": "1.0.0",