@qawolf/cli 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Try it without installing:
23
23
  npx @qawolf/cli --help
24
24
  ```
25
25
 
26
- Supported Node versions: active LTS and newer (currently Node 22+). See [nodejs.org](https://nodejs.org).
26
+ Supported Node versions: Node 20.19+. Node 20 reached [end-of-life](https://endoflife.date/nodejs) on 2026-04-30 and no longer receives security updates; it remains supported here only for environments still pinned to Node 20. Prefer Node 22+ where possible.
27
27
 
28
28
  ### Standalone binaries
29
29
 
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 join31(s3) {
8517
+ BufferList.prototype.join = function join32(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 join31(s3) {
23073
+ BufferList.prototype.join = function join32(s3) {
23074
23074
  if (this.length === 0)
23075
23075
  return "";
23076
23076
  var p3 = this.head;
@@ -134044,7 +134044,7 @@ var require_snapshot_utils = __commonJS((exports, module) => {
134044
134044
  // node_modules/undici/lib/mock/snapshot-recorder.js
134045
134045
  var require_snapshot_recorder = __commonJS((exports, module) => {
134046
134046
  var { writeFile, readFile, mkdir } = __require("node:fs/promises");
134047
- var { dirname: dirname10, resolve: resolve9 } = __require("node:path");
134047
+ var { dirname: dirname11, resolve: resolve9 } = __require("node:path");
134048
134048
  var { setTimeout: setTimeout3, clearTimeout: clearTimeout2 } = __require("node:timers");
134049
134049
  var { InvalidArgumentError: InvalidArgumentError3, UndiciError } = require_errors6();
134050
134050
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -134239,7 +134239,7 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
134239
134239
  throw new InvalidArgumentError3("Snapshot path is required");
134240
134240
  }
134241
134241
  const resolvedPath = resolve9(path4);
134242
- await mkdir(dirname10(resolvedPath), { recursive: true });
134242
+ await mkdir(dirname11(resolvedPath), { recursive: true });
134243
134243
  const data2 = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
134244
134244
  hash: hash2,
134245
134245
  snapshot
@@ -155130,6 +155130,15 @@ function errorCode(err) {
155130
155130
  function isNoEntError(err) {
155131
155131
  return errorCode(err) === "ENOENT";
155132
155132
  }
155133
+ var missingPackagePattern = /Cannot find (?:package|module) '([^']+)'/;
155134
+ var pathLikeSpecifierPattern = /^(?:\.|\/|[A-Za-z]:[\\/])/;
155135
+ function extractMissingPackage(text) {
155136
+ const specifier = missingPackagePattern.exec(text)?.[1];
155137
+ if (specifier === undefined || pathLikeSpecifierPattern.test(specifier)) {
155138
+ return;
155139
+ }
155140
+ return specifier;
155141
+ }
155133
155142
 
155134
155143
  // src/core/messages/auth.ts
155135
155144
  var authMessages = {
@@ -155264,6 +155273,10 @@ var flowsMessages = {
155264
155273
  title: "Flows",
155265
155274
  remoteTitle: "Remote Flows",
155266
155275
  flowCount: (count) => pluralize(count, "flow"),
155276
+ list: {
155277
+ remoteRequiresEnv: "--remote requires --env <env> to pick the environment to list",
155278
+ flagsRequireRemote: "--env and --include-drafts require --remote"
155279
+ },
155267
155280
  pull: {
155268
155281
  downloadingBundle: "Downloading flows bundle",
155269
155282
  fetchingEnvVars: "Fetching environment variables",
@@ -155391,7 +155404,10 @@ var runnerMessages = {
155391
155404
  retrying: (attempt, maxAttempts) => `Retrying (${attempt} of ${maxAttempts})...`,
155392
155405
  screenshot: (path) => `Screenshot: ${path}`,
155393
155406
  managedRuntimeNote: (dir) => `Using managed runtime — override with --deps <dir> or QAWOLF_RUNTIME_DIR:
155394
- ${dir}`
155407
+ ${dir}`,
155408
+ installingProjectDeps: (count) => `Installing ${pluralize(count, "project dependency", "project dependencies")}…`,
155409
+ outerHopCandidateRejected: (dir, missing) => `outer-hop candidate rejected: ${dir} missing ${missing.join(", ")}`,
155410
+ moduleNotFoundHint: (pkg, projectDir) => projectDir === undefined ? `Hint: '${pkg}' could not be resolved. Run from within your flows project so its dependencies can be found.` : `Hint: '${pkg}' could not be resolved. Ensure it is declared in ${projectDir}/package.json "dependencies" and run npm install in that project.`
155395
155411
  };
155396
155412
  // node_modules/env-paths/index.js
155397
155413
  import path from "node:path";
@@ -170010,7 +170026,8 @@ async function validateApiKey(deps) {
170010
170026
  }
170011
170027
  // src/shell/logger.ts
170012
170028
  var import_pino = __toESM(require_pino(), 1);
170013
- import { join as join4 } from "node:path";
170029
+ import { mkdirSync as mkdirSync2, openSync } from "node:fs";
170030
+ import { dirname, join as join4 } from "node:path";
170014
170031
  function defaultLogPath() {
170015
170032
  return join4(envPaths("qawolf", { suffix: "" }).log, "cli.log");
170016
170033
  }
@@ -170024,7 +170041,8 @@ function resolveStderrLevel(env2, verbose) {
170024
170041
  }
170025
170042
  function createLoggingSystem(opts) {
170026
170043
  const { stderrLevel, logPath } = opts;
170027
- const dest = import_pino.default.destination({ dest: logPath, mkdir: true, sync: false });
170044
+ mkdirSync2(dirname(logPath), { recursive: true });
170045
+ const dest = import_pino.default.destination({ fd: openSync(logPath, "a"), sync: false });
170028
170046
  const fileLogger = import_pino.default({ level: "debug" }, dest);
170029
170047
  const levelNums = {
170030
170048
  trace: 10,
@@ -171146,45 +171164,6 @@ async function getIdentity(apiKey, deps) {
171146
171164
  return { ok: true, data: parsed.data };
171147
171165
  }
171148
171166
 
171149
- // src/shell/platform/getRemoteFlows.ts
171150
- var remoteFlowSchema = exports_external.object({
171151
- executionTarget: exports_external.union([exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())]),
171152
- id: exports_external.string(),
171153
- name: exports_external.string(),
171154
- path: exports_external.string(),
171155
- tags: exports_external.array(exports_external.string())
171156
- });
171157
- var remoteFlowsResponseSchema = exports_external.object({
171158
- flows: exports_external.array(remoteFlowSchema)
171159
- });
171160
- async function getRemoteFlows(apiKey, deps) {
171161
- const url2 = `${deps.baseUrl}/api/v0/flows?includeDrafts=false`;
171162
- let response;
171163
- try {
171164
- response = await deps.fetch(url2, {
171165
- headers: { Authorization: `Bearer ${apiKey}` },
171166
- signal: AbortSignal.timeout(15000)
171167
- });
171168
- } catch (error51) {
171169
- return { ok: false, error: { kind: "network", cause: toError(error51) } };
171170
- }
171171
- if (!response.ok) {
171172
- const body = await response.text().catch(() => "");
171173
- return {
171174
- ok: false,
171175
- error: { kind: "http", status: response.status, body }
171176
- };
171177
- }
171178
- const json2 = await response.json().catch(() => {
171179
- return;
171180
- });
171181
- const parsed = remoteFlowsResponseSchema.safeParse(json2);
171182
- if (!parsed.success) {
171183
- return { ok: false, error: { kind: "parse", cause: parsed.error } };
171184
- }
171185
- return { ok: true, data: parsed.data };
171186
- }
171187
-
171188
171167
  // src/shell/platform/types.ts
171189
171168
  var environmentListItemSchema = exports_external.object({
171190
171169
  envId: exports_external.string(),
@@ -171238,10 +171217,10 @@ async function listTeamStorageFiles(trpc, args, deps) {
171238
171217
  }
171239
171218
 
171240
171219
  // src/shell/platform/teamStorageAssets.ts
171241
- import { dirname as dirname2, join as join8 } from "node:path";
171220
+ import { dirname as dirname3, join as join8 } from "node:path";
171242
171221
 
171243
171222
  // src/shell/platform/teamStorageAssetManifest.ts
171244
- import { dirname } from "node:path";
171223
+ import { dirname as dirname2 } from "node:path";
171245
171224
  var assetManifestSchema = exports_external.object({
171246
171225
  version: exports_external.literal(1),
171247
171226
  files: exports_external.array(exports_external.object({
@@ -171285,7 +171264,7 @@ async function writeAssetManifest(assetsAbs, files, fs2) {
171285
171264
  }))
171286
171265
  };
171287
171266
  const path2 = assetManifestPath(assetsAbs);
171288
- await fs2.mkdir(dirname(path2), { recursive: true });
171267
+ await fs2.mkdir(dirname2(path2), { recursive: true });
171289
171268
  await fs2.writeFile(path2, `${JSON.stringify(manifest, undefined, 2)}
171290
171269
  `);
171291
171270
  }
@@ -171451,7 +171430,7 @@ async function writeAssetSnapshot(args) {
171451
171430
  await args.deps.fs.mkdir(args.tmpAssets, { recursive: true });
171452
171431
  for (const { file: file2, relativePath } of args.safeFiles) {
171453
171432
  const dest = join8(args.tmpAssets, relativePath);
171454
- await args.deps.fs.mkdir(dirname2(dest), { recursive: true });
171433
+ await args.deps.fs.mkdir(dirname3(dest), { recursive: true });
171455
171434
  if (args.reusable.has(relativePath)) {
171456
171435
  await args.deps.fs.copyFile(join8(args.assetsAbs, relativePath), dest);
171457
171436
  continue;
@@ -171490,14 +171469,6 @@ function createPlatformClient(apiKey, deps) {
171490
171469
  sleep: deps.sleep
171491
171470
  });
171492
171471
  },
171493
- async getRemoteFlows() {
171494
- return requestWithRetry({
171495
- call: () => getRemoteFlows(apiKey, deps),
171496
- backoffMs: requestBackoffMs2,
171497
- describe: (err) => describeRequestError(err, deps.baseUrl, "flows"),
171498
- sleep: deps.sleep
171499
- });
171500
- },
171501
171472
  getFlowsBundleUrl: getFlowsBundleUrlImpl,
171502
171473
  callPublicApi: makeCallPublicApiMethod(trpc, deps, requestBackoffMs2),
171503
171474
  async getEnvVars(envId) {
@@ -173618,7 +173589,7 @@ function registerAuthCommand(program2, signals) {
173618
173589
  // package.json
173619
173590
  var package_default = {
173620
173591
  name: "@qawolf/cli",
173621
- version: "1.1.0",
173592
+ version: "1.3.0",
173622
173593
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173623
173594
  keywords: [
173624
173595
  "automation",
@@ -173676,8 +173647,9 @@ var package_default = {
173676
173647
  dependencies: {
173677
173648
  "@clack/prompts": "1.5.1",
173678
173649
  "@napi-rs/keyring": "1.3.0",
173650
+ "@oxc-node/core": "0.1.0",
173679
173651
  "@playwright/test": "1.60.0",
173680
- "@qawolf/api-contracts": "0.1.0",
173652
+ "@qawolf/api-contracts": "0.2.0",
173681
173653
  "@qawolf/emails": "1.1.1",
173682
173654
  "@qawolf/flow-targets": "1.0.0",
173683
173655
  "@qawolf/flows": "0.1.1",
@@ -173709,7 +173681,7 @@ var package_default = {
173709
173681
  typescript: "6.0.3"
173710
173682
  },
173711
173683
  engines: {
173712
- node: ">=22.15.0"
173684
+ node: ">=20.19.0"
173713
173685
  },
173714
173686
  packageManager: "bun@1.3.13"
173715
173687
  };
@@ -174035,15 +174007,15 @@ async function checkFileAssets(deps) {
174035
174007
 
174036
174008
  // src/domains/doctor/checks/nodeVersion.ts
174037
174009
  async function checkNodeVersion(deps) {
174038
- const minMajor = extractMajor(deps.enginesNode, /^>=\s*(\d+)/);
174039
- if (minMajor === undefined) {
174010
+ const min = parseVersion(deps.enginesNode, /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
174011
+ if (min === undefined) {
174040
174012
  return {
174041
174013
  name: "node-version",
174042
174014
  status: "fail",
174043
174015
  detail: doctorMessages.nodeVersion.couldNotParseEngines(deps.enginesNode)
174044
174016
  };
174045
174017
  }
174046
- const actual = extractMajor(deps.processVersion, /^v?(\d+)/);
174018
+ const actual = parseVersion(deps.processVersion, /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
174047
174019
  if (actual === undefined) {
174048
174020
  return {
174049
174021
  name: "node-version",
@@ -174051,7 +174023,7 @@ async function checkNodeVersion(deps) {
174051
174023
  detail: doctorMessages.nodeVersion.couldNotParseVersion(deps.processVersion)
174052
174024
  };
174053
174025
  }
174054
- if (actual < minMajor) {
174026
+ if (compareVersions(actual, min) < 0) {
174055
174027
  return {
174056
174028
  name: "node-version",
174057
174029
  status: "fail",
@@ -174065,9 +174037,18 @@ async function checkNodeVersion(deps) {
174065
174037
  version: deps.processVersion.replace(/^v/, "")
174066
174038
  };
174067
174039
  }
174068
- function extractMajor(input, pattern) {
174069
- const captured = input.trim().match(pattern)?.[1];
174070
- return captured === undefined ? undefined : Number.parseInt(captured, 10);
174040
+ function parseVersion(input, pattern) {
174041
+ const match = input.trim().match(pattern);
174042
+ if (match?.[1] === undefined)
174043
+ return;
174044
+ return [
174045
+ Number.parseInt(match[1], 10),
174046
+ match[2] === undefined ? 0 : Number.parseInt(match[2], 10),
174047
+ match[3] === undefined ? 0 : Number.parseInt(match[3], 10)
174048
+ ];
174049
+ }
174050
+ function compareVersions(a2, b2) {
174051
+ return a2[0] - b2[0] || a2[1] - b2[1] || a2[2] - b2[2];
174071
174052
  }
174072
174053
 
174073
174054
  // src/domains/doctor/checks/npmRegistry.ts
@@ -174195,13 +174176,13 @@ function renderResults(ui, results) {
174195
174176
  }
174196
174177
 
174197
174178
  // src/domains/flows/ensureDeps.ts
174198
- import { dirname as dirname3, join as join10 } from "node:path";
174179
+ import { dirname as dirname4, join as join10 } from "node:path";
174199
174180
  function findEnvDir(flowPath, fs2) {
174200
- let dir = dirname3(flowPath);
174181
+ let dir = dirname4(flowPath);
174201
174182
  while (true) {
174202
174183
  if (fs2.existsSync(join10(dir, "package.json")))
174203
174184
  return dir;
174204
- const parent = dirname3(dir);
174185
+ const parent = dirname4(dir);
174205
174186
  if (parent === dir)
174206
174187
  return;
174207
174188
  dir = parent;
@@ -174234,7 +174215,7 @@ import { fileURLToPath } from "url";
174234
174215
 
174235
174216
  // node_modules/fdir/dist/index.mjs
174236
174217
  import { createRequire as createRequire2 } from "module";
174237
- import { basename, dirname as dirname4, normalize as normalize2, relative as relative2, resolve, sep as sep3 } from "path";
174218
+ import { basename, dirname as dirname5, normalize as normalize2, relative as relative2, resolve, sep as sep3 } from "path";
174238
174219
  import * as nativeFs from "fs";
174239
174220
  var __require2 = /* @__PURE__ */ createRequire2(import.meta.url);
174240
174221
  function cleanPath(path2) {
@@ -174398,7 +174379,7 @@ function build$2(options, isSynchronous) {
174398
174379
  function isRecursive(path2, resolved, state) {
174399
174380
  if (state.options.useRealPaths)
174400
174381
  return isRecursiveUsingRealPaths(resolved, state);
174401
- let parent = dirname4(path2);
174382
+ let parent = dirname5(path2);
174402
174383
  let depth = 1;
174403
174384
  while (parent !== state.root && depth < 2) {
174404
174385
  const resolvedPath = state.symlinks.get(parent);
@@ -174406,7 +174387,7 @@ function isRecursive(path2, resolved, state) {
174406
174387
  if (isSameRoot)
174407
174388
  depth++;
174408
174389
  else
174409
- parent = dirname4(parent);
174390
+ parent = dirname5(parent);
174410
174391
  }
174411
174392
  state.symlinks.set(path2, resolved);
174412
174393
  return depth > 1;
@@ -174602,7 +174583,7 @@ var Walker = class {
174602
174583
  } else {
174603
174584
  resolvedPath = useRealPaths ? resolvedPath : path2;
174604
174585
  const filename = basename(resolvedPath);
174605
- const directoryPath$1 = normalizePath(dirname4(resolvedPath), this.state.options);
174586
+ const directoryPath$1 = normalizePath(dirname5(resolvedPath), this.state.options);
174606
174587
  resolvedPath = this.joinPath(filename, directoryPath$1);
174607
174588
  this.pushFile(resolvedPath, files, this.state.counts, filters);
174608
174589
  }
@@ -175680,14 +175661,80 @@ function handleFlowsList(ctx, pattern) {
175680
175661
 
175681
175662
  // src/domains/flows/listRemote.ts
175682
175663
  var import_picomatch2 = __toESM(require_picomatch2(), 1);
175683
- async function flowsListRemote(ctx, pattern) {
175684
- const result = await ctx.platform.getRemoteFlows();
175664
+
175665
+ // node_modules/@qawolf/api-contracts/dist/v1/ids.js
175666
+ var defaultIdSchemas = {
175667
+ environment: exports_external.string().min(1).describe("The id of the environment."),
175668
+ flow: exports_external.string().min(1).describe("The id of the flow."),
175669
+ run: exports_external.string().min(1).describe("The id of the run.")
175670
+ };
175671
+
175672
+ // node_modules/@qawolf/api-contracts/dist/v1/flow/index.js
175673
+ var makeListFlowsContract = (ids) => {
175674
+ const input = exports_external.object({
175675
+ environmentId: ids.environment,
175676
+ includeDrafts: exports_external.boolean().default(false).describe("Include draft flows in the listing.")
175677
+ });
175678
+ const executionTarget = exports_external.union([exports_external.string().min(1), exports_external.record(exports_external.string(), exports_external.unknown())]).describe("The target the flow executes against, as a preset or object.");
175679
+ const output = exports_external.object({
175680
+ flows: exports_external.array(exports_external.object({
175681
+ executionTarget,
175682
+ flowId: ids.flow,
175683
+ name: exports_external.string(),
175684
+ path: exports_external.string().describe("The flow file path, relative to the repo."),
175685
+ tags: exports_external.array(exports_external.string())
175686
+ }))
175687
+ });
175688
+ return {
175689
+ description: "List the flows of an environment at its latest reconciled commit.",
175690
+ input,
175691
+ kind: "read",
175692
+ name: "flow.list",
175693
+ output
175694
+ };
175695
+ };
175696
+
175697
+ // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
175698
+ var makeCreateRunContract = (ids) => {
175699
+ const input = exports_external.object({
175700
+ environmentId: ids.environment,
175701
+ environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
175702
+ flowIds: exports_external.array(ids.flow).min(1),
175703
+ ignoreRules: exports_external.boolean().default(false)
175704
+ });
175705
+ const output = exports_external.object({ runId: ids.run });
175706
+ return {
175707
+ description: "Create a run for selected flows in an environment.",
175708
+ input,
175709
+ kind: "write",
175710
+ name: "run.create",
175711
+ output
175712
+ };
175713
+ };
175714
+
175715
+ // node_modules/@qawolf/api-contracts/dist/v1/index.js
175716
+ var makeContractsV1 = (ids) => ({
175717
+ flow: {
175718
+ list: makeListFlowsContract(ids)
175719
+ },
175720
+ run: {
175721
+ create: makeCreateRunContract(ids)
175722
+ }
175723
+ });
175724
+ var publicContractsV1 = makeContractsV1(defaultIdSchemas);
175725
+
175726
+ // src/domains/flows/listRemote.ts
175727
+ async function flowsListRemote(ctx, pattern, options) {
175728
+ const result = await ctx.platform.callPublicApi(publicContractsV1.flow.list, {
175729
+ environmentId: options.env,
175730
+ includeDrafts: options.includeDrafts
175731
+ });
175685
175732
  if (!result.ok) {
175686
175733
  return { error: result.error };
175687
175734
  }
175688
175735
  const matches = pattern ? import_picomatch2.default(pattern) : undefined;
175689
175736
  const items = result.value.flows.filter((f2) => !matches || matches(f2.path)).map((f2) => ({
175690
- id: f2.id,
175737
+ flowId: f2.flowId,
175691
175738
  file: f2.path,
175692
175739
  name: f2.name,
175693
175740
  tags: f2.tags,
@@ -175717,7 +175764,7 @@ async function flowsListRemote(ctx, pattern) {
175717
175764
  }
175718
175765
 
175719
175766
  // src/domains/flows/pull/handler.ts
175720
- import { dirname as dirname6, join as join23, resolve as resolve7 } from "node:path";
175767
+ import { dirname as dirname7, join as join23, resolve as resolve7 } from "node:path";
175721
175768
 
175722
175769
  // src/shell/manifest/io.ts
175723
175770
  import { createHash as createHash2 } from "node:crypto";
@@ -176108,7 +176155,7 @@ async function writeEnvFile(envDir, vars, fs2 = makeDefaultFs()) {
176108
176155
  }
176109
176156
 
176110
176157
  // src/domains/flows/pull/extract.ts
176111
- import { dirname as dirname5, resolve as resolve6 } from "node:path";
176158
+ import { dirname as dirname6, resolve as resolve6 } from "node:path";
176112
176159
  import { createGunzip } from "node:zlib";
176113
176160
 
176114
176161
  // node_modules/tar/dist/esm/index.min.js
@@ -179372,7 +179419,7 @@ async function handleEntry(args) {
179372
179419
  total: args.getTotal()
179373
179420
  });
179374
179421
  args.addTotal(size);
179375
- args.fs.mkdirSync(dirname5(target), { recursive: true });
179422
+ args.fs.mkdirSync(dirname6(target), { recursive: true });
179376
179423
  const chunks = [];
179377
179424
  await new Promise((res, rej) => {
179378
179425
  entry.on("data", (chunk) => chunks.push(chunk));
@@ -179476,7 +179523,7 @@ async function handleFlowsPull(ctx, opts, deps) {
179476
179523
  return { error: validation.error };
179477
179524
  }
179478
179525
  const destAbs = resolve7(opts.out ?? join23(".qawolf", opts.env));
179479
- const assetsAbs = join23(dirname6(destAbs), "assets");
179526
+ const assetsAbs = join23(dirname7(destAbs), "assets");
179480
179527
  await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
179481
179528
  const yes = opts.yes ?? false;
179482
179529
  let archive;
@@ -180012,7 +180059,7 @@ async function defaultRunWebFlowDeps(cwd = process.cwd(), signals) {
180012
180059
 
180013
180060
  // src/domains/runtimeEnv/prepareRunDir.ts
180014
180061
  import { createHash as createHash3 } from "node:crypto";
180015
- import { basename as basename4, dirname as dirname9, join as join28, resolve as resolve8, sep as sep4 } from "node:path";
180062
+ import { basename as basename4, dirname as dirname10, join as join28, resolve as resolve8, sep as sep4 } from "node:path";
180016
180063
 
180017
180064
  // src/shell/copyDir.ts
180018
180065
  import { cp, readdir as readdir2 } from "node:fs/promises";
@@ -180053,7 +180100,7 @@ async function readPackageJson(pkgPath, fs3) {
180053
180100
  }
180054
180101
 
180055
180102
  // src/domains/runtimeEnv/innerHop.ts
180056
- import { dirname as dirname7, join as join26 } from "node:path";
180103
+ import { dirname as dirname8, join as join26 } from "node:path";
180057
180104
 
180058
180105
  // src/domains/runtimeEnv/symlinkDir.ts
180059
180106
  import { symlink } from "node:fs/promises";
@@ -180072,7 +180119,7 @@ async function populateInnerHop(args) {
180072
180119
  const segments = name.split("/");
180073
180120
  const target = join26(innerModules, ...segments);
180074
180121
  if (segments.length > 1) {
180075
- await fs3.mkdir(dirname7(target), { recursive: true });
180122
+ await fs3.mkdir(dirname8(target), { recursive: true });
180076
180123
  }
180077
180124
  await createDirSymlink(join26(managedModules, ...segments), target);
180078
180125
  }
@@ -180080,33 +180127,40 @@ async function populateInnerHop(args) {
180080
180127
 
180081
180128
  // src/domains/runtimeEnv/outerHop.ts
180082
180129
  import { lstat } from "node:fs/promises";
180083
- import { dirname as dirname8, join as join27 } from "node:path";
180130
+ import { dirname as dirname9, join as join27 } from "node:path";
180084
180131
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
180085
180132
  async function populateOuterHop(args) {
180086
180133
  const { projectDir, runDir, fs: fs3 } = args;
180087
180134
  if (projectDir === undefined)
180088
- return;
180089
- const nearestNm = await findNearestNodeModulesDir(projectDir);
180090
- if (nearestNm !== undefined) {
180091
- await createDirSymlink(nearestNm, join27(runDir, "node_modules"));
180092
- return;
180093
- }
180135
+ return { mode: "none" };
180094
180136
  const installableDeps = await readInstallableDeps(projectDir, fs3);
180095
- if (Object.keys(installableDeps).length > 0) {
180096
- await installOuterDeps(runDir, installableDeps, fs3);
180097
- }
180098
- }
180099
- async function findNearestNodeModulesDir(startDir) {
180137
+ const depNames = Object.keys(installableDeps);
180138
+ const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
180139
+ if (satisfying !== undefined) {
180140
+ await createDirSymlink(satisfying, join27(runDir, "node_modules"));
180141
+ return { mode: "symlink", nodeModulesDir: satisfying };
180142
+ }
180143
+ if (depNames.length === 0)
180144
+ return { mode: "none" };
180145
+ args.onInstallStart?.(depNames.length);
180146
+ await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
180147
+ return { mode: "install", depCount: depNames.length, rejected };
180148
+ }
180149
+ async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
180150
+ const rejected = [];
180100
180151
  let dir = startDir;
180101
180152
  while (true) {
180102
180153
  const candidate = join27(dir, "node_modules");
180103
180154
  const stats = await lstatSafe(candidate);
180104
180155
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
180105
- return candidate;
180156
+ const missing = depNames.filter((name) => !fs3.existsSync(join27(candidate, name, "package.json")));
180157
+ if (missing.length === 0)
180158
+ return { satisfying: candidate, rejected };
180159
+ rejected.push({ dir: candidate, missing });
180106
180160
  }
180107
- const parent = dirname8(dir);
180161
+ const parent = dirname9(dir);
180108
180162
  if (parent === dir)
180109
- return;
180163
+ return { satisfying: undefined, rejected };
180110
180164
  dir = parent;
180111
180165
  }
180112
180166
  }
@@ -180124,7 +180178,12 @@ async function readInstallableDeps(projectDir, fs3) {
180124
180178
  } catch {
180125
180179
  return {};
180126
180180
  }
180127
- const parsed = JSON.parse(content);
180181
+ let parsed;
180182
+ try {
180183
+ parsed = JSON.parse(content);
180184
+ } catch {
180185
+ return {};
180186
+ }
180128
180187
  if (typeof parsed !== "object" || parsed === null)
180129
180188
  return {};
180130
180189
  const rawDeps = parsed["dependencies"];
@@ -180132,11 +180191,11 @@ async function readInstallableDeps(projectDir, fs3) {
180132
180191
  return {};
180133
180192
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
180134
180193
  }
180135
- async function installOuterDeps(runDir, deps, fs3) {
180194
+ async function installOuterDeps(runDir, deps, fs3, install) {
180136
180195
  await fs3.writeFile(join27(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180137
180196
  await fs3.writeFile(join27(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180138
180197
  `);
180139
- const result = await spawnNpmInstall(runDir);
180198
+ const result = await install(runDir);
180140
180199
  if (result.exitCode !== 0) {
180141
180200
  throw new Error(`Failed to install project dependencies into ${runDir}: ${result.stderr.trim()}`);
180142
180201
  }
@@ -180158,10 +180217,16 @@ async function prepareRunDir(args) {
180158
180217
  if (projectDir !== undefined) {
180159
180218
  await writeExecSubpathImports({ execDir, fs: fs3 });
180160
180219
  }
180161
- await populateOuterHop({ projectDir, runDir, fs: fs3 });
180220
+ const outerHop = await populateOuterHop({
180221
+ projectDir,
180222
+ runDir,
180223
+ fs: fs3,
180224
+ ...args.onInstallStart !== undefined ? { onInstallStart: args.onInstallStart } : {}
180225
+ });
180162
180226
  return {
180163
180227
  files: stagedFiles,
180164
180228
  runDir,
180229
+ outerHop,
180165
180230
  cleanup: () => fs3.rm(runDir, { recursive: true, force: true })
180166
180231
  };
180167
180232
  }
@@ -180181,7 +180246,7 @@ async function stageFlowFiles(args) {
180181
180246
  }
180182
180247
  if (files.length > 1) {
180183
180248
  return Promise.all(files.map(async (f3) => {
180184
- const dirHash = createHash3("sha256").update(dirname9(f3)).digest("hex").slice(0, 8);
180249
+ const dirHash = createHash3("sha256").update(dirname10(f3)).digest("hex").slice(0, 8);
180185
180250
  const subDir = join28(execDir, dirHash);
180186
180251
  await fs3.mkdir(subDir, { recursive: true });
180187
180252
  const dest = join28(subDir, basename4(f3));
@@ -180244,8 +180309,143 @@ function resolveDepsRoot(args) {
180244
180309
  }, { fs: fs3 });
180245
180310
  }
180246
180311
 
180247
- // src/shell/appium/createAppiumServer.ts
180312
+ // src/domains/emails/configureEmails.ts
180313
+ async function loadSdkDeps2(cwd) {
180314
+ try {
180315
+ const emailsPath = resolveFromEnvDir(cwd, "@qawolf/emails");
180316
+ return await import(emailsPath);
180317
+ } catch (err) {
180318
+ throw new Error("Could not load @qawolf/emails. Install it in your project: `npm install @qawolf/emails` or `bun add @qawolf/emails`.", { cause: err });
180319
+ }
180320
+ }
180321
+ async function configureEmails(config2, cwd, deps) {
180322
+ const { createEmailsClient, configureEmailsClient } = deps ?? await loadSdkDeps2(cwd);
180323
+ const client = await createEmailsClient({
180324
+ ...config2,
180325
+ pollForEmailsDefaultTimeoutMs: 60000,
180326
+ waitForMessagesDefaultDelayMs: 1000
180327
+ });
180328
+ configureEmailsClient(client);
180329
+ return client;
180330
+ }
180331
+ async function registerLazyEmailsClient(getInbox, cwd, deps) {
180332
+ const { configureEmailsClient } = deps ?? await loadSdkDeps2(cwd);
180333
+ configureEmailsClient({ getInbox });
180334
+ }
180335
+
180336
+ // src/commands/flows/envVarDeps.ts
180248
180337
  import { join as join30 } from "node:path";
180338
+ function envFilePath(envDir) {
180339
+ return join30(envDir, ".env");
180340
+ }
180341
+ async function readEnv(envDir, fs3) {
180342
+ try {
180343
+ return parseDotenv(await fs3.readFile(envFilePath(envDir)));
180344
+ } catch (err) {
180345
+ if (isNoEntError(err))
180346
+ return {};
180347
+ throw err;
180348
+ }
180349
+ }
180350
+ async function writeEnv(envDir, fs3, vars) {
180351
+ const target = envFilePath(envDir);
180352
+ const tmp = `${target}.${process.pid}.tmp`;
180353
+ await fs3.writeFile(tmp, serializeDotenv(vars), { mode: 384 });
180354
+ await fs3.rename(tmp, target);
180355
+ }
180356
+ function makeEnvVarDeps(envDir, fs3) {
180357
+ return {
180358
+ setEnvironmentVariable: async (key, value) => {
180359
+ const vars = await readEnv(envDir, fs3);
180360
+ vars[key] = value;
180361
+ await writeEnv(envDir, fs3, vars);
180362
+ process.env[key] = value;
180363
+ },
180364
+ fetchLatestEnvironmentVariables: async () => {
180365
+ const vars = await readEnv(envDir, fs3);
180366
+ for (const [key, value] of Object.entries(vars)) {
180367
+ process.env[key] = value;
180368
+ }
180369
+ }
180370
+ };
180371
+ }
180372
+
180373
+ // src/commands/flows/flowRuntimeDeps.ts
180374
+ async function maybeGetIdentityTeamId(platform) {
180375
+ const identity = await platform.getIdentity();
180376
+ return identity.ok ? identity.value.team.id : undefined;
180377
+ }
180378
+ function lazyGetInbox({
180379
+ envDir,
180380
+ ctx,
180381
+ platform,
180382
+ env: env2,
180383
+ resolveApiKeyFn,
180384
+ configureEmailsFn,
180385
+ createPlatform
180386
+ }) {
180387
+ let getInboxPromise;
180388
+ async function configure() {
180389
+ const explicitEmailerUrl = env2["EMAILER_URL"];
180390
+ let teamId = env2["CLOUD_AGENTS_INBOX_TEAM_ID"];
180391
+ const apiKeyResult = explicitEmailerUrl === undefined ? await resolveApiKeyFn(ctx.configDir, ctx.fs) : undefined;
180392
+ if (teamId === undefined && platform !== undefined) {
180393
+ teamId = await maybeGetIdentityTeamId(platform);
180394
+ }
180395
+ if (teamId === undefined && apiKeyResult !== undefined) {
180396
+ const identityPlatform = createPlatform(apiKeyResult.key, {
180397
+ baseUrl: ctx.apiBaseUrl,
180398
+ fetch: globalThis.fetch,
180399
+ ...ctx.log ? { logger: ctx.log("trpc") } : {}
180400
+ });
180401
+ teamId = await maybeGetIdentityTeamId(identityPlatform);
180402
+ }
180403
+ const teamIdPart = teamId !== undefined ? { teamId } : {};
180404
+ if (explicitEmailerUrl !== undefined) {
180405
+ const client2 = await configureEmailsFn({ emailerUrl: explicitEmailerUrl, ...teamIdPart }, envDir);
180406
+ return client2.getInbox;
180407
+ }
180408
+ if (apiKeyResult === undefined) {
180409
+ throw new Error("getInbox requires EMAILER_URL, QAWOLF_API_KEY, or stored QA Wolf credentials. Run 'qawolf auth login'.");
180410
+ }
180411
+ const client = await configureEmailsFn({ apiKey: apiKeyResult.key, url: `${ctx.apiBaseUrl}/api`, ...teamIdPart }, envDir);
180412
+ return client.getInbox;
180413
+ }
180414
+ return async (...args) => {
180415
+ getInboxPromise ??= configure();
180416
+ const getInbox = await getInboxPromise;
180417
+ return getInbox(...args);
180418
+ };
180419
+ }
180420
+ async function createFlowRuntimeDeps({
180421
+ envDir,
180422
+ ctx,
180423
+ platform,
180424
+ env: env2 = process.env,
180425
+ resolveApiKeyFn = resolveApiKey,
180426
+ configureEmailsFn = configureEmails,
180427
+ createPlatform = createPlatformClient
180428
+ }) {
180429
+ const getInbox = lazyGetInbox({
180430
+ envDir,
180431
+ ctx,
180432
+ platform,
180433
+ env: env2,
180434
+ resolveApiKeyFn,
180435
+ configureEmailsFn,
180436
+ createPlatform
180437
+ });
180438
+ try {
180439
+ await registerLazyEmailsClient(getInbox, envDir);
180440
+ } catch {}
180441
+ return {
180442
+ ...makeEnvVarDeps(envDir, ctx.fs),
180443
+ getInbox
180444
+ };
180445
+ }
180446
+
180447
+ // src/shell/appium/createAppiumServer.ts
180448
+ import { join as join31 } from "node:path";
180249
180449
 
180250
180450
  // src/shell/appium/spawnAppium.ts
180251
180451
  import { spawn as spawn2 } from "node:child_process";
@@ -180320,7 +180520,7 @@ async function createAppiumServer(envDir, signals, params) {
180320
180520
  const spawnFn = params?.deps?.spawn ?? defaultSpawnAppium;
180321
180521
  const findFreePortFn = params?.deps?.findFreePort ?? findFreePort;
180322
180522
  const resolveAppiumBinFn = params?.deps?.resolveAppiumBin ?? resolveAppiumBin;
180323
- const appiumHome2 = params?.options?.appiumHome ?? join30(envPaths("qawolf").data, "appium");
180523
+ const appiumHome2 = params?.options?.appiumHome ?? join31(envPaths("qawolf").data, "appium");
180324
180524
  const timeoutMs3 = params?.options?.startTimeoutMs ?? defaultStartTimeoutMs;
180325
180525
  const bin = resolveAppiumBinFn(envDir);
180326
180526
  const port = await findFreePortFn();
@@ -180659,12 +180859,12 @@ Install Android Studio and open Tools > SDK Manager.`);
180659
180859
  }
180660
180860
 
180661
180861
  // src/shell/manifest/lookup.ts
180662
- import { basename as basename5, dirname as dirname10, relative as relative6 } from "node:path";
180862
+ import { basename as basename5, dirname as dirname11, relative as relative6 } from "node:path";
180663
180863
  async function findFlowStamp(flowAbsPath) {
180664
180864
  let envDir;
180665
- let current = dirname10(flowAbsPath);
180666
- while (current !== dirname10(current)) {
180667
- const parent2 = dirname10(current);
180865
+ let current = dirname11(flowAbsPath);
180866
+ while (current !== dirname11(current)) {
180867
+ const parent2 = dirname11(current);
180668
180868
  if (basename5(parent2) === ".qawolf") {
180669
180869
  envDir = current;
180670
180870
  break;
@@ -180806,6 +181006,9 @@ function createAndroidLaunchContext({
180806
181006
  import path11 from "node:path";
180807
181007
  import { pathToFileURL } from "node:url";
180808
181008
 
181009
+ // src/shell/resolver/detectRuntimeCapabilities.ts
181010
+ import nodeModule2 from "node:module";
181011
+
180809
181012
  // src/shell/resolver/registerFlowModuleResolver.ts
180810
181013
  import nodeModule from "node:module";
180811
181014
 
@@ -180872,6 +181075,50 @@ function registerFlowModuleResolver() {
180872
181075
  registerHooks({ resolve: flowResolveHook });
180873
181076
  }
180874
181077
 
181078
+ // src/shell/resolver/detectRuntimeCapabilities.ts
181079
+ function detectRuntimeCapabilities() {
181080
+ return {
181081
+ isBun: globalThis.Bun !== undefined,
181082
+ hasSyncHooks: resolveRegisterHooks(nodeModule2) !== undefined,
181083
+ hasNativeTypeScript: Boolean(process.features.typescript),
181084
+ hasAsyncRegister: typeof nodeModule2.register === "function"
181085
+ };
181086
+ }
181087
+
181088
+ // src/shell/resolver/selectFlowLoaderStrategy.ts
181089
+ function selectFlowLoaderStrategy(caps) {
181090
+ if (caps.isBun)
181091
+ return "none";
181092
+ if (caps.hasNativeTypeScript) {
181093
+ return caps.hasSyncHooks ? "sync-alias" : "none";
181094
+ }
181095
+ return caps.hasAsyncRegister ? "oxc-transpile" : "unsupported";
181096
+ }
181097
+
181098
+ // src/shell/resolver/registerFlowLoader.ts
181099
+ async function defaultRegisterOxcLoader() {
181100
+ try {
181101
+ await import("@oxc-node/core/register");
181102
+ } catch (cause) {
181103
+ throw new Error(`Failed to load the @oxc-node/core TypeScript loader needed to run flows on Node ${process.version}. Ensure @oxc-node/core is installed for this platform (${process.platform}-${process.arch}).`, { cause });
181104
+ }
181105
+ }
181106
+ async function registerFlowLoader(deps = {}) {
181107
+ const {
181108
+ capabilities = detectRuntimeCapabilities(),
181109
+ registerSyncAlias = registerFlowModuleResolver,
181110
+ registerOxcLoader = defaultRegisterOxcLoader
181111
+ } = deps;
181112
+ const strategy = selectFlowLoaderStrategy(capabilities);
181113
+ if (strategy === "sync-alias") {
181114
+ registerSyncAlias();
181115
+ } else if (strategy === "oxc-transpile") {
181116
+ await registerOxcLoader();
181117
+ } else if (strategy === "unsupported") {
181118
+ throw new Error(`Running TypeScript flows requires Node 20.6+ (for module.register) or a runtime with native TypeScript support. Current runtime: ${process.version}. Upgrade Node to 20.6 or newer.`);
181119
+ }
181120
+ }
181121
+
180875
181122
  // src/domains/runner/executorPlugin.ts
180876
181123
  import path10 from "node:path";
180877
181124
  var executorImportDetect = /from ['"]@qawolf\/(?:flows|testkit|emails)/;
@@ -180971,7 +181218,7 @@ async function loadFlowDefault(args) {
180971
181218
  depsRoot = ""
180972
181219
  } = args;
180973
181220
  if (bundleFlow === undefined) {
180974
- registerFlowModuleResolver();
181221
+ await registerFlowLoader();
180975
181222
  return importDefaultExport(pathToFileURL(flowPath).href, flowPath);
180976
181223
  }
180977
181224
  const code = await bundleFlow(flowPath, depsRoot);
@@ -181180,6 +181427,7 @@ async function runAndroidFlow({
181180
181427
  callback: async (flowDeps) => {
181181
181428
  const androidDeps = {
181182
181429
  ...Object.fromEntries(unsupportedAndroidDepNames.map((name) => [name, notSupported(name)])),
181430
+ ...deps.flowRuntimeDeps,
181183
181431
  inputs: flowDeps.flowInputs,
181184
181432
  workflowInputs: flowDeps.flowInputs,
181185
181433
  setOutput: flowDeps.setOutput,
@@ -181390,6 +181638,7 @@ async function runWebFlow({
181390
181638
  callback: async (flowDeps) => {
181391
181639
  const webDeps = {
181392
181640
  ...Object.fromEntries(unsupportedWebDepNames.map((name) => [name, notSupported(name)])),
181641
+ ...deps.flowRuntimeDeps,
181393
181642
  launch,
181394
181643
  launchWithGpu: launch,
181395
181644
  isGpuAvailable: () => false,
@@ -181422,18 +181671,18 @@ async function runWebFlow({
181422
181671
  }
181423
181672
 
181424
181673
  // src/shell/embeddedWorkerCli.ts
181425
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
181426
- import { basename as basename6, join as join31 } from "node:path";
181674
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
181675
+ import { basename as basename6, join as join32 } from "node:path";
181427
181676
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
181428
181677
  function extractEmbeddedWorkerCli() {
181429
181678
  const assetPath = process.env[embeddedCliPathEnv];
181430
181679
  if (assetPath === undefined || assetPath === "")
181431
181680
  return;
181432
- const dir = join31(getDataDir(), "worker");
181433
- const dest = join31(dir, basename6(assetPath));
181681
+ const dir = join32(getDataDir(), "worker");
181682
+ const dest = join32(dir, basename6(assetPath));
181434
181683
  if (existsSync3(dest))
181435
181684
  return dest;
181436
- mkdirSync2(dir, { recursive: true });
181685
+ mkdirSync3(dir, { recursive: true });
181437
181686
  writeFileSync2(dest, readFileSync2(assetPath));
181438
181687
  return dest;
181439
181688
  }
@@ -181583,7 +181832,7 @@ function makePooledDispatch(resolvedDir) {
181583
181832
  }
181584
181833
 
181585
181834
  // src/commands/flows/buildRunReporter.ts
181586
- import { dirname as dirname11 } from "node:path";
181835
+ import { dirname as dirname12 } from "node:path";
181587
181836
 
181588
181837
  // src/shell/reporter/createConsoleReporter.ts
181589
181838
  import { styleText as styleText3 } from "node:util";
@@ -181675,6 +181924,12 @@ function createConsoleReporter(deps) {
181675
181924
  deps.stderr.write(`${formatted}
181676
181925
 
181677
181926
  `);
181927
+ const missingPackage = extractMissingPackage(errStr);
181928
+ if (missingPackage !== undefined) {
181929
+ deps.stderr.write(`${styleText3("yellow", `${indent}${runnerMessages.moduleNotFoundHint(missingPackage, deps.projectDir)}`)}
181930
+
181931
+ `);
181932
+ }
181678
181933
  const counts = tests.total > 0 ? `${tests.passed}/${tests.total} tests ` : "";
181679
181934
  deps.stdout.write(` ${styleText3("red", "✗")} ${counts}passed ${styleText3("dim", fmtDuration(durationMs))}
181680
181935
  `);
@@ -181727,7 +181982,7 @@ function createCompositeReporter(reporters) {
181727
181982
  };
181728
181983
  }
181729
181984
  // src/shell/reporter/createJUnitReporter.ts
181730
- import { join as join32 } from "node:path";
181985
+ import { join as join33 } from "node:path";
181731
181986
 
181732
181987
  // src/shell/reporter/junitXml.ts
181733
181988
  function escapeXml(str) {
@@ -181766,7 +182021,7 @@ ${suites}`;
181766
182021
 
181767
182022
  // src/shell/reporter/createJUnitReporter.ts
181768
182023
  function resolveJUnitOutputPath(junit, outputDir) {
181769
- return typeof junit === "string" && junit.trim() !== "" ? junit : join32(outputDir, "junit-report.xml");
182024
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join33(outputDir, "junit-report.xml");
181770
182025
  }
181771
182026
  function createJUnitReporter(deps) {
181772
182027
  const flows = [];
@@ -181795,13 +182050,17 @@ function createJUnitReporter(deps) {
181795
182050
  function buildRunReporter(flags, deps) {
181796
182051
  const stdout2 = deps.stdout ?? process.stdout;
181797
182052
  const stderr = deps.stderr ?? process.stderr;
181798
- const console2 = createConsoleReporter({ stdout: stdout2, stderr });
182053
+ const console2 = createConsoleReporter({
182054
+ stdout: stdout2,
182055
+ stderr,
182056
+ ...deps.projectDir !== undefined ? { projectDir: deps.projectDir } : {}
182057
+ });
181799
182058
  if (flags.junit === undefined)
181800
182059
  return console2;
181801
182060
  const junit = createJUnitReporter({
181802
182061
  outputPath: resolveJUnitOutputPath(flags.junit, flags.outputDir),
181803
182062
  writeFile: (path9, content) => {
181804
- deps.fs.mkdirSync(dirname11(path9), { recursive: true });
182063
+ deps.fs.mkdirSync(dirname12(path9), { recursive: true });
181805
182064
  deps.fs.writeFileSync(path9, content);
181806
182065
  },
181807
182066
  stderr
@@ -181811,7 +182070,15 @@ function buildRunReporter(flags, deps) {
181811
182070
 
181812
182071
  // src/commands/flows/buildFlowsRunDeps.ts
181813
182072
  function buildFlowsRunDeps(args) {
181814
- const { ctx, resolvedDir, android, runWebFlowDeps, flags } = args;
182073
+ const {
182074
+ ctx,
182075
+ resolvedDir,
182076
+ android,
182077
+ runWebFlowDeps,
182078
+ flowRuntimeDeps,
182079
+ flags,
182080
+ projectDir
182081
+ } = args;
181815
182082
  return {
181816
182083
  peekFlowMeta: makePeekFlowMeta(ctx.fs),
181817
182084
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
@@ -181820,9 +182087,9 @@ function buildFlowsRunDeps(args) {
181820
182087
  playwrightCliPath: resolvePlaywrightCli(resolvedDir, process.platform)
181821
182088
  }),
181822
182089
  runWebFlow,
181823
- runWebFlowDeps,
182090
+ runWebFlowDeps: { ...runWebFlowDeps, flowRuntimeDeps },
181824
182091
  runAndroidFlow,
181825
- runAndroidFlowDeps: android.deps,
182092
+ runAndroidFlowDeps: { ...android.deps, flowRuntimeDeps },
181826
182093
  bootAndroid: android.boot,
181827
182094
  shutdownAndroid: android.shutdown,
181828
182095
  createPooledDispatch: makePooledDispatch(resolvedDir),
@@ -181832,7 +182099,8 @@ function buildFlowsRunDeps(args) {
181832
182099
  reporter: buildRunReporter(flags, {
181833
182100
  fs: ctx.fs,
181834
182101
  stdout: { write: (text3) => ctx.ui.write(text3) },
181835
- stderr: { write: (text3) => ctx.ui.write(text3) }
182102
+ stderr: { write: (text3) => ctx.ui.write(text3) },
182103
+ ...projectDir !== undefined ? { projectDir } : {}
181836
182104
  }),
181837
182105
  now: () => Date.now()
181838
182106
  };
@@ -181840,11 +182108,11 @@ function buildFlowsRunDeps(args) {
181840
182108
 
181841
182109
  // src/commands/flows/loadEnvFile.ts
181842
182110
  import { readFile } from "node:fs/promises";
181843
- import { join as join33 } from "node:path";
182111
+ import { join as join34 } from "node:path";
181844
182112
  async function loadEnvFile(envDir) {
181845
182113
  let content;
181846
182114
  try {
181847
- content = await readFile(join33(envDir, ".env"), "utf8");
182115
+ content = await readFile(join34(envDir, ".env"), "utf8");
181848
182116
  } catch (err) {
181849
182117
  if (isNoEntError(err))
181850
182118
  return;
@@ -181880,15 +182148,34 @@ async function runStagedFlows(args) {
181880
182148
  files,
181881
182149
  projectDir,
181882
182150
  depsRoot: runtimeEnv.depsRoot,
181883
- runRoot: runStagingRoot()
182151
+ runRoot: runStagingRoot(),
182152
+ onInstallStart: (depCount) => ctx.ui.info(runnerMessages.installingProjectDeps(depCount))
181884
182153
  });
182154
+ if (staged.outerHop.mode === "install") {
182155
+ const log31 = ctx.log("runner");
182156
+ for (const candidate of staged.outerHop.rejected) {
182157
+ log31.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
182158
+ }
182159
+ }
181885
182160
  const unregisterCleanup = ctx.signals.register(staged.cleanup);
181886
182161
  try {
181887
182162
  const resolvedDir = runtimeEnv.depsRoot;
181888
182163
  await deps.configureTestkit(resolvedDir);
181889
182164
  const android = createAndroidDeps(resolvedDir, ctx.signals);
181890
182165
  const runWebFlowDeps = await deps.runWebFlowDeps(resolvedDir, ctx.signals);
181891
- return await deps.flowsRun(ctx, staged.files, flags, buildFlowsRunDeps({ ctx, resolvedDir, android, runWebFlowDeps, flags }));
182166
+ const flowRuntimeDeps = await deps.createFlowRuntimeDeps({
182167
+ envDir: resolvedDir,
182168
+ ctx
182169
+ });
182170
+ return await deps.flowsRun(ctx, staged.files, flags, buildFlowsRunDeps({
182171
+ ctx,
182172
+ resolvedDir,
182173
+ android,
182174
+ runWebFlowDeps,
182175
+ flowRuntimeDeps,
182176
+ flags,
182177
+ projectDir
182178
+ }));
181892
182179
  } finally {
181893
182180
  unregisterCleanup();
181894
182181
  await staged.cleanup();
@@ -181903,6 +182190,7 @@ function makeDefaultDeps2(fs9) {
181903
182190
  prepareRunDir: (args) => prepareRunDir({ ...args, fs: fs9 }),
181904
182191
  configureTestkit,
181905
182192
  runWebFlowDeps: defaultRunWebFlowDeps,
182193
+ createFlowRuntimeDeps,
181906
182194
  flowsRun
181907
182195
  };
181908
182196
  }
@@ -181924,7 +182212,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
181924
182212
  }
181925
182213
 
181926
182214
  // src/commands/flows/hybridRunDefaults.ts
181927
- import { join as join34, resolve as resolve9 } from "node:path";
182215
+ import { join as join35, resolve as resolve9 } from "node:path";
181928
182216
  function makeDefaultHybridDeps(fs9) {
181929
182217
  return {
181930
182218
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -181933,7 +182221,8 @@ function makeDefaultHybridDeps(fs9) {
181933
182221
  prepareRunDir: (args) => prepareRunDir({ ...args, fs: fs9 }),
181934
182222
  configureTestkit,
181935
182223
  flowsRun,
181936
- runWebFlowDeps: defaultRunWebFlowDeps
182224
+ runWebFlowDeps: defaultRunWebFlowDeps,
182225
+ createFlowRuntimeDeps
181937
182226
  };
181938
182227
  }
181939
182228
  async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
@@ -181942,7 +182231,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
181942
182231
  if (validation !== "ok") {
181943
182232
  return { error: validation.error, exitCode: 2 };
181944
182233
  }
181945
- const envDir = resolve9(join34(".qawolf", flags.env));
182234
+ const envDir = resolve9(join35(".qawolf", flags.env));
181946
182235
  const patternArgs = buildPatternArgs(pattern);
181947
182236
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
181948
182237
  let files = await globFlows();
@@ -182015,7 +182304,16 @@ async function runWorker(signals) {
182015
182304
  if (input.flow.kind !== "web")
182016
182305
  throw new Error("worker subprocess currently supports web flows only");
182017
182306
  await configureTestkit(input.resolvedDir);
182018
- const runWebFlowDeps = await defaultRunWebFlowDeps(input.resolvedDir, signals);
182307
+ const fs9 = makeDefaultFs();
182308
+ const apiBaseUrl = process.env["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
182309
+ const flowRuntimeDeps = await createFlowRuntimeDeps({
182310
+ envDir: input.resolvedDir,
182311
+ ctx: { apiBaseUrl, configDir: getConfigDir(), fs: fs9 }
182312
+ });
182313
+ const runWebFlowDeps = {
182314
+ ...await defaultRunWebFlowDeps(input.resolvedDir, signals),
182315
+ flowRuntimeDeps
182316
+ };
182019
182317
  const reporter = {};
182020
182318
  const deps = {
182021
182319
  peekFlowMeta: unavailable("peekFlowMeta"),
@@ -182048,8 +182346,8 @@ var listExamples = `
182048
182346
  Examples:
182049
182347
  $ qawolf flows list
182050
182348
  $ qawolf flows list "flows/checkout/**"
182051
- $ qawolf flows list --remote
182052
- $ qawolf flows list "**/checkout/**" --remote`;
182349
+ $ qawolf flows list --remote --env staging
182350
+ $ qawolf flows list "**/checkout/**" --remote --env staging --include-drafts`;
182053
182351
  var pullExamples = `
182054
182352
  Examples:
182055
182353
  $ qawolf flows pull --env staging
@@ -182061,9 +182359,23 @@ function registerFlowsCommand(program2, signals) {
182061
182359
  registerRunWorkerCommand(flows, signals);
182062
182360
  declareCommandKind(flows.command("list [pattern]"), "local", {
182063
182361
  kindNote: "read with --remote"
182064
- }).description("List flows matching [pattern] from the local project, or from QA Wolf with --remote").option("--remote", "List flows from the QA Wolf platform instead of the local project", false).addHelpText("after", listExamples).action((pattern, opts, command) => {
182362
+ }).description("List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote").option("--remote", "List flows from the QA Wolf platform instead of the local project", false).option("--env <env>", "Environment to list flows from (required with --remote)").option("--include-drafts", "Include draft flows in the listing (requires --remote)", false).addHelpText("after", listExamples).action((pattern, opts, command) => {
182065
182363
  if (opts.remote) {
182066
- return withAuthContext(signals, (ctx) => flowsListRemote(ctx, pattern))(opts, command);
182364
+ const env3 = opts.env;
182365
+ if (env3 === undefined) {
182366
+ return withContext(signals, async () => ({
182367
+ error: flowsMessages.list.remoteRequiresEnv
182368
+ }))(opts, command);
182369
+ }
182370
+ return withAuthContext(signals, (ctx) => flowsListRemote(ctx, pattern, {
182371
+ env: env3,
182372
+ includeDrafts: opts.includeDrafts
182373
+ }))(opts, command);
182374
+ }
182375
+ if (opts.env !== undefined || opts.includeDrafts) {
182376
+ return withContext(signals, async () => ({
182377
+ error: flowsMessages.list.flagsRequireRemote
182378
+ }))(opts, command);
182067
182379
  }
182068
182380
  return withContext(signals, (ctx) => handleFlowsList(ctx, pattern))(opts, command);
182069
182381
  });
@@ -182073,7 +182385,7 @@ function registerFlowsCommand(program2, signals) {
182073
182385
  }
182074
182386
 
182075
182387
  // src/domains/init/init.ts
182076
- import { dirname as dirname12, join as join35, relative as relative7 } from "node:path";
182388
+ import { dirname as dirname13, join as join36, relative as relative7 } from "node:path";
182077
182389
 
182078
182390
  // src/domains/init/templates.ts
182079
182391
  var qawolfConfigTs = `export default {
@@ -182111,9 +182423,9 @@ function makeDefaultInitDeps() {
182111
182423
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
182112
182424
  ctx.ui.gap();
182113
182425
  ctx.ui.intro(initMessages.title);
182114
- const configPath = join35(deps.cwd, "qawolf.config.ts");
182115
- const flowPath = join35(deps.cwd, "src", "flows", "example.flow.ts");
182116
- const gitignorePath = join35(deps.cwd, ".qawolf", ".gitignore");
182426
+ const configPath = join36(deps.cwd, "qawolf.config.ts");
182427
+ const flowPath = join36(deps.cwd, "src", "flows", "example.flow.ts");
182428
+ const gitignorePath = join36(deps.cwd, ".qawolf", ".gitignore");
182117
182429
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
182118
182430
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
182119
182431
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -182132,12 +182444,12 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
182132
182444
  return;
182133
182445
  }
182134
182446
  }
182135
- await deps.fs.mkdir(dirname12(filePath), { recursive: true });
182447
+ await deps.fs.mkdir(dirname13(filePath), { recursive: true });
182136
182448
  await deps.fs.writeFile(filePath, content);
182137
182449
  ctx.ui.step(initMessages.createdFile(relPath));
182138
182450
  }
182139
182451
  async function ensurePackageJson(ctx, yes, deps) {
182140
- const pkgPath = join35(deps.cwd, "package.json");
182452
+ const pkgPath = join36(deps.cwd, "package.json");
182141
182453
  if (!await deps.fs.pathExists(pkgPath)) {
182142
182454
  const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
182143
182455
  if (!confirmed2.ok || !confirmed2.value) {
@@ -182196,19 +182508,19 @@ Examples:
182196
182508
  }
182197
182509
 
182198
182510
  // src/commands/install/android.ts
182199
- import { join as join38 } from "node:path";
182511
+ import { join as join39 } from "node:path";
182200
182512
 
182201
182513
  // src/domains/install/android/index.ts
182202
182514
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
182203
182515
 
182204
182516
  // src/domains/install/android/avd.ts
182205
- import { join as join36 } from "node:path";
182517
+ import { join as join37 } from "node:path";
182206
182518
  async function installAvds(ctx, specs, deps) {
182207
182519
  const versionResult = await deps.spawn(deps.sdkManagerPath, ["--version"]);
182208
182520
  if (versionResult.exitCode !== 0) {
182209
182521
  throw new Error(installMessages.android.sdkmanagerNotFound(deps.sdkManagerPath));
182210
182522
  }
182211
- const licenseFile = join36(deps.androidHome, "licenses", "android-sdk-license");
182523
+ const licenseFile = join37(deps.androidHome, "licenses", "android-sdk-license");
182212
182524
  if (deps.checkExists(licenseFile)) {
182213
182525
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
182214
182526
  } else {
@@ -182222,7 +182534,7 @@ async function installAvds(ctx, specs, deps) {
182222
182534
  for (const spec of specs) {
182223
182535
  if (installedImages.has(spec.systemImage))
182224
182536
  continue;
182225
- const systemImg = join36(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182537
+ const systemImg = join37(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182226
182538
  if (deps.checkExists(systemImg)) {
182227
182539
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
182228
182540
  installedImages.add(spec.systemImage);
@@ -182270,9 +182582,9 @@ async function installAvds(ctx, specs, deps) {
182270
182582
  }
182271
182583
 
182272
182584
  // src/domains/install/android/driver.ts
182273
- import { join as join37 } from "node:path";
182585
+ import { join as join38 } from "node:path";
182274
182586
  var appiumEnv = {
182275
- APPIUM_HOME: join37(envPaths("qawolf").data, "appium")
182587
+ APPIUM_HOME: join38(envPaths("qawolf").data, "appium")
182276
182588
  };
182277
182589
  async function installUiautomator2Driver(ctx, deps) {
182278
182590
  const listResult = await deps.spawn(deps.appiumBinPath, ["driver", "list", "--installed"], { env: appiumEnv });
@@ -182359,8 +182671,8 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182359
182671
  arch: process.arch,
182360
182672
  androidHome: androidHome2,
182361
182673
  checkExists: (path9) => fs9.existsSync(path9),
182362
- sdkManagerPath: join38(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182363
- avdManagerPath: join38(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182674
+ sdkManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182675
+ avdManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182364
182676
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182365
182677
  peekFlowMeta: makePeekFlowMeta(fs9),
182366
182678
  resolveDepsRoot: async (files) => envDir ?? (await resolveDepsRoot({ files, fs: fs9 })).depsRoot,
@@ -182506,39 +182818,6 @@ Examples:
182506
182818
  });
182507
182819
  }
182508
182820
 
182509
- // node_modules/@qawolf/api-contracts/dist/v1/ids.js
182510
- var defaultIdSchemas = {
182511
- environment: exports_external.string().min(1).describe("The id of the environment."),
182512
- flow: exports_external.string().min(1).describe("The id of the flow."),
182513
- run: exports_external.string().min(1).describe("The id of the run.")
182514
- };
182515
-
182516
- // node_modules/@qawolf/api-contracts/dist/v1/run/index.js
182517
- var makeCreateRunContract = (ids) => {
182518
- const input = exports_external.object({
182519
- environmentId: ids.environment,
182520
- environmentVariables: exports_external.record(exports_external.string(), exports_external.string()).optional(),
182521
- flowIds: exports_external.array(ids.flow).min(1),
182522
- ignoreRules: exports_external.boolean().default(false)
182523
- });
182524
- const output = exports_external.object({ runId: ids.run });
182525
- return {
182526
- description: "Create a run for selected flows in an environment.",
182527
- input,
182528
- kind: "write",
182529
- name: "run.create",
182530
- output
182531
- };
182532
- };
182533
-
182534
- // node_modules/@qawolf/api-contracts/dist/v1/index.js
182535
- var makeContractsV1 = (ids) => ({
182536
- run: {
182537
- create: makeCreateRunContract(ids)
182538
- }
182539
- });
182540
- var publicContractsV1 = makeContractsV1(defaultIdSchemas);
182541
-
182542
182821
  // src/core/publicApi/flagSpecs.ts
182543
182822
  var kebabCase = (field) => field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
182544
182823
  function flagKind(schema) {
@@ -182635,8 +182914,16 @@ function buildSpec(contract, path9) {
182635
182914
  contract
182636
182915
  };
182637
182916
  }
182638
- function buildCommandSpecs(tree) {
182639
- const walk3 = (node2, path9) => Object.entries(node2).flatMap(([key, value]) => isContract(value) ? [buildSpec(value, [...path9, key])] : walk3(value, [...path9, key]));
182917
+ function buildCommandSpecs(tree, options = {}) {
182918
+ const skip = options.skipContractNames;
182919
+ const walk3 = (node2, path9) => Object.entries(node2).flatMap(([key, value]) => {
182920
+ const childPath = [...path9, key];
182921
+ if (!isContract(value))
182922
+ return walk3(value, childPath);
182923
+ if (skip?.has(childPath.join(".")))
182924
+ return [];
182925
+ return [buildSpec(value, childPath)];
182926
+ });
182640
182927
  return walk3(tree, []);
182641
182928
  }
182642
182929
 
@@ -182707,6 +182994,7 @@ async function handlePublicApiCommand(ctx, spec, options) {
182707
182994
  var groupDescriptions = {
182708
182995
  run: "Trigger and manage QA Wolf runs on the platform"
182709
182996
  };
182997
+ var handWrittenContractNames = new Set(["flow.list"]);
182710
182998
  function resolveGroup(parent2, segment) {
182711
182999
  const existing = parent2.commands.find((command) => command.name() === segment);
182712
183000
  return existing ?? parent2.command(segment).description(groupDescriptions[segment] ?? `QA Wolf public API ${segment} commands`);
@@ -182732,7 +183020,9 @@ function registerSpec(program2, signals, spec, authDeps) {
182732
183020
  command.action((options, leaf) => withAuthContext(signals, (ctx) => handlePublicApiCommand(ctx, spec, options), authDeps ?? {})(options, leaf));
182733
183021
  }
182734
183022
  function registerPublicApiCommands(program2, signals, options = {}) {
182735
- const specs = buildCommandSpecs(options.contracts ?? publicContractsV1);
183023
+ const specs = buildCommandSpecs(options.contracts ?? publicContractsV1, {
183024
+ skipContractNames: handWrittenContractNames
183025
+ });
182736
183026
  for (const spec of specs) {
182737
183027
  registerSpec(program2, signals, spec, options.authDeps);
182738
183028
  }
@@ -182773,4 +183063,4 @@ createProgram({ signals }).parseAsync().catch(() => {
182773
183063
  process.exitCode = 1;
182774
183064
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
182775
183065
 
182776
- //# debugId=1EAFDAECED4A4A1164756E2164756E21
183066
+ //# debugId=64AB4032821CC8E864756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
5
5
  "keywords": [
6
6
  "automation",
@@ -58,8 +58,9 @@
58
58
  "dependencies": {
59
59
  "@clack/prompts": "1.5.1",
60
60
  "@napi-rs/keyring": "1.3.0",
61
+ "@oxc-node/core": "0.1.0",
61
62
  "@playwright/test": "1.60.0",
62
- "@qawolf/api-contracts": "0.1.0",
63
+ "@qawolf/api-contracts": "0.2.0",
63
64
  "@qawolf/emails": "1.1.1",
64
65
  "@qawolf/flow-targets": "1.0.0",
65
66
  "@qawolf/flows": "0.1.1",
@@ -91,7 +92,7 @@
91
92
  "typescript": "6.0.3"
92
93
  },
93
94
  "engines": {
94
- "node": ">=22.15.0"
95
+ "node": ">=20.19.0"
95
96
  },
96
97
  "packageManager": "bun@1.3.13"
97
98
  }
@@ -41,7 +41,7 @@ write on timeout: it may have reached the server the first time.
41
41
  | `qawolf auth logout` | local | Remove stored credentials |
42
42
  | `qawolf auth whoami` | read | Show authentication status |
43
43
  | `qawolf doctor` | local | Diagnose problems running flows locally |
44
- | `qawolf flows list` | local (read with --remote) | List flows matching [pattern] from the local project, or from QA Wolf with --remote |
44
+ | `qawolf flows list` | local (read with --remote) | List flows matching [pattern] from the local project, or from a QA Wolf environment with --remote |
45
45
  | `qawolf flows pull` | read | Download an environment's flows into the local .qawolf/<env>/ cache |
46
46
  | `qawolf flows run` | local (read with --env) | Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment |
47
47
  | `qawolf init` | local | Scaffold a QA Wolf project in the current directory |