@qawolf/cli 1.7.2 → 1.7.4

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 +461 -298
  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;
@@ -144620,19 +144620,19 @@ async function takeScreenshotBidi(filepath, options) {
144620
144620
  type: imageFormat,
144621
144621
  quality
144622
144622
  };
144623
- const clip2 = options?.clip ? {
144623
+ const clip3 = options?.clip ? {
144624
144624
  type: "box",
144625
144625
  x: options.clip.x,
144626
144626
  y: options.clip.y,
144627
144627
  width: options.clip.width,
144628
144628
  height: options.clip.height
144629
144629
  } : undefined;
144630
- if (clip2) {
144631
- if (typeof clip2.x !== "number" || typeof clip2.y !== "number" || typeof clip2.width !== "number" || typeof clip2.height !== "number") {
144630
+ if (clip3) {
144631
+ if (typeof clip3.x !== "number" || typeof clip3.y !== "number" || typeof clip3.width !== "number" || typeof clip3.height !== "number") {
144632
144632
  throw new Error("Invalid clip, use an object with x, y, width and height properties");
144633
144633
  }
144634
144634
  }
144635
- const { data: data2 } = contextManager.findParentContext(context, tree.contexts) ? await browser.$("html").getElement().then((el) => this.takeElementScreenshot(el.elementId).then((data22) => ({ data: data22 }))) : await this.browsingContextCaptureScreenshot({ context, origin, format, clip: clip2 });
144635
+ const { data: data2 } = contextManager.findParentContext(context, tree.contexts) ? await browser.$("html").getElement().then((el) => this.takeElementScreenshot(el.elementId).then((data22) => ({ data: data22 }))) : await this.browsingContextCaptureScreenshot({ context, origin, format, clip: clip3 });
144636
144636
  return data2;
144637
144637
  }
144638
144638
  async function saveElementScreenshot(filepath) {
@@ -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}`,
@@ -170184,49 +170194,86 @@ function createLoggingSystem(opts) {
170184
170194
  };
170185
170195
  }
170186
170196
 
170197
+ // src/shell/platform/parseErrorBody.ts
170198
+ var defaultMaxLength = 1024;
170199
+ function asRecord(value) {
170200
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
170201
+ }
170202
+ function asString(value) {
170203
+ return typeof value === "string" ? value : undefined;
170204
+ }
170205
+ function extractMessage(parsed) {
170206
+ const root = asRecord(parsed);
170207
+ if (!root)
170208
+ return;
170209
+ const flat = asString(root["error"]);
170210
+ if (flat !== undefined)
170211
+ return flat;
170212
+ const error51 = asRecord(root["error"]);
170213
+ if (!error51)
170214
+ return;
170215
+ const json2 = asRecord(error51["json"]);
170216
+ return asString(json2?.["message"]) ?? asString(error51["message"]);
170217
+ }
170218
+ function parseErrorBody(body, maxLength = defaultMaxLength) {
170219
+ if (!body)
170220
+ return "";
170221
+ let parsed;
170222
+ try {
170223
+ parsed = JSON.parse(body);
170224
+ } catch {
170225
+ return "";
170226
+ }
170227
+ const message = extractMessage(parsed)?.trim();
170228
+ if (!message)
170229
+ return "";
170230
+ return clip(message, maxLength);
170231
+ }
170232
+ function clip(message, maxLength) {
170233
+ if (message.length <= maxLength)
170234
+ return message;
170235
+ return maxLength < 1 ? "" : `${message.slice(0, maxLength - 1)}…`;
170236
+ }
170237
+
170187
170238
  // src/shell/platform/describeErrors.ts
170188
170239
  var m = authMessages.errors;
170240
+ var inlineReasonMaxLength = 200;
170189
170241
  function describeIdentityError(err) {
170190
170242
  if (err.kind === "http") {
170191
170243
  if (err.status === 401 || err.status === 403) {
170192
- return m.identity.invalidOrUnauthorized;
170244
+ return { error: m.identity.invalidOrUnauthorized };
170193
170245
  }
170194
- return m.identity.couldNotVerify(parseErrorBody(err.body), err.status);
170246
+ return {
170247
+ error: m.identity.couldNotVerify(parseErrorBody(err.body, inlineReasonMaxLength), err.status)
170248
+ };
170195
170249
  }
170196
170250
  if (err.kind === "network") {
170197
- return m.identity.couldNotVerifyNetwork(err.cause.message);
170251
+ return { error: m.identity.couldNotVerifyNetwork(err.cause.message) };
170198
170252
  }
170199
- if (err.kind === "timeout")
170200
- return m.identity.timedOut(err.timeoutMs);
170201
- return m.identity.unexpectedFormat;
170202
- }
170203
- function parseErrorBody(body) {
170204
- if (!body)
170205
- return "";
170206
- try {
170207
- const parsed = JSON.parse(body);
170208
- if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
170209
- return parsed.error;
170210
- }
170211
- } catch {}
170212
- return "";
170253
+ if (err.kind === "timeout") {
170254
+ return { error: m.identity.timedOut(err.timeoutMs) };
170255
+ }
170256
+ return { error: m.identity.unexpectedFormat };
170213
170257
  }
170214
170258
  function describeRequestError(err, baseUrl, noun) {
170215
170259
  if (err.kind === "http") {
170260
+ const reason = parseErrorBody(err.body);
170261
+ const body = reason ? { errorBody: reason } : {};
170216
170262
  if (err.status === 401)
170217
- return m.request.rejected401(noun);
170263
+ return { error: m.request.rejected401(noun), ...body };
170218
170264
  if (err.status === 403)
170219
- return m.request.rejected403(noun);
170265
+ return { error: m.request.rejected403(noun), ...body };
170220
170266
  if (err.status === 404)
170221
- return m.request.notFound404(noun);
170222
- return m.request.failedWithStatus(err.status, noun);
170267
+ return { error: m.request.notFound404(noun), ...body };
170268
+ return { error: m.request.failedWithStatus(err.status, noun), ...body };
170223
170269
  }
170224
170270
  if (err.kind === "network") {
170225
- return m.request.networkUnreachable(baseUrl, noun);
170271
+ return { error: m.request.networkUnreachable(baseUrl, noun) };
170226
170272
  }
170227
- if (err.kind === "timeout")
170228
- return m.request.timedOut(err.timeoutMs, noun);
170229
- return m.request.unexpectedResponse(noun);
170273
+ if (err.kind === "timeout") {
170274
+ return { error: m.request.timedOut(err.timeoutMs, noun) };
170275
+ }
170276
+ return { error: m.request.unexpectedResponse(noun) };
170230
170277
  }
170231
170278
  function describeBundleDownloadError(err) {
170232
170279
  if (err.kind === "http") {
@@ -170261,6 +170308,9 @@ import { setTimeout as setTimeout2 } from "node:timers/promises";
170261
170308
  var sleep = (ms) => setTimeout2(ms);
170262
170309
 
170263
170310
  // src/shell/platform/requestWithRetry.ts
170311
+ function failureFields(failure) {
170312
+ return failure.errorBody ? { error: failure.error, errorBody: failure.errorBody } : { error: failure.error };
170313
+ }
170264
170314
  async function requestWithRetry(args) {
170265
170315
  const sleep2 = args.sleep ?? sleep;
170266
170316
  for (let attempt = 0;; attempt++) {
@@ -170270,7 +170320,7 @@ async function requestWithRetry(args) {
170270
170320
  const backoff = args.backoffMs[attempt];
170271
170321
  const retryable = result.error.kind === "network" || result.error.kind === "timeout";
170272
170322
  if (backoff === undefined || !retryable) {
170273
- return { ok: false, error: args.describe(result.error) };
170323
+ return { ok: false, ...args.describe(result.error) };
170274
170324
  }
170275
170325
  await sleep2(backoff);
170276
170326
  }
@@ -171175,15 +171225,15 @@ function createTrpcClient(apiKey, deps) {
171175
171225
  }
171176
171226
  };
171177
171227
  }
171178
- function clip(text) {
171228
+ function clip2(text) {
171179
171229
  return text.length > 200 ? text.slice(0, 200) + "…" : text;
171180
171230
  }
171181
171231
  function summarize(error51) {
171182
171232
  if (error51.kind === "http")
171183
- return `${error51.status} ${clip(error51.body)}`;
171233
+ return `${error51.status} ${clip2(error51.body)}`;
171184
171234
  if (error51.kind === "timeout")
171185
171235
  return `timed out after ${error51.timeoutMs}ms`;
171186
- return clip(error51.cause.message);
171236
+ return clip2(error51.cause.message);
171187
171237
  }
171188
171238
 
171189
171239
  // src/shell/platform/downloadBundle.ts
@@ -174616,7 +174666,7 @@ function startUpdateCheck(deps) {
174616
174666
  // package.json
174617
174667
  var package_default = {
174618
174668
  name: "@qawolf/cli",
174619
- version: "1.7.2",
174669
+ version: "1.7.4",
174620
174670
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
174621
174671
  keywords: [
174622
174672
  "automation",
@@ -174676,7 +174726,6 @@ var package_default = {
174676
174726
  "@clack/prompts": "1.5.1",
174677
174727
  "@napi-rs/keyring": "1.3.0",
174678
174728
  "@oxc-node/core": "0.1.0",
174679
- "@playwright/test": "1.62.0",
174680
174729
  "@qawolf/api-contracts": "0.22.0",
174681
174730
  "@qawolf/emails": "1.1.1",
174682
174731
  "@qawolf/flow-targets": "1.0.0",
@@ -174770,7 +174819,7 @@ function withContext(signals, fn) {
174770
174819
  try {
174771
174820
  const result = await fn(ctx);
174772
174821
  if (result !== undefined) {
174773
- ctx.ui.error(result.error);
174822
+ ctx.ui.error(result.error, result.errorBody);
174774
174823
  process.exitCode = result.exitCode ?? 1;
174775
174824
  }
174776
174825
  } catch (err) {
@@ -174803,7 +174852,7 @@ function withAuthContext(signals, fn, deps = {}) {
174803
174852
  apiKeySource: resolved.source
174804
174853
  });
174805
174854
  if (result !== undefined) {
174806
- ctx.ui.error(result.error);
174855
+ ctx.ui.error(result.error, result.errorBody);
174807
174856
  process.exitCode = result.exitCode ?? 1;
174808
174857
  }
174809
174858
  } catch (err) {
@@ -175047,9 +175096,6 @@ function nodeModulesBinCandidates(envDir, name, platform) {
175047
175096
  function appiumCliCandidates(envDir, platform) {
175048
175097
  return nodeModulesBinCandidates(envDir, "appium", platform);
175049
175098
  }
175050
- function playwrightCliCandidates(envDir, platform) {
175051
- return nodeModulesBinCandidates(envDir, "playwright", platform);
175052
- }
175053
175099
 
175054
175100
  // src/domains/doctor/checks/androidAppium.ts
175055
175101
  function firstLine(result) {
@@ -175412,19 +175458,48 @@ async function checkNpmRegistry(deps) {
175412
175458
  return { name: "npm-registry", status: "pass" };
175413
175459
  }
175414
175460
 
175461
+ // src/core/playwrightCli.ts
175462
+ import { join as join13 } from "node:path";
175463
+ function playwrightCliJsPath(envDir) {
175464
+ return join13(envDir, "node_modules", "playwright", "cli.js");
175465
+ }
175466
+ function playwrightCliInvocation(args) {
175467
+ return {
175468
+ cmd: args.execPath,
175469
+ args: [playwrightCliJsPath(args.envDir), ...args.cliArgs],
175470
+ env: { BUN_BE_BUN: "1" }
175471
+ };
175472
+ }
175473
+
175474
+ // src/generated/dependencyVersions.ts
175475
+ var flowsVersion = "0.1.4";
175476
+ var playwrightVersion = "1.62.0";
175477
+ var emailsVersion = "1.1.1";
175478
+ var testkitVersion = "1.1.1";
175479
+ var appiumVersion = "2.11.3";
175480
+ var appiumXcuitestDriverVersion = "9.10.5";
175481
+ var appiumUiautomator2DriverVersion = "3.7.0";
175482
+ var expectWebdriverioVersion = "5.6.5";
175483
+
175415
175484
  // src/domains/doctor/checks/playwright.ts
175416
175485
  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) {
175486
+ const { envDir } = deps;
175487
+ const cliJsPath = envDir === undefined ? undefined : playwrightCliJsPath(envDir);
175488
+ if (envDir === undefined || cliJsPath === undefined || !deps.checkExists(cliJsPath)) {
175420
175489
  return {
175421
175490
  name: "playwright",
175422
175491
  status: "fail",
175423
- detail: doctorMessages.playwright.notFound(candidates[0])
175492
+ detail: doctorMessages.playwright.notFound(cliJsPath)
175424
175493
  };
175425
175494
  }
175426
- const result = await deps.spawn(cliPath, ["--version"], {
175427
- platform: deps.platform
175495
+ const invocation = playwrightCliInvocation({
175496
+ envDir,
175497
+ execPath: deps.execPath,
175498
+ cliArgs: ["--version"]
175499
+ });
175500
+ const result = await deps.spawn(invocation.cmd, invocation.args, {
175501
+ platform: deps.platform,
175502
+ env: invocation.env
175428
175503
  });
175429
175504
  if (result.exitCode < 0) {
175430
175505
  return {
@@ -175446,6 +175521,14 @@ async function checkPlaywright(deps) {
175446
175521
  detail: doctorMessages.playwright.versionUnparseable
175447
175522
  };
175448
175523
  }
175524
+ if (version2 !== playwrightVersion) {
175525
+ return {
175526
+ name: "playwright",
175527
+ status: "fail",
175528
+ version: version2,
175529
+ detail: doctorMessages.playwright.versionMismatch(version2, playwrightVersion)
175530
+ };
175531
+ }
175449
175532
  return { name: "playwright", status: "pass", version: version2 };
175450
175533
  }
175451
175534
 
@@ -175466,6 +175549,7 @@ async function runChecks(deps) {
175466
175549
  }),
175467
175550
  checkPlaywright({
175468
175551
  spawn: deps.spawn,
175552
+ execPath: deps.execPath,
175469
175553
  envDir: deps.envDir,
175470
175554
  platform: deps.platform,
175471
175555
  checkExists: deps.checkExists
@@ -175525,11 +175609,11 @@ function renderResults(ui, results) {
175525
175609
  }
175526
175610
 
175527
175611
  // src/domains/flows/ensureDeps.ts
175528
- import { dirname as dirname4, join as join13 } from "node:path";
175612
+ import { dirname as dirname4, join as join14 } from "node:path";
175529
175613
  function findEnvDir(flowPath, fs2) {
175530
175614
  let dir = dirname4(flowPath);
175531
175615
  while (true) {
175532
- if (fs2.existsSync(join13(dir, "package.json")))
175616
+ if (fs2.existsSync(join14(dir, "package.json")))
175533
175617
  return dir;
175534
175618
  const parent = dirname4(dir);
175535
175619
  if (parent === dir)
@@ -175555,7 +175639,7 @@ function resolveProjectDirSafe(files, fs2) {
175555
175639
  }
175556
175640
 
175557
175641
  // src/domains/flows/expand.ts
175558
- import { join as join14, resolve as resolve3 } from "node:path";
175642
+ import { join as join15, resolve as resolve3 } from "node:path";
175559
175643
 
175560
175644
  // node_modules/tinyglobby/dist/index.mjs
175561
175645
  import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
@@ -176446,11 +176530,11 @@ function makePeekFlowMeta(fs2) {
176446
176530
  };
176447
176531
  }
176448
176532
  async function resolveGlobRoots(cwd, fs2) {
176449
- const qawolfPath = join14(cwd, ".qawolf");
176533
+ const qawolfPath = join15(cwd, ".qawolf");
176450
176534
  let envDirs = [];
176451
176535
  try {
176452
176536
  const entries = await fs2.readdirWithTypes(qawolfPath);
176453
- envDirs = entries.filter((e) => e.isDirectory()).map((e) => join14(qawolfPath, e.name));
176537
+ envDirs = entries.filter((e) => e.isDirectory()).map((e) => join15(qawolfPath, e.name));
176454
176538
  } catch {}
176455
176539
  return [cwd, ...envDirs];
176456
176540
  }
@@ -176475,21 +176559,11 @@ async function expandPatterns(patterns, cwd, logger, fs2) {
176475
176559
  }
176476
176560
 
176477
176561
  // src/domains/runtimeEnv/clearRuntimeEnv.ts
176478
- import { join as join16 } from "node:path";
176562
+ import { join as join17 } from "node:path";
176479
176563
 
176480
176564
  // src/domains/runtimeEnv/managedEnvDir.ts
176481
176565
  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";
176566
+ import { join as join16, resolve as resolve4 } from "node:path";
176493
176567
 
176494
176568
  // src/domains/runtimeEnv/pinnedPackages.ts
176495
176569
  var pinnedPackages = [
@@ -176522,19 +176596,19 @@ function managedEnvBaseDir() {
176522
176596
  const override = process.env["QAWOLF_RUNTIME_DIR"]?.trim();
176523
176597
  if (override)
176524
176598
  return resolve4(override);
176525
- return join15(getDataDir(), "runtime");
176599
+ return join16(getDataDir(), "runtime");
176526
176600
  }
176527
176601
  function runStagingRoot() {
176528
176602
  return `${managedEnvBaseDir()}-runs`;
176529
176603
  }
176530
176604
  function managedEnvDir() {
176531
- return join15(managedEnvBaseDir(), managedEnvHash());
176605
+ return join16(managedEnvBaseDir(), managedEnvHash());
176532
176606
  }
176533
176607
  async function scaffoldManagedEnv(dir, fs2) {
176534
176608
  await fs2.mkdir(dir, { recursive: true });
176535
176609
  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/
176610
+ await fs2.writeFile(join16(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
176611
+ await fs2.writeFile(join16(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
176538
176612
  `);
176539
176613
  }
176540
176614
 
@@ -176556,14 +176630,14 @@ async function isManagedRuntimeBase(dir, fs2) {
176556
176630
  for (const entry of entries) {
176557
176631
  if (!entry.isDirectory())
176558
176632
  return false;
176559
- if (!await hasRuntimeSentinel(join16(dir, entry.name), fs2))
176633
+ if (!await hasRuntimeSentinel(join17(dir, entry.name), fs2))
176560
176634
  return false;
176561
176635
  }
176562
176636
  return true;
176563
176637
  }
176564
176638
  async function hasRuntimeSentinel(hashDir, fs2) {
176565
176639
  try {
176566
- const pkg = JSON.parse(await fs2.readFile(join16(hashDir, "package.json")));
176640
+ const pkg = JSON.parse(await fs2.readFile(join17(hashDir, "package.json")));
176567
176641
  return pkg.name === runtimeSentinel;
176568
176642
  } catch {
176569
176643
  return false;
@@ -176637,10 +176711,10 @@ function spawnNpmInstall(cwd, spawn2 = spawn) {
176637
176711
  }
176638
176712
 
176639
176713
  // src/domains/runtimeEnv/resolvePinned.ts
176640
- import { join as join17 } from "node:path";
176714
+ import { join as join18 } from "node:path";
176641
176715
  function readInstalledVersion(dir, pkgName, fs2) {
176642
176716
  try {
176643
- const pkgPath = join17(dir, "node_modules", ...pkgName.split("/"), "package.json");
176717
+ const pkgPath = join18(dir, "node_modules", ...pkgName.split("/"), "package.json");
176644
176718
  const raw = JSON.parse(fs2.readFileSync(pkgPath));
176645
176719
  const { version: version2 } = raw;
176646
176720
  return typeof version2 === "string" ? version2 : undefined;
@@ -176649,8 +176723,18 @@ function readInstalledVersion(dir, pkgName, fs2) {
176649
176723
  }
176650
176724
  }
176651
176725
  var shims = [
176652
- { name: "playwright", candidates: playwrightCliCandidates },
176653
- { name: "appium", candidates: appiumCliCandidates }
176726
+ {
176727
+ name: "playwright",
176728
+ display: "node_modules/playwright/cli.js",
176729
+ candidates: (dir, _platform) => [
176730
+ playwrightCliJsPath(dir)
176731
+ ]
176732
+ },
176733
+ {
176734
+ name: "appium",
176735
+ display: "node_modules/.bin/appium",
176736
+ candidates: appiumCliCandidates
176737
+ }
176654
176738
  ];
176655
176739
  function pinnedResolutionFailures(dir, fs2, platform) {
176656
176740
  const failures = [];
@@ -176660,16 +176744,16 @@ function pinnedResolutionFailures(dir, fs2, platform) {
176660
176744
  failures.push({ kind: "package", name, pinned: version2, installed });
176661
176745
  }
176662
176746
  }
176663
- for (const { name, candidates } of shims) {
176747
+ for (const { name, display, candidates } of shims) {
176664
176748
  if (!candidates(dir, platform).some((p2) => fs2.existsSync(p2))) {
176665
- failures.push({ kind: "shim", name });
176749
+ failures.push({ kind: "shim", name, display });
176666
176750
  }
176667
176751
  }
176668
176752
  return failures;
176669
176753
  }
176670
176754
  function describePinnedFailure(failure) {
176671
176755
  if (failure.kind === "shim") {
176672
- return `node_modules/.bin/${failure.name} (missing)`;
176756
+ return `${failure.display} (missing)`;
176673
176757
  }
176674
176758
  return failure.installed === undefined ? `${failure.name} (missing, pinned ${failure.pinned})` : `${failure.name} ${failure.installed} (pinned ${failure.pinned})`;
176675
176759
  }
@@ -176678,10 +176762,10 @@ function allPinnedResolved(dir, fs2, platform) {
176678
176762
  }
176679
176763
 
176680
176764
  // src/domains/runtimeEnv/shimDeps.ts
176681
- import { join as join19 } from "node:path";
176765
+ import { join as join20 } from "node:path";
176682
176766
 
176683
176767
  // src/shell/resolveExport.ts
176684
- import { join as join18 } from "node:path";
176768
+ import { join as join19 } from "node:path";
176685
176769
  function pickPath(entry, preference) {
176686
176770
  if (typeof entry === "string")
176687
176771
  return entry;
@@ -176713,28 +176797,28 @@ function resolveFromEnvDir(envDir, specifier, preference = "esm", fs2 = makeDefa
176713
176797
  const pkgName = parts.slice(0, pkgPartCount).join("/");
176714
176798
  const rest = parts.slice(pkgPartCount);
176715
176799
  const subpath = rest.length > 0 ? "./" + rest.join("/") : ".";
176716
- const pkgDir = join18(envDir, "node_modules", ...pkgName.split("/"));
176800
+ const pkgDir = join19(envDir, "node_modules", ...pkgName.split("/"));
176717
176801
  let pkg;
176718
176802
  try {
176719
- pkg = JSON.parse(fs2.readFileSync(join18(pkgDir, "package.json")));
176803
+ pkg = JSON.parse(fs2.readFileSync(join19(pkgDir, "package.json")));
176720
176804
  } catch (err) {
176721
176805
  if (isNoEntError(err))
176722
- throw new Error(`Package '${pkgName}' not found in ${join18(envDir, "node_modules")}`, { cause: err });
176806
+ throw new Error(`Package '${pkgName}' not found in ${join19(envDir, "node_modules")}`, { cause: err });
176723
176807
  throw err;
176724
176808
  }
176725
176809
  const exportsMap = normalizeExports(pkg.exports);
176726
176810
  const entry = exportsMap?.[subpath];
176727
176811
  const relative3 = pickPath(entry, preference) ?? (subpath === "." ? pkg.module ?? pkg.main : undefined);
176728
176812
  if (!relative3) {
176729
- throw new Error(`No entry for "${subpath}" in exports of ${join18(pkgDir, "package.json")}`);
176813
+ throw new Error(`No entry for "${subpath}" in exports of ${join19(pkgDir, "package.json")}`);
176730
176814
  }
176731
- return join18(pkgDir, relative3);
176815
+ return join19(pkgDir, relative3);
176732
176816
  }
176733
176817
 
176734
176818
  // src/domains/runtimeEnv/shimDeps.ts
176735
176819
  function readShimMarker(shimDir, fs2) {
176736
176820
  try {
176737
- const pkg = JSON.parse(fs2.readFileSync(join19(shimDir, "package.json")));
176821
+ const pkg = JSON.parse(fs2.readFileSync(join20(shimDir, "package.json")));
176738
176822
  if (pkg._qawolf_format) {
176739
176823
  return {
176740
176824
  _qawolf_version: pkg._qawolf_version ?? "",
@@ -176750,11 +176834,11 @@ function resolveBunBuild(bunBuild) {
176750
176834
  return bunBuild === false ? undefined : { build: bunBuild };
176751
176835
  }
176752
176836
  async function removeStaleShims(flowsDir, flowsDeps, fs2) {
176753
- const shimsDir = join19(flowsDir, "node_modules");
176837
+ const shimsDir = join20(flowsDir, "node_modules");
176754
176838
  if (!fs2.existsSync(shimsDir))
176755
176839
  return;
176756
176840
  for (const dep of flowsDeps) {
176757
- const shimDepDir = join19(shimsDir, ...dep.split("/"));
176841
+ const shimDepDir = join20(shimsDir, ...dep.split("/"));
176758
176842
  if (readShimMarker(shimDepDir, fs2)) {
176759
176843
  await fs2.rm(shimDepDir, { recursive: true, force: true });
176760
176844
  }
@@ -176763,17 +176847,17 @@ async function removeStaleShims(flowsDir, flowsDeps, fs2) {
176763
176847
  async function buildDepShim(args) {
176764
176848
  const { envDir, flowsDir, dep, fs: fs2, build: build2 } = args;
176765
176849
  const depParts = dep.split("/");
176766
- const depDir = join19(envDir, "node_modules", ...depParts);
176850
+ const depDir = join20(envDir, "node_modules", ...depParts);
176767
176851
  if (!fs2.existsSync(depDir))
176768
176852
  return;
176769
176853
  let depVersion;
176770
176854
  try {
176771
- const pkg = JSON.parse(fs2.readFileSync(join19(depDir, "package.json")));
176855
+ const pkg = JSON.parse(fs2.readFileSync(join20(depDir, "package.json")));
176772
176856
  depVersion = pkg.version ?? "unknown";
176773
176857
  } catch {
176774
176858
  depVersion = "unknown";
176775
176859
  }
176776
- const shimDir = join19(flowsDir, "node_modules", ...depParts);
176860
+ const shimDir = join20(flowsDir, "node_modules", ...depParts);
176777
176861
  if (fs2.existsSync(shimDir)) {
176778
176862
  const marker = readShimMarker(shimDir, fs2);
176779
176863
  if (!marker)
@@ -176800,20 +176884,20 @@ async function buildDepShim(args) {
176800
176884
  }
176801
176885
  const shimCode = await output.text();
176802
176886
  await fs2.mkdir(shimDir, { recursive: true });
176803
- await fs2.writeFile(join19(shimDir, "package.json"), JSON.stringify({
176887
+ await fs2.writeFile(join20(shimDir, "package.json"), JSON.stringify({
176804
176888
  name: dep,
176805
176889
  _qawolf_version: depVersion,
176806
176890
  _qawolf_format: "bun-build-v1"
176807
176891
  }));
176808
- await fs2.writeFile(join19(shimDir, "index.js"), shimCode);
176892
+ await fs2.writeFile(join20(shimDir, "index.js"), shimCode);
176809
176893
  }
176810
176894
  async function shimFlowsDeps(envDir, fs2 = makeDefaultFs(), bunBuild) {
176811
- const flowsDir = join19(envDir, "node_modules", "@qawolf", "flows");
176895
+ const flowsDir = join20(envDir, "node_modules", "@qawolf", "flows");
176812
176896
  if (!fs2.existsSync(flowsDir))
176813
176897
  return;
176814
176898
  let flowsDeps;
176815
176899
  try {
176816
- const flowsPkg = JSON.parse(fs2.readFileSync(join19(flowsDir, "package.json")));
176900
+ const flowsPkg = JSON.parse(fs2.readFileSync(join20(flowsDir, "package.json")));
176817
176901
  flowsDeps = Object.keys(flowsPkg.dependencies ?? {});
176818
176902
  } catch {
176819
176903
  return;
@@ -176937,6 +177021,7 @@ async function handleDoctor(ctx, opts) {
176937
177021
  apiKey: resolved?.key,
176938
177022
  fetch: globalThis.fetch,
176939
177023
  spawn: defaultSpawn,
177024
+ execPath: process.execPath,
176940
177025
  apiBaseUrl: ctx.apiBaseUrl,
176941
177026
  enginesNode: package_default.engines.node,
176942
177027
  processVersion: process.version,
@@ -177044,9 +177129,8 @@ async function flowsListRemote(ctx, pattern, options) {
177044
177129
  environmentId: options.env,
177045
177130
  includeDrafts: options.includeDrafts
177046
177131
  });
177047
- if (!result.ok) {
177048
- return { error: result.error };
177049
- }
177132
+ if (!result.ok)
177133
+ return failureFields(result);
177050
177134
  const matches = pattern ? import_picomatch2.default(pattern) : undefined;
177051
177135
  const items = result.value.flows.filter((f2) => !matches || matches(f2.path)).map((f2) => ({
177052
177136
  flowId: f2.flowId,
@@ -177079,11 +177163,11 @@ async function flowsListRemote(ctx, pattern, options) {
177079
177163
  }
177080
177164
 
177081
177165
  // src/domains/flows/pull/handler.ts
177082
- import { dirname as dirname7, join as join24, resolve as resolve8 } from "node:path";
177166
+ import { dirname as dirname7, join as join25, resolve as resolve8 } from "node:path";
177083
177167
 
177084
177168
  // src/shell/manifest/io.ts
177085
177169
  import { createHash as createHash2 } from "node:crypto";
177086
- import { join as join20 } from "node:path";
177170
+ import { join as join21 } from "node:path";
177087
177171
  var manifestFilename = ".manifest.json";
177088
177172
  var flowEntrySchema = exports_external.object({
177089
177173
  path: exports_external.string(),
@@ -177102,7 +177186,7 @@ var manifestSchema = exports_external.object({
177102
177186
  async function readManifest(envDir, fs2 = makeDefaultFs()) {
177103
177187
  let raw;
177104
177188
  try {
177105
- raw = await fs2.readFile(join20(envDir, manifestFilename));
177189
+ raw = await fs2.readFile(join21(envDir, manifestFilename));
177106
177190
  } catch (err) {
177107
177191
  if (isNoEntError(err))
177108
177192
  return "missing";
@@ -177130,7 +177214,7 @@ async function readManifest(envDir, fs2 = makeDefaultFs()) {
177130
177214
  }
177131
177215
  async function writeManifest(envDir, manifest, fs2 = makeDefaultFs()) {
177132
177216
  const body = JSON.stringify(manifest, undefined, 2);
177133
- await fs2.writeFile(join20(envDir, manifestFilename), `${body}
177217
+ await fs2.writeFile(join21(envDir, manifestFilename), `${body}
177134
177218
  `);
177135
177219
  }
177136
177220
  function hashFile(absPath, fs2 = makeDefaultFs()) {
@@ -177265,7 +177349,7 @@ async function checkSafety(args) {
177265
177349
  }
177266
177350
 
177267
177351
  // src/domains/flows/pull/bundle.ts
177268
- import { join as join21, relative as relative4 } from "node:path";
177352
+ import { join as join22, relative as relative4 } from "node:path";
177269
177353
  async function flattenSingleWrapper(dir, fs2 = makeDefaultFs()) {
177270
177354
  const entries = await fs2.readdir(dir);
177271
177355
  if (entries.length !== 1)
@@ -177273,12 +177357,12 @@ async function flattenSingleWrapper(dir, fs2 = makeDefaultFs()) {
177273
177357
  const innerName = entries[0];
177274
177358
  if (!innerName)
177275
177359
  return;
177276
- const inner = join21(dir, innerName);
177360
+ const inner = join22(dir, innerName);
177277
177361
  const innerStat = await fs2.stat(inner);
177278
177362
  if (!innerStat.isDirectory())
177279
177363
  return;
177280
177364
  for (const e of await fs2.readdir(inner)) {
177281
- await fs2.rename(join21(inner, e), join21(dir, e));
177365
+ await fs2.rename(join22(inner, e), join22(dir, e));
177282
177366
  }
177283
177367
  const leftovers = await fs2.readdir(inner);
177284
177368
  if (leftovers.length > 0) {
@@ -177297,7 +177381,7 @@ async function buildManifest(args, fs2 = makeDefaultFs()) {
177297
177381
  const flowPaths = await walkForFlows(args.bundleDir, fs2);
177298
177382
  const flows = await Promise.all(flowPaths.map(async (rel) => ({
177299
177383
  path: rel,
177300
- contentHash: await hashFile(join21(args.bundleDir, rel), fs2)
177384
+ contentHash: await hashFile(join22(args.bundleDir, rel), fs2)
177301
177385
  })));
177302
177386
  return {
177303
177387
  envId: args.envId,
@@ -177318,7 +177402,7 @@ async function walkForFlows(root, fs2) {
177318
177402
  async function walk(current, root, out, fs2) {
177319
177403
  const entries = await fs2.readdirWithTypes(current);
177320
177404
  for (const e of entries) {
177321
- const abs = join21(current, e.name);
177405
+ const abs = join22(current, e.name);
177322
177406
  if (e.isDirectory()) {
177323
177407
  await walk(abs, root, out, fs2);
177324
177408
  } else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
@@ -177331,11 +177415,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs2 = makeDefaultFs()) {
177331
177415
  const sample = flowPaths[0];
177332
177416
  if (!sample)
177333
177417
  return;
177334
- return (await fs2.stat(join21(bundleDir, sample))).mtime.toISOString();
177418
+ return (await fs2.stat(join22(bundleDir, sample))).mtime.toISOString();
177335
177419
  }
177336
177420
 
177337
177421
  // src/domains/flows/pull/applyTeamStorageRewrite.ts
177338
- import { join as join22, relative as relative5 } from "node:path";
177422
+ import { join as join23, relative as relative5 } from "node:path";
177339
177423
 
177340
177424
  // src/domains/flows/pull/rewriteTeamStorage.ts
177341
177425
  var runnerMountPrefix = "/home/wolf/team-storage/";
@@ -177371,7 +177455,7 @@ function isFlowFile(name) {
177371
177455
  async function walk2(dir, out, fs2) {
177372
177456
  const entries = await fs2.readdirWithTypes(dir);
177373
177457
  for (const e of entries) {
177374
- const abs = join22(dir, e.name);
177458
+ const abs = join23(dir, e.name);
177375
177459
  if (e.isDirectory()) {
177376
177460
  await walk2(abs, out, fs2);
177377
177461
  } else if (e.isFile() && isSourceFile(e.name)) {
@@ -177399,7 +177483,7 @@ async function applyTeamStorageRewrite(rootDir, fs2 = makeDefaultFs()) {
177399
177483
  }
177400
177484
 
177401
177485
  // src/domains/flows/pull/envVars.ts
177402
- import { join as join23 } from "node:path";
177486
+ import { join as join24 } from "node:path";
177403
177487
 
177404
177488
  // src/core/dotenv.ts
177405
177489
  var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -177464,7 +177548,7 @@ function unquote(escaped) {
177464
177548
  async function writeEnvFile(envDir, vars, fs2 = makeDefaultFs()) {
177465
177549
  if (Object.keys(vars).length === 0)
177466
177550
  return;
177467
- await fs2.writeFile(join23(envDir, ".env"), serializeDotenv(vars), {
177551
+ await fs2.writeFile(join24(envDir, ".env"), serializeDotenv(vars), {
177468
177552
  mode: 384
177469
177553
  });
177470
177554
  }
@@ -180837,8 +180921,8 @@ async function handleFlowsPull(ctx, opts, deps) {
180837
180921
  ctx.ui.error(validation.error);
180838
180922
  return { error: validation.error };
180839
180923
  }
180840
- const destAbs = resolve8(opts.out ?? join24(".qawolf", opts.env));
180841
- const assetsAbs = join24(dirname7(destAbs), "assets");
180924
+ const destAbs = resolve8(opts.out ?? join25(".qawolf", opts.env));
180925
+ const assetsAbs = join25(dirname7(destAbs), "assets");
180842
180926
  await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
180843
180927
  const yes = opts.yes ?? false;
180844
180928
  let archive;
@@ -180904,7 +180988,7 @@ async function handleFlowsPull(ctx, opts, deps) {
180904
180988
  assetDownloadedCount: assetResult.downloadedCount,
180905
180989
  assetReusedCount: assetResult.reusedCount,
180906
180990
  assetSkippedCount: assetResult.skippedCount,
180907
- manifestPath: join24(result.envDir, manifestFilename)
180991
+ manifestPath: join25(result.envDir, manifestFilename)
180908
180992
  }, "");
180909
180993
  }
180910
180994
  } finally {
@@ -180918,7 +181002,7 @@ var findContract = publicContractsV1.environment.find;
180918
181002
  async function pickEnvironment(deps) {
180919
181003
  const fetched = await fetchAllEnvironments(deps.platformClient);
180920
181004
  if (!fetched.ok)
180921
- return { kind: "error", error: fetched.error };
181005
+ return { ...failureFields(fetched), kind: "error" };
180922
181006
  const environments = fetched.environments;
180923
181007
  if (environments.length === 0) {
180924
181008
  return { kind: "error", error: environmentsMessages.noEnvironments };
@@ -181011,6 +181095,7 @@ async function resolveRef2(deps, ref) {
181011
181095
  });
181012
181096
  if (!result.ok) {
181013
181097
  return {
181098
+ ...failureFields(result),
181014
181099
  kind: "error",
181015
181100
  error: environmentsMessages.couldNotResolve(ref, result.error)
181016
181101
  };
@@ -181044,7 +181129,7 @@ function withResolvedEnv(signals, args, fn2) {
181044
181129
  return;
181045
181130
  }
181046
181131
  if (outcome.kind === "error") {
181047
- return { error: outcome.error };
181132
+ return failureFields(outcome);
181048
181133
  }
181049
181134
  return fn2(ctx, outcome.env);
181050
181135
  })(opts, command);
@@ -181090,6 +181175,72 @@ function buildPatternArgs(pattern) {
181090
181175
  return pattern ? [pattern] : [];
181091
181176
  }
181092
181177
 
181178
+ // src/shell/reporter/createCompositeReporter.ts
181179
+ function callEach(reporters, pick2, event) {
181180
+ for (const r2 of reporters) {
181181
+ try {
181182
+ pick2(r2)?.(event);
181183
+ } catch (err) {
181184
+ process.stderr.write(`[reporter error] ${String(err)}
181185
+ `);
181186
+ }
181187
+ }
181188
+ }
181189
+ function createCompositeReporter(reporters) {
181190
+ return {
181191
+ onFlowStart: (e) => callEach(reporters, (r2) => r2.onFlowStart, e),
181192
+ onFlowPass: (e) => callEach(reporters, (r2) => r2.onFlowPass, e),
181193
+ onFlowFail: (e) => callEach(reporters, (r2) => r2.onFlowFail, e),
181194
+ onTestStart: (e) => callEach(reporters, (r2) => r2.onTestStart, e),
181195
+ onTestResult: (e) => callEach(reporters, (r2) => r2.onTestResult, e),
181196
+ onScreenshot: (e) => callEach(reporters, (r2) => r2.onScreenshot, e),
181197
+ onRunComplete: (e) => callEach(reporters, (r2) => r2.onRunComplete, e)
181198
+ };
181199
+ }
181200
+
181201
+ // src/shell/reporter/formatErrorWithCause.ts
181202
+ function filterStack(stack) {
181203
+ const cwd = process.cwd();
181204
+ return stack.split(`
181205
+ `).filter((line) => {
181206
+ if (!/^\s+at /.test(line))
181207
+ return true;
181208
+ return !line.includes("node_modules") && !line.includes("dist/cli.js");
181209
+ }).map((line) => {
181210
+ if (!/^\s+at /.test(line))
181211
+ return line;
181212
+ return line.replace(`file://${cwd}/`, "").replace(`${cwd}/`, "");
181213
+ }).join(`
181214
+ `);
181215
+ }
181216
+ function renderCause(cause) {
181217
+ if (cause instanceof Error)
181218
+ return filterStack(cause.stack ?? cause.message);
181219
+ if (typeof cause === "object" && cause !== null) {
181220
+ const obj = cause;
181221
+ if (typeof obj["message"] === "string")
181222
+ return obj["message"];
181223
+ try {
181224
+ return JSON.stringify(cause);
181225
+ } catch {
181226
+ return String(cause);
181227
+ }
181228
+ }
181229
+ return String(cause);
181230
+ }
181231
+ function formatErrorWithCause(err) {
181232
+ const parts = [String(err)];
181233
+ let cause = err.cause;
181234
+ while (cause !== undefined && cause !== null) {
181235
+ parts.push(`Caused by: ${renderCause(cause)}`);
181236
+ if (!(cause instanceof Error))
181237
+ break;
181238
+ cause = cause.cause;
181239
+ }
181240
+ return parts.join(`
181241
+ `);
181242
+ }
181243
+
181093
181244
  // src/domains/runner/runAndroidFlowUtils.ts
181094
181245
  import { parseExecutionTarget as parseExecutionTarget3 } from "@qawolf/flow-targets";
181095
181246
 
@@ -181437,9 +181588,16 @@ async function flowsRun(ctx, files, flags, deps) {
181437
181588
  return { error: runnerMessages.androidWorkersUnsupported, exitCode: 2 };
181438
181589
  }
181439
181590
  const { webOptions, androidOptions } = buildRunOptions(flags);
181591
+ const failureDetails = [];
181592
+ const reporter = ctx.outputMode === "json" ? createCompositeReporter([
181593
+ deps.reporter,
181594
+ {
181595
+ onFlowFail: ({ err }) => failureDetails.push(formatErrorWithCause(err))
181596
+ }
181597
+ ]) : deps.reporter;
181440
181598
  const result = await executeFlows({
181441
181599
  ctx,
181442
- deps,
181600
+ deps: { ...deps, reporter },
181443
181601
  flags,
181444
181602
  flows,
181445
181603
  webFlows,
@@ -181467,7 +181625,13 @@ async function flowsRun(ctx, files, flags, deps) {
181467
181625
  deps.reporter.onRunComplete?.({ summary });
181468
181626
  ctx.ui.gap();
181469
181627
  if (counts.flowsFailed > 0) {
181470
- return { error: runnerMessages.flowsFailed(counts.flowsFailed) };
181628
+ const error51 = runnerMessages.flowsFailed(counts.flowsFailed);
181629
+ if (ctx.outputMode === "json" && failureDetails.length > 0) {
181630
+ return { error: error51, errorBody: failureDetails.join(`
181631
+
181632
+ `) };
181633
+ }
181634
+ return { error: error51 };
181471
181635
  }
181472
181636
  }
181473
181637
 
@@ -181518,15 +181682,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
181518
181682
 
181519
181683
  // src/domains/runtimeEnv/prepareRunDir.ts
181520
181684
  import { createHash as createHash4 } from "node:crypto";
181521
- import { join as join31, resolve as resolve9 } from "node:path";
181685
+ import { join as join32, resolve as resolve9 } from "node:path";
181522
181686
 
181523
181687
  // src/domains/runtimeEnv/execSubpathImports.ts
181524
- import { join as join25 } from "node:path";
181688
+ import { join as join26 } from "node:path";
181525
181689
  var flowSubpathImports = { "#playwright": "playwright" };
181526
181690
  var packageJsonSchema = exports_external.looseObject({ imports: exports_external.record(exports_external.string(), exports_external.unknown()).catch({}) }).catch({ imports: {} });
181527
181691
  async function writeExecSubpathImports(args) {
181528
181692
  const { execDir, fs: fs3 } = args;
181529
- const pkgPath = join25(execDir, "package.json");
181693
+ const pkgPath = join26(execDir, "package.json");
181530
181694
  const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs3));
181531
181695
  const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
181532
181696
  await fs3.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
@@ -181548,10 +181712,10 @@ async function readPackageJson(pkgPath, fs3) {
181548
181712
  }
181549
181713
 
181550
181714
  // src/domains/runtimeEnv/innerHop.ts
181551
- import { join as join27 } from "node:path";
181715
+ import { join as join28 } from "node:path";
181552
181716
 
181553
181717
  // src/domains/runtimeEnv/linkPinnedPackages.ts
181554
- import { dirname as dirname8, join as join26 } from "node:path";
181718
+ import { dirname as dirname8, join as join27 } from "node:path";
181555
181719
 
181556
181720
  // src/domains/runtimeEnv/symlinkDir.ts
181557
181721
  import { symlink } from "node:fs/promises";
@@ -181563,16 +181727,16 @@ async function createDirSymlink(source, target) {
181563
181727
  // src/domains/runtimeEnv/linkPinnedPackages.ts
181564
181728
  async function linkPinnedPackages(args) {
181565
181729
  const { depsRoot, nodeModulesDir, fs: fs3 } = args;
181566
- const managedModules = join26(depsRoot, "node_modules");
181730
+ const managedModules = join27(depsRoot, "node_modules");
181567
181731
  await fs3.mkdir(nodeModulesDir, { recursive: true });
181568
181732
  for (const { name } of pinnedPackages) {
181569
181733
  const segments = name.split("/");
181570
- const target = join26(nodeModulesDir, ...segments);
181734
+ const target = join27(nodeModulesDir, ...segments);
181571
181735
  if (segments.length > 1) {
181572
181736
  await fs3.mkdir(dirname8(target), { recursive: true });
181573
181737
  }
181574
181738
  await fs3.rm(target, { recursive: true, force: true });
181575
- await createDirSymlink(join26(managedModules, ...segments), target);
181739
+ await createDirSymlink(join27(managedModules, ...segments), target);
181576
181740
  }
181577
181741
  }
181578
181742
 
@@ -181581,14 +181745,14 @@ async function populateInnerHop(args) {
181581
181745
  const { depsRoot, execDir, fs: fs3 } = args;
181582
181746
  await linkPinnedPackages({
181583
181747
  depsRoot,
181584
- nodeModulesDir: join27(execDir, "node_modules"),
181748
+ nodeModulesDir: join28(execDir, "node_modules"),
181585
181749
  fs: fs3
181586
181750
  });
181587
181751
  }
181588
181752
 
181589
181753
  // src/domains/runtimeEnv/outerHop.ts
181590
181754
  import { lstat } from "node:fs/promises";
181591
- import { dirname as dirname9, join as join28 } from "node:path";
181755
+ import { dirname as dirname9, join as join29 } from "node:path";
181592
181756
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
181593
181757
  async function populateOuterHop(args) {
181594
181758
  const { projectDir, runDir, fs: fs3 } = args;
@@ -181598,7 +181762,7 @@ async function populateOuterHop(args) {
181598
181762
  const depNames = Object.keys(installableDeps);
181599
181763
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
181600
181764
  if (satisfying !== undefined) {
181601
- await createDirSymlink(satisfying, join28(runDir, "node_modules"));
181765
+ await createDirSymlink(satisfying, join29(runDir, "node_modules"));
181602
181766
  return { mode: "symlink", nodeModulesDir: satisfying };
181603
181767
  }
181604
181768
  if (depNames.length === 0)
@@ -181607,7 +181771,7 @@ async function populateOuterHop(args) {
181607
181771
  await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
181608
181772
  await linkPinnedPackages({
181609
181773
  depsRoot: args.depsRoot,
181610
- nodeModulesDir: join28(runDir, "node_modules"),
181774
+ nodeModulesDir: join29(runDir, "node_modules"),
181611
181775
  fs: fs3
181612
181776
  });
181613
181777
  return { mode: "install", depCount: depNames.length, rejected };
@@ -181616,10 +181780,10 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
181616
181780
  const rejected = [];
181617
181781
  let dir = startDir;
181618
181782
  while (true) {
181619
- const candidate = join28(dir, "node_modules");
181783
+ const candidate = join29(dir, "node_modules");
181620
181784
  const stats = await lstatSafe(candidate);
181621
181785
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
181622
- const missing = depNames.filter((name) => !fs3.existsSync(join28(candidate, name, "package.json")));
181786
+ const missing = depNames.filter((name) => !fs3.existsSync(join29(candidate, name, "package.json")));
181623
181787
  if (missing.length === 0)
181624
181788
  return { satisfying: candidate, rejected };
181625
181789
  rejected.push({ dir: candidate, missing });
@@ -181640,7 +181804,7 @@ async function lstatSafe(path3) {
181640
181804
  async function readInstallableDeps(projectDir, fs3) {
181641
181805
  let content;
181642
181806
  try {
181643
- content = await fs3.readFile(join28(projectDir, "package.json"));
181807
+ content = await fs3.readFile(join29(projectDir, "package.json"));
181644
181808
  } catch {
181645
181809
  return {};
181646
181810
  }
@@ -181658,8 +181822,8 @@ async function readInstallableDeps(projectDir, fs3) {
181658
181822
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
181659
181823
  }
181660
181824
  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/
181825
+ await fs3.writeFile(join29(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
181826
+ await fs3.writeFile(join29(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
181663
181827
  `);
181664
181828
  const result = await install(runDir);
181665
181829
  if (result.exitCode !== 0) {
@@ -181669,14 +181833,14 @@ async function installOuterDeps(runDir, deps, fs3, install) {
181669
181833
 
181670
181834
  // src/domains/runtimeEnv/stageFlowFiles.ts
181671
181835
  import { createHash as createHash3 } from "node:crypto";
181672
- import { basename as basename4, dirname as dirname10, join as join30, sep as sep4 } from "node:path";
181836
+ import { basename as basename4, dirname as dirname10, join as join31, sep as sep4 } from "node:path";
181673
181837
 
181674
181838
  // src/shell/copyDir.ts
181675
181839
  import { cp, readdir as readdir2 } from "node:fs/promises";
181676
- import { basename as basename3, join as join29 } from "node:path";
181840
+ import { basename as basename3, join as join30 } from "node:path";
181677
181841
  async function copyDirExcluding(source, destination, excludedNames) {
181678
181842
  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), {
181843
+ await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join30(source, entry.name), join30(destination, entry.name), {
181680
181844
  recursive: true,
181681
181845
  filter: (path3) => !excludedNames.has(basename3(path3))
181682
181846
  })));
@@ -181693,14 +181857,14 @@ async function stageFlowFiles(args) {
181693
181857
  if (files.length > 1) {
181694
181858
  return Promise.all(files.map((f3) => stageOneFile(f3, execDir, fs3)));
181695
181859
  }
181696
- await Promise.all(files.map((f3) => fs3.copyFile(f3, join30(execDir, basename4(f3)))));
181697
- return files.map((f3) => join30(execDir, basename4(f3)));
181860
+ await Promise.all(files.map((f3) => fs3.copyFile(f3, join31(execDir, basename4(f3)))));
181861
+ return files.map((f3) => join31(execDir, basename4(f3)));
181698
181862
  }
181699
181863
  async function stageOneFile(file2, execDir, fs3) {
181700
181864
  const dirHash = createHash3("sha256").update(dirname10(file2)).digest("hex").slice(0, 8);
181701
- const subDir = join30(execDir, dirHash);
181865
+ const subDir = join31(execDir, dirHash);
181702
181866
  await fs3.mkdir(subDir, { recursive: true });
181703
- const dest = join30(subDir, basename4(file2));
181867
+ const dest = join31(subDir, basename4(file2));
181704
181868
  await fs3.copyFile(file2, dest);
181705
181869
  return dest;
181706
181870
  }
@@ -181708,7 +181872,7 @@ function remapPath(file2, projectDir, execDir) {
181708
181872
  if (file2 === projectDir)
181709
181873
  return execDir;
181710
181874
  if (file2.startsWith(projectDir + sep4)) {
181711
- return join30(execDir, file2.slice(projectDir.length + 1));
181875
+ return join31(execDir, file2.slice(projectDir.length + 1));
181712
181876
  }
181713
181877
  return;
181714
181878
  }
@@ -181718,10 +181882,10 @@ async function prepareRunDir(args) {
181718
181882
  const { files, projectDir, depsRoot, runRoot } = args;
181719
181883
  const fs3 = args.fs ?? makeDefaultFs();
181720
181884
  const runId = buildRunId(projectDir, files);
181721
- const runDir = join31(runRoot, runId);
181885
+ const runDir = join32(runRoot, runId);
181722
181886
  await fs3.rm(runDir, { recursive: true, force: true });
181723
181887
  await fs3.mkdir(runDir, { recursive: true });
181724
- const execDir = join31(runDir, "exec");
181888
+ const execDir = join32(runDir, "exec");
181725
181889
  await fs3.mkdir(execDir, { recursive: true });
181726
181890
  await populateInnerHop({ depsRoot, execDir, fs: fs3 });
181727
181891
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs3 });
@@ -181752,17 +181916,17 @@ function buildRunId(projectDir, files) {
181752
181916
  }
181753
181917
 
181754
181918
  // src/shell/testkit.ts
181755
- import { join as join32 } from "node:path";
181919
+ import { join as join33 } from "node:path";
181756
181920
  function notAvailableLocally(name) {
181757
181921
  throw new Error(runnerMessages.notAvailableLocally(name));
181758
181922
  }
181759
181923
  async function loadSdkDeps(cwd) {
181760
- const testkitDir = join32(cwd, "node_modules", "@qawolf", "testkit");
181924
+ const testkitDir = join33(cwd, "node_modules", "@qawolf", "testkit");
181761
181925
  try {
181762
181926
  const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
181763
181927
  if (typeof clientMod.createTestkitClient !== "function")
181764
181928
  throw new Error("createTestkitClient is not a function");
181765
- const scopeMod = await importFromPath(join32(testkitDir, "dist", "clientScope.js"));
181929
+ const scopeMod = await importFromPath(join33(testkitDir, "dist", "clientScope.js"));
181766
181930
  if (typeof scopeMod.configureTestkitClient !== "function")
181767
181931
  throw new Error("configureTestkitClient is not a function");
181768
181932
  return {
@@ -181820,9 +181984,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
181820
181984
  }
181821
181985
 
181822
181986
  // src/commands/flows/envVarDeps.ts
181823
- import { join as join33 } from "node:path";
181987
+ import { join as join34 } from "node:path";
181824
181988
  function envFilePath(envDir) {
181825
- return join33(envDir, ".env");
181989
+ return join34(envDir, ".env");
181826
181990
  }
181827
181991
  async function readEnv(envDir, fs3) {
181828
181992
  try {
@@ -182319,17 +182483,21 @@ async function findFlowStamp(flowAbsPath) {
182319
182483
 
182320
182484
  // src/domains/install/browsers.ts
182321
182485
  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));
182486
+ const cliJsPath = playwrightCliJsPath(deps.envDir);
182487
+ if (!deps.checkExists(cliJsPath)) {
182488
+ throw new Error(installMessages.playwrightNotFound(cliJsPath));
182326
182489
  }
182327
182490
  await ctx.ui.withProgress(browsers.map((browser) => ({
182328
182491
  message: installMessages.installingBrowser(browser),
182329
182492
  task: async () => {
182330
- const args = buildArgs(browser, deps.platform, deps.browserDeps);
182331
- const result = await deps.spawn(cliPath, args, {
182332
- platform: deps.platform
182493
+ const invocation = playwrightCliInvocation({
182494
+ envDir: deps.envDir,
182495
+ execPath: deps.execPath,
182496
+ cliArgs: buildArgs(browser, deps.platform, deps.browserDeps)
182497
+ });
182498
+ const result = await deps.spawn(invocation.cmd, invocation.args, {
182499
+ platform: deps.platform,
182500
+ env: invocation.env
182333
182501
  });
182334
182502
  if (result.exitCode !== 0) {
182335
182503
  throw new Error(formatError2(browser, result));
@@ -182347,6 +182515,7 @@ async function installBrowsers(ctx, pattern, deps) {
182347
182515
  }
182348
182516
  await installBrowserList(ctx, browsers, {
182349
182517
  spawn: deps.spawn,
182518
+ execPath: deps.execPath,
182350
182519
  platform: deps.platform,
182351
182520
  browserDeps: deps.browserDeps,
182352
182521
  envDir: await deps.resolveDepsRoot(files),
@@ -183221,14 +183390,14 @@ async function runWebFlow({
183221
183390
 
183222
183391
  // src/shell/embeddedWorkerCli.ts
183223
183392
  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";
183393
+ import { basename as basename6, join as join35 } from "node:path";
183225
183394
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
183226
183395
  function extractEmbeddedWorkerCli() {
183227
183396
  const assetPath = process.env[embeddedCliPathEnv];
183228
183397
  if (assetPath === undefined || assetPath === "")
183229
183398
  return;
183230
- const dir = join34(getDataDir(), "worker");
183231
- const dest = join34(dir, basename6(assetPath));
183399
+ const dir = join35(getDataDir(), "worker");
183400
+ const dest = join35(dir, basename6(assetPath));
183232
183401
  if (existsSync3(dest))
183233
183402
  return dest;
183234
183403
  mkdirSync3(dir, { recursive: true });
@@ -183387,51 +183556,6 @@ import { dirname as dirname12 } from "node:path";
183387
183556
 
183388
183557
  // src/shell/reporter/createConsoleReporter.ts
183389
183558
  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
183559
  function fmtDuration(ms2) {
183436
183560
  return `(${(ms2 / 1000).toFixed(2)}s)`;
183437
183561
  }
@@ -183510,30 +183634,8 @@ ${styleText3("dim", "─".repeat(width))}
183510
183634
  }
183511
183635
  };
183512
183636
  }
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
183637
  // src/shell/reporter/createJUnitReporter.ts
183536
- import { join as join35 } from "node:path";
183638
+ import { join as join36 } from "node:path";
183537
183639
 
183538
183640
  // src/shell/reporter/junitXml.ts
183539
183641
  function escapeXml(str) {
@@ -183572,7 +183674,7 @@ ${suites}`;
183572
183674
 
183573
183675
  // src/shell/reporter/createJUnitReporter.ts
183574
183676
  function resolveJUnitOutputPath(junit, outputDir) {
183575
- return typeof junit === "string" && junit.trim() !== "" ? junit : join35(outputDir, "junit-report.xml");
183677
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join36(outputDir, "junit-report.xml");
183576
183678
  }
183577
183679
  function createJUnitReporter(deps) {
183578
183680
  const flows = [];
@@ -183634,6 +183736,7 @@ function buildFlowsRunDeps(args) {
183634
183736
  peekFlowMeta: makePeekFlowMeta(ctx.fs),
183635
183737
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
183636
183738
  spawn: defaultSpawn,
183739
+ execPath: process.execPath,
183637
183740
  platform: process.platform,
183638
183741
  browserDeps: flags.browserDeps,
183639
183742
  envDir: resolvedDir,
@@ -183661,11 +183764,11 @@ function buildFlowsRunDeps(args) {
183661
183764
 
183662
183765
  // src/commands/flows/loadEnvFile.ts
183663
183766
  import { readFile } from "node:fs/promises";
183664
- import { join as join36 } from "node:path";
183767
+ import { join as join37 } from "node:path";
183665
183768
  async function loadEnvFile(envDir) {
183666
183769
  let content;
183667
183770
  try {
183668
- content = await readFile(join36(envDir, ".env"), "utf8");
183771
+ content = await readFile(join37(envDir, ".env"), "utf8");
183669
183772
  } catch (err) {
183670
183773
  if (isNoEntError(err))
183671
183774
  return;
@@ -183766,7 +183869,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
183766
183869
  }
183767
183870
 
183768
183871
  // src/commands/flows/hybridRunDefaults.ts
183769
- import { join as join37, resolve as resolve10 } from "node:path";
183872
+ import { join as join38, resolve as resolve10 } from "node:path";
183770
183873
  function makeDefaultHybridDeps(fs9) {
183771
183874
  return {
183772
183875
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -183785,7 +183888,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
183785
183888
  if (validation !== "ok") {
183786
183889
  return { error: validation.error, exitCode: 2 };
183787
183890
  }
183788
- const envDir = resolve10(join37(".qawolf", flags.env));
183891
+ const envDir = resolve10(join38(".qawolf", flags.env));
183789
183892
  const patternArgs = buildPatternArgs(pattern);
183790
183893
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
183791
183894
  let files = await globFlows();
@@ -183931,7 +184034,100 @@ function registerFlowsCommand(program2, signals) {
183931
184034
  }
183932
184035
 
183933
184036
  // src/domains/init/init.ts
183934
- import { dirname as dirname13, join as join38, relative as relative7 } from "node:path";
184037
+ import { dirname as dirname13, join as join40, relative as relative7 } from "node:path";
184038
+
184039
+ // src/domains/init/ensurePackageJson.ts
184040
+ import { join as join39 } from "node:path";
184041
+ async function ensurePackageJson(ctx, yes, deps) {
184042
+ const pkgPath = join39(deps.cwd, "package.json");
184043
+ if (!await deps.fs.pathExists(pkgPath)) {
184044
+ const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
184045
+ if (!confirmed2.ok || !confirmed2.value) {
184046
+ ctx.ui.info(initMessages.skippedCreatingPackageJson);
184047
+ return;
184048
+ }
184049
+ const pkg2 = {
184050
+ private: true,
184051
+ type: "module",
184052
+ dependencies: { "@qawolf/flows": flowsVersion },
184053
+ scripts: { "test:e2e": "qawolf flows run" }
184054
+ };
184055
+ await deps.fs.writeFile(pkgPath, JSON.stringify(pkg2, undefined, 2) + `
184056
+ `);
184057
+ ctx.ui.step(initMessages.createdPackageJson);
184058
+ return;
184059
+ }
184060
+ const raw = await deps.fs.readFile(pkgPath);
184061
+ let parsed;
184062
+ try {
184063
+ parsed = JSON.parse(raw);
184064
+ } catch {
184065
+ ctx.ui.warn(initMessages.packageJsonInvalidJson);
184066
+ return;
184067
+ }
184068
+ if (!isRecord3(parsed)) {
184069
+ ctx.ui.warn(initMessages.packageJsonNotAnObject);
184070
+ return;
184071
+ }
184072
+ const pkg = parsed;
184073
+ const scripts = readSection(ctx, pkg, "scripts");
184074
+ const dependencies = readSection(ctx, pkg, "dependencies");
184075
+ const devDependencies = readSection(ctx, pkg, "devDependencies");
184076
+ const needScript = scripts !== undefined && !scripts["test:e2e"];
184077
+ const priorType = pkg["type"];
184078
+ const needType = priorType !== "module";
184079
+ const needFlowsDep = dependencies !== undefined && !dependencies["@qawolf/flows"] && !devDependencies?.["@qawolf/flows"];
184080
+ const changes = [];
184081
+ if (needScript)
184082
+ changes.push(initMessages.pkgChanges.script);
184083
+ if (needType)
184084
+ changes.push(initMessages.pkgChanges.type);
184085
+ if (needFlowsDep)
184086
+ changes.push(initMessages.pkgChanges.flowsDep);
184087
+ if (changes.length === 0) {
184088
+ ctx.ui.info(initMessages.packageJsonUpToDate);
184089
+ return;
184090
+ }
184091
+ if (scripts !== undefined && scripts["test:e2e"]) {
184092
+ ctx.ui.warn(initMessages.packageJsonHasTestE2e);
184093
+ }
184094
+ const confirmed = await ctx.ui.confirm(initMessages.updatePackageJsonPrompt(changes), { yes, destructive: true });
184095
+ if (!confirmed.ok || !confirmed.value) {
184096
+ ctx.ui.info(initMessages.skippedUpdatingPackageJson);
184097
+ return;
184098
+ }
184099
+ if (needScript && scripts !== undefined) {
184100
+ scripts["test:e2e"] = "qawolf flows run";
184101
+ pkg["scripts"] = scripts;
184102
+ }
184103
+ if (needType)
184104
+ pkg["type"] = "module";
184105
+ if (needFlowsDep && dependencies !== undefined) {
184106
+ dependencies["@qawolf/flows"] = flowsVersion;
184107
+ pkg["dependencies"] = dependencies;
184108
+ }
184109
+ const trailingNewline = raw.endsWith(`
184110
+ `) ? `
184111
+ ` : "";
184112
+ await deps.fs.writeFile(pkgPath, JSON.stringify(pkg, undefined, 2) + trailingNewline);
184113
+ ctx.ui.step(initMessages.updatedPackageJson);
184114
+ if (needType && priorType !== undefined) {
184115
+ const label = typeof priorType === "string" ? priorType : JSON.stringify(priorType);
184116
+ ctx.ui.warn(initMessages.typeChanged(label));
184117
+ }
184118
+ }
184119
+ function isRecord3(value) {
184120
+ return typeof value === "object" && value !== null && !Array.isArray(value);
184121
+ }
184122
+ function readSection(ctx, pkg, key) {
184123
+ const value = pkg[key];
184124
+ if (value === undefined)
184125
+ return {};
184126
+ if (isRecord3(value))
184127
+ return value;
184128
+ ctx.ui.warn(initMessages.packageJsonMalformedSection(key));
184129
+ return;
184130
+ }
183935
184131
 
183936
184132
  // src/domains/init/templates.ts
183937
184133
  var qawolfConfigTs = `export default {
@@ -183969,9 +184165,9 @@ function makeDefaultInitDeps() {
183969
184165
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
183970
184166
  ctx.ui.gap();
183971
184167
  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");
184168
+ const configPath = join40(deps.cwd, "qawolf.config.ts");
184169
+ const flowPath = join40(deps.cwd, "src", "flows", "example.flow.ts");
184170
+ const gitignorePath = join40(deps.cwd, ".qawolf", ".gitignore");
183975
184171
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
183976
184172
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
183977
184173
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -183994,54 +184190,6 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
183994
184190
  await deps.fs.writeFile(filePath, content);
183995
184191
  ctx.ui.step(initMessages.createdFile(relPath));
183996
184192
  }
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
184193
 
184046
184194
  // src/commands/init/index.ts
184047
184195
  function registerInitCommand(program2, signals) {
@@ -184057,7 +184205,7 @@ Examples:
184057
184205
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
184058
184206
 
184059
184207
  // src/domains/install/android/avd.ts
184060
- import { join as join39 } from "node:path";
184208
+ import { join as join41 } from "node:path";
184061
184209
  async function installAvds(ctx, specs, deps) {
184062
184210
  const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
184063
184211
  const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
@@ -184067,7 +184215,7 @@ async function installAvds(ctx, specs, deps) {
184067
184215
  if (versionResult.exitCode !== 0) {
184068
184216
  throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
184069
184217
  }
184070
- const licenseFile = join39(deps.androidHome, "licenses", "android-sdk-license");
184218
+ const licenseFile = join41(deps.androidHome, "licenses", "android-sdk-license");
184071
184219
  if (deps.checkExists(licenseFile)) {
184072
184220
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
184073
184221
  } else {
@@ -184082,7 +184230,7 @@ async function installAvds(ctx, specs, deps) {
184082
184230
  for (const spec of specs) {
184083
184231
  if (installedImages.has(spec.systemImage))
184084
184232
  continue;
184085
- const systemImg = join39(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
184233
+ const systemImg = join41(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
184086
184234
  if (deps.checkExists(systemImg)) {
184087
184235
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
184088
184236
  installedImages.add(spec.systemImage);
@@ -184236,6 +184384,7 @@ async function handleInstallBrowsers(ctx, pattern, options) {
184236
184384
  return installBrowsers(ctx, pattern, {
184237
184385
  cwd,
184238
184386
  spawn: defaultSpawn,
184387
+ execPath: process.execPath,
184239
184388
  platform: process.platform,
184240
184389
  browserDeps,
184241
184390
  expandPatterns: (patterns, dir) => expandPatterns(patterns, dir ?? cwd, undefined, fs9),
@@ -184644,7 +184793,7 @@ async function handlePublicApiCommand(ctx, spec, options) {
184644
184793
  return { error: exports_external.prettifyError(parsed.error) };
184645
184794
  const result = await ctx.platformClient.callPublicApi(spec.contract, parsed.data);
184646
184795
  if (!result.ok)
184647
- return { error: result.error };
184796
+ return failureFields(result);
184648
184797
  ctx.ui.output(result.value, renderHuman(result.value));
184649
184798
  return;
184650
184799
  }
@@ -184656,8 +184805,16 @@ var groupDescriptions = {
184656
184805
  var handWrittenContractNames = new Set(["flow.list"]);
184657
184806
  var skippedContractNames = new Set([
184658
184807
  ...handWrittenContractNames,
184659
- "issue.update"
184808
+ "issue.update",
184809
+ "runner.performAction"
184660
184810
  ]);
184811
+ var optionEnvironmentVariables = new Map([
184812
+ ["environmentId", "QAWOLF_ENVIRONMENT"],
184813
+ ["public.run.create.aiTaskId", "QAWOLF_AI_TASK_ID"]
184814
+ ]);
184815
+ function optionEnvironmentVariable(spec, field) {
184816
+ return optionEnvironmentVariables.get(`${spec.trpcPath}.${field}`) ?? optionEnvironmentVariables.get(field);
184817
+ }
184661
184818
  function resolveGroup(parent2, segment) {
184662
184819
  const existing = parent2.commands.find((command) => command.name() === segment);
184663
184820
  return existing ?? parent2.command(segment).description(groupDescriptions[segment] ?? `QA Wolf public API ${segment} commands`);
@@ -184674,7 +184831,13 @@ function registerSpec(program2, signals, spec, authDeps) {
184674
184831
  }
184675
184832
  const command = declareCommandKind(parent2.command(leafName), spec.kind).description(spec.description);
184676
184833
  for (const flag of spec.flags) {
184677
- if (flag.required) {
184834
+ const environmentVariable = optionEnvironmentVariable(spec, flag.field);
184835
+ if (environmentVariable) {
184836
+ const option = new Option(flag.flag, flag.description).env(environmentVariable);
184837
+ if (flag.required)
184838
+ option.makeOptionMandatory();
184839
+ command.addOption(option);
184840
+ } else if (flag.required) {
184678
184841
  command.requiredOption(flag.flag, flag.description);
184679
184842
  } else {
184680
184843
  command.option(flag.flag, flag.description);
@@ -184726,4 +184889,4 @@ createProgram({ signals }).parseAsync().catch(() => {
184726
184889
  process.exitCode = 1;
184727
184890
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
184728
184891
 
184729
- //# debugId=F77E4418A62E284264756E2164756E21
184892
+ //# debugId=6CBE0567ED2CC02E64756E2164756E21