@qawolf/cli 1.1.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +337 -64
  3. package/package.json +3 -2
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;
@@ -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 = {
@@ -155391,7 +155400,10 @@ var runnerMessages = {
155391
155400
  retrying: (attempt, maxAttempts) => `Retrying (${attempt} of ${maxAttempts})...`,
155392
155401
  screenshot: (path) => `Screenshot: ${path}`,
155393
155402
  managedRuntimeNote: (dir) => `Using managed runtime — override with --deps <dir> or QAWOLF_RUNTIME_DIR:
155394
- ${dir}`
155403
+ ${dir}`,
155404
+ installingProjectDeps: (count) => `Installing ${pluralize(count, "project dependency", "project dependencies")}…`,
155405
+ outerHopCandidateRejected: (dir, missing) => `outer-hop candidate rejected: ${dir} missing ${missing.join(", ")}`,
155406
+ 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
155407
  };
155396
155408
  // node_modules/env-paths/index.js
155397
155409
  import path from "node:path";
@@ -173618,7 +173630,7 @@ function registerAuthCommand(program2, signals) {
173618
173630
  // package.json
173619
173631
  var package_default = {
173620
173632
  name: "@qawolf/cli",
173621
- version: "1.1.0",
173633
+ version: "1.2.0",
173622
173634
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
173623
173635
  keywords: [
173624
173636
  "automation",
@@ -173676,6 +173688,7 @@ var package_default = {
173676
173688
  dependencies: {
173677
173689
  "@clack/prompts": "1.5.1",
173678
173690
  "@napi-rs/keyring": "1.3.0",
173691
+ "@oxc-node/core": "0.1.0",
173679
173692
  "@playwright/test": "1.60.0",
173680
173693
  "@qawolf/api-contracts": "0.1.0",
173681
173694
  "@qawolf/emails": "1.1.1",
@@ -173709,7 +173722,7 @@ var package_default = {
173709
173722
  typescript: "6.0.3"
173710
173723
  },
173711
173724
  engines: {
173712
- node: ">=22.15.0"
173725
+ node: ">=20.19.0"
173713
173726
  },
173714
173727
  packageManager: "bun@1.3.13"
173715
173728
  };
@@ -174035,15 +174048,15 @@ async function checkFileAssets(deps) {
174035
174048
 
174036
174049
  // src/domains/doctor/checks/nodeVersion.ts
174037
174050
  async function checkNodeVersion(deps) {
174038
- const minMajor = extractMajor(deps.enginesNode, /^>=\s*(\d+)/);
174039
- if (minMajor === undefined) {
174051
+ const min = parseVersion(deps.enginesNode, /^>=\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
174052
+ if (min === undefined) {
174040
174053
  return {
174041
174054
  name: "node-version",
174042
174055
  status: "fail",
174043
174056
  detail: doctorMessages.nodeVersion.couldNotParseEngines(deps.enginesNode)
174044
174057
  };
174045
174058
  }
174046
- const actual = extractMajor(deps.processVersion, /^v?(\d+)/);
174059
+ const actual = parseVersion(deps.processVersion, /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
174047
174060
  if (actual === undefined) {
174048
174061
  return {
174049
174062
  name: "node-version",
@@ -174051,7 +174064,7 @@ async function checkNodeVersion(deps) {
174051
174064
  detail: doctorMessages.nodeVersion.couldNotParseVersion(deps.processVersion)
174052
174065
  };
174053
174066
  }
174054
- if (actual < minMajor) {
174067
+ if (compareVersions(actual, min) < 0) {
174055
174068
  return {
174056
174069
  name: "node-version",
174057
174070
  status: "fail",
@@ -174065,9 +174078,18 @@ async function checkNodeVersion(deps) {
174065
174078
  version: deps.processVersion.replace(/^v/, "")
174066
174079
  };
174067
174080
  }
174068
- function extractMajor(input, pattern) {
174069
- const captured = input.trim().match(pattern)?.[1];
174070
- return captured === undefined ? undefined : Number.parseInt(captured, 10);
174081
+ function parseVersion(input, pattern) {
174082
+ const match = input.trim().match(pattern);
174083
+ if (match?.[1] === undefined)
174084
+ return;
174085
+ return [
174086
+ Number.parseInt(match[1], 10),
174087
+ match[2] === undefined ? 0 : Number.parseInt(match[2], 10),
174088
+ match[3] === undefined ? 0 : Number.parseInt(match[3], 10)
174089
+ ];
174090
+ }
174091
+ function compareVersions(a2, b2) {
174092
+ return a2[0] - b2[0] || a2[1] - b2[1] || a2[2] - b2[2];
174071
174093
  }
174072
174094
 
174073
174095
  // src/domains/doctor/checks/npmRegistry.ts
@@ -180085,28 +180107,35 @@ var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
180085
180107
  async function populateOuterHop(args) {
180086
180108
  const { projectDir, runDir, fs: fs3 } = args;
180087
180109
  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
- }
180110
+ return { mode: "none" };
180094
180111
  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) {
180112
+ const depNames = Object.keys(installableDeps);
180113
+ const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs3);
180114
+ if (satisfying !== undefined) {
180115
+ await createDirSymlink(satisfying, join27(runDir, "node_modules"));
180116
+ return { mode: "symlink", nodeModulesDir: satisfying };
180117
+ }
180118
+ if (depNames.length === 0)
180119
+ return { mode: "none" };
180120
+ args.onInstallStart?.(depNames.length);
180121
+ await installOuterDeps(runDir, installableDeps, fs3, args.install ?? spawnNpmInstall);
180122
+ return { mode: "install", depCount: depNames.length, rejected };
180123
+ }
180124
+ async function findSatisfyingNodeModulesDir(startDir, depNames, fs3) {
180125
+ const rejected = [];
180100
180126
  let dir = startDir;
180101
180127
  while (true) {
180102
180128
  const candidate = join27(dir, "node_modules");
180103
180129
  const stats = await lstatSafe(candidate);
180104
180130
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
180105
- return candidate;
180131
+ const missing = depNames.filter((name) => !fs3.existsSync(join27(candidate, name, "package.json")));
180132
+ if (missing.length === 0)
180133
+ return { satisfying: candidate, rejected };
180134
+ rejected.push({ dir: candidate, missing });
180106
180135
  }
180107
180136
  const parent = dirname8(dir);
180108
180137
  if (parent === dir)
180109
- return;
180138
+ return { satisfying: undefined, rejected };
180110
180139
  dir = parent;
180111
180140
  }
180112
180141
  }
@@ -180124,7 +180153,12 @@ async function readInstallableDeps(projectDir, fs3) {
180124
180153
  } catch {
180125
180154
  return {};
180126
180155
  }
180127
- const parsed = JSON.parse(content);
180156
+ let parsed;
180157
+ try {
180158
+ parsed = JSON.parse(content);
180159
+ } catch {
180160
+ return {};
180161
+ }
180128
180162
  if (typeof parsed !== "object" || parsed === null)
180129
180163
  return {};
180130
180164
  const rawDeps = parsed["dependencies"];
@@ -180132,11 +180166,11 @@ async function readInstallableDeps(projectDir, fs3) {
180132
180166
  return {};
180133
180167
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames.has(name)).filter((entry) => typeof entry[1] === "string"));
180134
180168
  }
180135
- async function installOuterDeps(runDir, deps, fs3) {
180169
+ async function installOuterDeps(runDir, deps, fs3, install) {
180136
180170
  await fs3.writeFile(join27(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
180137
180171
  await fs3.writeFile(join27(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
180138
180172
  `);
180139
- const result = await spawnNpmInstall(runDir);
180173
+ const result = await install(runDir);
180140
180174
  if (result.exitCode !== 0) {
180141
180175
  throw new Error(`Failed to install project dependencies into ${runDir}: ${result.stderr.trim()}`);
180142
180176
  }
@@ -180158,10 +180192,16 @@ async function prepareRunDir(args) {
180158
180192
  if (projectDir !== undefined) {
180159
180193
  await writeExecSubpathImports({ execDir, fs: fs3 });
180160
180194
  }
180161
- await populateOuterHop({ projectDir, runDir, fs: fs3 });
180195
+ const outerHop = await populateOuterHop({
180196
+ projectDir,
180197
+ runDir,
180198
+ fs: fs3,
180199
+ ...args.onInstallStart !== undefined ? { onInstallStart: args.onInstallStart } : {}
180200
+ });
180162
180201
  return {
180163
180202
  files: stagedFiles,
180164
180203
  runDir,
180204
+ outerHop,
180165
180205
  cleanup: () => fs3.rm(runDir, { recursive: true, force: true })
180166
180206
  };
180167
180207
  }
@@ -180244,8 +180284,143 @@ function resolveDepsRoot(args) {
180244
180284
  }, { fs: fs3 });
180245
180285
  }
180246
180286
 
180247
- // src/shell/appium/createAppiumServer.ts
180287
+ // src/domains/emails/configureEmails.ts
180288
+ async function loadSdkDeps2(cwd) {
180289
+ try {
180290
+ const emailsPath = resolveFromEnvDir(cwd, "@qawolf/emails");
180291
+ return await import(emailsPath);
180292
+ } catch (err) {
180293
+ throw new Error("Could not load @qawolf/emails. Install it in your project: `npm install @qawolf/emails` or `bun add @qawolf/emails`.", { cause: err });
180294
+ }
180295
+ }
180296
+ async function configureEmails(config2, cwd, deps) {
180297
+ const { createEmailsClient, configureEmailsClient } = deps ?? await loadSdkDeps2(cwd);
180298
+ const client = await createEmailsClient({
180299
+ ...config2,
180300
+ pollForEmailsDefaultTimeoutMs: 60000,
180301
+ waitForMessagesDefaultDelayMs: 1000
180302
+ });
180303
+ configureEmailsClient(client);
180304
+ return client;
180305
+ }
180306
+ async function registerLazyEmailsClient(getInbox, cwd, deps) {
180307
+ const { configureEmailsClient } = deps ?? await loadSdkDeps2(cwd);
180308
+ configureEmailsClient({ getInbox });
180309
+ }
180310
+
180311
+ // src/commands/flows/envVarDeps.ts
180248
180312
  import { join as join30 } from "node:path";
180313
+ function envFilePath(envDir) {
180314
+ return join30(envDir, ".env");
180315
+ }
180316
+ async function readEnv(envDir, fs3) {
180317
+ try {
180318
+ return parseDotenv(await fs3.readFile(envFilePath(envDir)));
180319
+ } catch (err) {
180320
+ if (isNoEntError(err))
180321
+ return {};
180322
+ throw err;
180323
+ }
180324
+ }
180325
+ async function writeEnv(envDir, fs3, vars) {
180326
+ const target = envFilePath(envDir);
180327
+ const tmp = `${target}.${process.pid}.tmp`;
180328
+ await fs3.writeFile(tmp, serializeDotenv(vars), { mode: 384 });
180329
+ await fs3.rename(tmp, target);
180330
+ }
180331
+ function makeEnvVarDeps(envDir, fs3) {
180332
+ return {
180333
+ setEnvironmentVariable: async (key, value) => {
180334
+ const vars = await readEnv(envDir, fs3);
180335
+ vars[key] = value;
180336
+ await writeEnv(envDir, fs3, vars);
180337
+ process.env[key] = value;
180338
+ },
180339
+ fetchLatestEnvironmentVariables: async () => {
180340
+ const vars = await readEnv(envDir, fs3);
180341
+ for (const [key, value] of Object.entries(vars)) {
180342
+ process.env[key] = value;
180343
+ }
180344
+ }
180345
+ };
180346
+ }
180347
+
180348
+ // src/commands/flows/flowRuntimeDeps.ts
180349
+ async function maybeGetIdentityTeamId(platform) {
180350
+ const identity = await platform.getIdentity();
180351
+ return identity.ok ? identity.value.team.id : undefined;
180352
+ }
180353
+ function lazyGetInbox({
180354
+ envDir,
180355
+ ctx,
180356
+ platform,
180357
+ env: env2,
180358
+ resolveApiKeyFn,
180359
+ configureEmailsFn,
180360
+ createPlatform
180361
+ }) {
180362
+ let getInboxPromise;
180363
+ async function configure() {
180364
+ const explicitEmailerUrl = env2["EMAILER_URL"];
180365
+ let teamId = env2["CLOUD_AGENTS_INBOX_TEAM_ID"];
180366
+ const apiKeyResult = explicitEmailerUrl === undefined ? await resolveApiKeyFn(ctx.configDir, ctx.fs) : undefined;
180367
+ if (teamId === undefined && platform !== undefined) {
180368
+ teamId = await maybeGetIdentityTeamId(platform);
180369
+ }
180370
+ if (teamId === undefined && apiKeyResult !== undefined) {
180371
+ const identityPlatform = createPlatform(apiKeyResult.key, {
180372
+ baseUrl: ctx.apiBaseUrl,
180373
+ fetch: globalThis.fetch,
180374
+ ...ctx.log ? { logger: ctx.log("trpc") } : {}
180375
+ });
180376
+ teamId = await maybeGetIdentityTeamId(identityPlatform);
180377
+ }
180378
+ const teamIdPart = teamId !== undefined ? { teamId } : {};
180379
+ if (explicitEmailerUrl !== undefined) {
180380
+ const client2 = await configureEmailsFn({ emailerUrl: explicitEmailerUrl, ...teamIdPart }, envDir);
180381
+ return client2.getInbox;
180382
+ }
180383
+ if (apiKeyResult === undefined) {
180384
+ throw new Error("getInbox requires EMAILER_URL, QAWOLF_API_KEY, or stored QA Wolf credentials. Run 'qawolf auth login'.");
180385
+ }
180386
+ const client = await configureEmailsFn({ apiKey: apiKeyResult.key, url: `${ctx.apiBaseUrl}/api`, ...teamIdPart }, envDir);
180387
+ return client.getInbox;
180388
+ }
180389
+ return async (...args) => {
180390
+ getInboxPromise ??= configure();
180391
+ const getInbox = await getInboxPromise;
180392
+ return getInbox(...args);
180393
+ };
180394
+ }
180395
+ async function createFlowRuntimeDeps({
180396
+ envDir,
180397
+ ctx,
180398
+ platform,
180399
+ env: env2 = process.env,
180400
+ resolveApiKeyFn = resolveApiKey,
180401
+ configureEmailsFn = configureEmails,
180402
+ createPlatform = createPlatformClient
180403
+ }) {
180404
+ const getInbox = lazyGetInbox({
180405
+ envDir,
180406
+ ctx,
180407
+ platform,
180408
+ env: env2,
180409
+ resolveApiKeyFn,
180410
+ configureEmailsFn,
180411
+ createPlatform
180412
+ });
180413
+ try {
180414
+ await registerLazyEmailsClient(getInbox, envDir);
180415
+ } catch {}
180416
+ return {
180417
+ ...makeEnvVarDeps(envDir, ctx.fs),
180418
+ getInbox
180419
+ };
180420
+ }
180421
+
180422
+ // src/shell/appium/createAppiumServer.ts
180423
+ import { join as join31 } from "node:path";
180249
180424
 
180250
180425
  // src/shell/appium/spawnAppium.ts
180251
180426
  import { spawn as spawn2 } from "node:child_process";
@@ -180320,7 +180495,7 @@ async function createAppiumServer(envDir, signals, params) {
180320
180495
  const spawnFn = params?.deps?.spawn ?? defaultSpawnAppium;
180321
180496
  const findFreePortFn = params?.deps?.findFreePort ?? findFreePort;
180322
180497
  const resolveAppiumBinFn = params?.deps?.resolveAppiumBin ?? resolveAppiumBin;
180323
- const appiumHome2 = params?.options?.appiumHome ?? join30(envPaths("qawolf").data, "appium");
180498
+ const appiumHome2 = params?.options?.appiumHome ?? join31(envPaths("qawolf").data, "appium");
180324
180499
  const timeoutMs3 = params?.options?.startTimeoutMs ?? defaultStartTimeoutMs;
180325
180500
  const bin = resolveAppiumBinFn(envDir);
180326
180501
  const port = await findFreePortFn();
@@ -180806,6 +180981,9 @@ function createAndroidLaunchContext({
180806
180981
  import path11 from "node:path";
180807
180982
  import { pathToFileURL } from "node:url";
180808
180983
 
180984
+ // src/shell/resolver/detectRuntimeCapabilities.ts
180985
+ import nodeModule2 from "node:module";
180986
+
180809
180987
  // src/shell/resolver/registerFlowModuleResolver.ts
180810
180988
  import nodeModule from "node:module";
180811
180989
 
@@ -180872,6 +181050,50 @@ function registerFlowModuleResolver() {
180872
181050
  registerHooks({ resolve: flowResolveHook });
180873
181051
  }
180874
181052
 
181053
+ // src/shell/resolver/detectRuntimeCapabilities.ts
181054
+ function detectRuntimeCapabilities() {
181055
+ return {
181056
+ isBun: globalThis.Bun !== undefined,
181057
+ hasSyncHooks: resolveRegisterHooks(nodeModule2) !== undefined,
181058
+ hasNativeTypeScript: Boolean(process.features.typescript),
181059
+ hasAsyncRegister: typeof nodeModule2.register === "function"
181060
+ };
181061
+ }
181062
+
181063
+ // src/shell/resolver/selectFlowLoaderStrategy.ts
181064
+ function selectFlowLoaderStrategy(caps) {
181065
+ if (caps.isBun)
181066
+ return "none";
181067
+ if (caps.hasNativeTypeScript) {
181068
+ return caps.hasSyncHooks ? "sync-alias" : "none";
181069
+ }
181070
+ return caps.hasAsyncRegister ? "oxc-transpile" : "unsupported";
181071
+ }
181072
+
181073
+ // src/shell/resolver/registerFlowLoader.ts
181074
+ async function defaultRegisterOxcLoader() {
181075
+ try {
181076
+ await import("@oxc-node/core/register");
181077
+ } catch (cause) {
181078
+ 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 });
181079
+ }
181080
+ }
181081
+ async function registerFlowLoader(deps = {}) {
181082
+ const {
181083
+ capabilities = detectRuntimeCapabilities(),
181084
+ registerSyncAlias = registerFlowModuleResolver,
181085
+ registerOxcLoader = defaultRegisterOxcLoader
181086
+ } = deps;
181087
+ const strategy = selectFlowLoaderStrategy(capabilities);
181088
+ if (strategy === "sync-alias") {
181089
+ registerSyncAlias();
181090
+ } else if (strategy === "oxc-transpile") {
181091
+ await registerOxcLoader();
181092
+ } else if (strategy === "unsupported") {
181093
+ 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.`);
181094
+ }
181095
+ }
181096
+
180875
181097
  // src/domains/runner/executorPlugin.ts
180876
181098
  import path10 from "node:path";
180877
181099
  var executorImportDetect = /from ['"]@qawolf\/(?:flows|testkit|emails)/;
@@ -180971,7 +181193,7 @@ async function loadFlowDefault(args) {
180971
181193
  depsRoot = ""
180972
181194
  } = args;
180973
181195
  if (bundleFlow === undefined) {
180974
- registerFlowModuleResolver();
181196
+ await registerFlowLoader();
180975
181197
  return importDefaultExport(pathToFileURL(flowPath).href, flowPath);
180976
181198
  }
180977
181199
  const code = await bundleFlow(flowPath, depsRoot);
@@ -181180,6 +181402,7 @@ async function runAndroidFlow({
181180
181402
  callback: async (flowDeps) => {
181181
181403
  const androidDeps = {
181182
181404
  ...Object.fromEntries(unsupportedAndroidDepNames.map((name) => [name, notSupported(name)])),
181405
+ ...deps.flowRuntimeDeps,
181183
181406
  inputs: flowDeps.flowInputs,
181184
181407
  workflowInputs: flowDeps.flowInputs,
181185
181408
  setOutput: flowDeps.setOutput,
@@ -181390,6 +181613,7 @@ async function runWebFlow({
181390
181613
  callback: async (flowDeps) => {
181391
181614
  const webDeps = {
181392
181615
  ...Object.fromEntries(unsupportedWebDepNames.map((name) => [name, notSupported(name)])),
181616
+ ...deps.flowRuntimeDeps,
181393
181617
  launch,
181394
181618
  launchWithGpu: launch,
181395
181619
  isGpuAvailable: () => false,
@@ -181423,14 +181647,14 @@ async function runWebFlow({
181423
181647
 
181424
181648
  // src/shell/embeddedWorkerCli.ts
181425
181649
  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";
181650
+ import { basename as basename6, join as join32 } from "node:path";
181427
181651
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
181428
181652
  function extractEmbeddedWorkerCli() {
181429
181653
  const assetPath = process.env[embeddedCliPathEnv];
181430
181654
  if (assetPath === undefined || assetPath === "")
181431
181655
  return;
181432
- const dir = join31(getDataDir(), "worker");
181433
- const dest = join31(dir, basename6(assetPath));
181656
+ const dir = join32(getDataDir(), "worker");
181657
+ const dest = join32(dir, basename6(assetPath));
181434
181658
  if (existsSync3(dest))
181435
181659
  return dest;
181436
181660
  mkdirSync2(dir, { recursive: true });
@@ -181675,6 +181899,12 @@ function createConsoleReporter(deps) {
181675
181899
  deps.stderr.write(`${formatted}
181676
181900
 
181677
181901
  `);
181902
+ const missingPackage = extractMissingPackage(errStr);
181903
+ if (missingPackage !== undefined) {
181904
+ deps.stderr.write(`${styleText3("yellow", `${indent}${runnerMessages.moduleNotFoundHint(missingPackage, deps.projectDir)}`)}
181905
+
181906
+ `);
181907
+ }
181678
181908
  const counts = tests.total > 0 ? `${tests.passed}/${tests.total} tests ` : "";
181679
181909
  deps.stdout.write(` ${styleText3("red", "✗")} ${counts}passed ${styleText3("dim", fmtDuration(durationMs))}
181680
181910
  `);
@@ -181727,7 +181957,7 @@ function createCompositeReporter(reporters) {
181727
181957
  };
181728
181958
  }
181729
181959
  // src/shell/reporter/createJUnitReporter.ts
181730
- import { join as join32 } from "node:path";
181960
+ import { join as join33 } from "node:path";
181731
181961
 
181732
181962
  // src/shell/reporter/junitXml.ts
181733
181963
  function escapeXml(str) {
@@ -181766,7 +181996,7 @@ ${suites}`;
181766
181996
 
181767
181997
  // src/shell/reporter/createJUnitReporter.ts
181768
181998
  function resolveJUnitOutputPath(junit, outputDir) {
181769
- return typeof junit === "string" && junit.trim() !== "" ? junit : join32(outputDir, "junit-report.xml");
181999
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join33(outputDir, "junit-report.xml");
181770
182000
  }
181771
182001
  function createJUnitReporter(deps) {
181772
182002
  const flows = [];
@@ -181795,7 +182025,11 @@ function createJUnitReporter(deps) {
181795
182025
  function buildRunReporter(flags, deps) {
181796
182026
  const stdout2 = deps.stdout ?? process.stdout;
181797
182027
  const stderr = deps.stderr ?? process.stderr;
181798
- const console2 = createConsoleReporter({ stdout: stdout2, stderr });
182028
+ const console2 = createConsoleReporter({
182029
+ stdout: stdout2,
182030
+ stderr,
182031
+ ...deps.projectDir !== undefined ? { projectDir: deps.projectDir } : {}
182032
+ });
181799
182033
  if (flags.junit === undefined)
181800
182034
  return console2;
181801
182035
  const junit = createJUnitReporter({
@@ -181811,7 +182045,15 @@ function buildRunReporter(flags, deps) {
181811
182045
 
181812
182046
  // src/commands/flows/buildFlowsRunDeps.ts
181813
182047
  function buildFlowsRunDeps(args) {
181814
- const { ctx, resolvedDir, android, runWebFlowDeps, flags } = args;
182048
+ const {
182049
+ ctx,
182050
+ resolvedDir,
182051
+ android,
182052
+ runWebFlowDeps,
182053
+ flowRuntimeDeps,
182054
+ flags,
182055
+ projectDir
182056
+ } = args;
181815
182057
  return {
181816
182058
  peekFlowMeta: makePeekFlowMeta(ctx.fs),
181817
182059
  installBrowsers: (innerCtx, browsers) => installBrowserList(innerCtx, browsers, {
@@ -181820,9 +182062,9 @@ function buildFlowsRunDeps(args) {
181820
182062
  playwrightCliPath: resolvePlaywrightCli(resolvedDir, process.platform)
181821
182063
  }),
181822
182064
  runWebFlow,
181823
- runWebFlowDeps,
182065
+ runWebFlowDeps: { ...runWebFlowDeps, flowRuntimeDeps },
181824
182066
  runAndroidFlow,
181825
- runAndroidFlowDeps: android.deps,
182067
+ runAndroidFlowDeps: { ...android.deps, flowRuntimeDeps },
181826
182068
  bootAndroid: android.boot,
181827
182069
  shutdownAndroid: android.shutdown,
181828
182070
  createPooledDispatch: makePooledDispatch(resolvedDir),
@@ -181832,7 +182074,8 @@ function buildFlowsRunDeps(args) {
181832
182074
  reporter: buildRunReporter(flags, {
181833
182075
  fs: ctx.fs,
181834
182076
  stdout: { write: (text3) => ctx.ui.write(text3) },
181835
- stderr: { write: (text3) => ctx.ui.write(text3) }
182077
+ stderr: { write: (text3) => ctx.ui.write(text3) },
182078
+ ...projectDir !== undefined ? { projectDir } : {}
181836
182079
  }),
181837
182080
  now: () => Date.now()
181838
182081
  };
@@ -181840,11 +182083,11 @@ function buildFlowsRunDeps(args) {
181840
182083
 
181841
182084
  // src/commands/flows/loadEnvFile.ts
181842
182085
  import { readFile } from "node:fs/promises";
181843
- import { join as join33 } from "node:path";
182086
+ import { join as join34 } from "node:path";
181844
182087
  async function loadEnvFile(envDir) {
181845
182088
  let content;
181846
182089
  try {
181847
- content = await readFile(join33(envDir, ".env"), "utf8");
182090
+ content = await readFile(join34(envDir, ".env"), "utf8");
181848
182091
  } catch (err) {
181849
182092
  if (isNoEntError(err))
181850
182093
  return;
@@ -181880,15 +182123,34 @@ async function runStagedFlows(args) {
181880
182123
  files,
181881
182124
  projectDir,
181882
182125
  depsRoot: runtimeEnv.depsRoot,
181883
- runRoot: runStagingRoot()
182126
+ runRoot: runStagingRoot(),
182127
+ onInstallStart: (depCount) => ctx.ui.info(runnerMessages.installingProjectDeps(depCount))
181884
182128
  });
182129
+ if (staged.outerHop.mode === "install") {
182130
+ const log31 = ctx.log("runner");
182131
+ for (const candidate of staged.outerHop.rejected) {
182132
+ log31.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
182133
+ }
182134
+ }
181885
182135
  const unregisterCleanup = ctx.signals.register(staged.cleanup);
181886
182136
  try {
181887
182137
  const resolvedDir = runtimeEnv.depsRoot;
181888
182138
  await deps.configureTestkit(resolvedDir);
181889
182139
  const android = createAndroidDeps(resolvedDir, ctx.signals);
181890
182140
  const runWebFlowDeps = await deps.runWebFlowDeps(resolvedDir, ctx.signals);
181891
- return await deps.flowsRun(ctx, staged.files, flags, buildFlowsRunDeps({ ctx, resolvedDir, android, runWebFlowDeps, flags }));
182141
+ const flowRuntimeDeps = await deps.createFlowRuntimeDeps({
182142
+ envDir: resolvedDir,
182143
+ ctx
182144
+ });
182145
+ return await deps.flowsRun(ctx, staged.files, flags, buildFlowsRunDeps({
182146
+ ctx,
182147
+ resolvedDir,
182148
+ android,
182149
+ runWebFlowDeps,
182150
+ flowRuntimeDeps,
182151
+ flags,
182152
+ projectDir
182153
+ }));
181892
182154
  } finally {
181893
182155
  unregisterCleanup();
181894
182156
  await staged.cleanup();
@@ -181903,6 +182165,7 @@ function makeDefaultDeps2(fs9) {
181903
182165
  prepareRunDir: (args) => prepareRunDir({ ...args, fs: fs9 }),
181904
182166
  configureTestkit,
181905
182167
  runWebFlowDeps: defaultRunWebFlowDeps,
182168
+ createFlowRuntimeDeps,
181906
182169
  flowsRun
181907
182170
  };
181908
182171
  }
@@ -181924,7 +182187,7 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
181924
182187
  }
181925
182188
 
181926
182189
  // src/commands/flows/hybridRunDefaults.ts
181927
- import { join as join34, resolve as resolve9 } from "node:path";
182190
+ import { join as join35, resolve as resolve9 } from "node:path";
181928
182191
  function makeDefaultHybridDeps(fs9) {
181929
182192
  return {
181930
182193
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
@@ -181933,7 +182196,8 @@ function makeDefaultHybridDeps(fs9) {
181933
182196
  prepareRunDir: (args) => prepareRunDir({ ...args, fs: fs9 }),
181934
182197
  configureTestkit,
181935
182198
  flowsRun,
181936
- runWebFlowDeps: defaultRunWebFlowDeps
182199
+ runWebFlowDeps: defaultRunWebFlowDeps,
182200
+ createFlowRuntimeDeps
181937
182201
  };
181938
182202
  }
181939
182203
  async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
@@ -181942,7 +182206,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
181942
182206
  if (validation !== "ok") {
181943
182207
  return { error: validation.error, exitCode: 2 };
181944
182208
  }
181945
- const envDir = resolve9(join34(".qawolf", flags.env));
182209
+ const envDir = resolve9(join35(".qawolf", flags.env));
181946
182210
  const patternArgs = buildPatternArgs(pattern);
181947
182211
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
181948
182212
  let files = await globFlows();
@@ -182015,7 +182279,16 @@ async function runWorker(signals) {
182015
182279
  if (input.flow.kind !== "web")
182016
182280
  throw new Error("worker subprocess currently supports web flows only");
182017
182281
  await configureTestkit(input.resolvedDir);
182018
- const runWebFlowDeps = await defaultRunWebFlowDeps(input.resolvedDir, signals);
182282
+ const fs9 = makeDefaultFs();
182283
+ const apiBaseUrl = process.env["QAWOLF_API_URL"]?.replace(/\/+$/, "") || "https://app.qawolf.com";
182284
+ const flowRuntimeDeps = await createFlowRuntimeDeps({
182285
+ envDir: input.resolvedDir,
182286
+ ctx: { apiBaseUrl, configDir: getConfigDir(), fs: fs9 }
182287
+ });
182288
+ const runWebFlowDeps = {
182289
+ ...await defaultRunWebFlowDeps(input.resolvedDir, signals),
182290
+ flowRuntimeDeps
182291
+ };
182019
182292
  const reporter = {};
182020
182293
  const deps = {
182021
182294
  peekFlowMeta: unavailable("peekFlowMeta"),
@@ -182073,7 +182346,7 @@ function registerFlowsCommand(program2, signals) {
182073
182346
  }
182074
182347
 
182075
182348
  // src/domains/init/init.ts
182076
- import { dirname as dirname12, join as join35, relative as relative7 } from "node:path";
182349
+ import { dirname as dirname12, join as join36, relative as relative7 } from "node:path";
182077
182350
 
182078
182351
  // src/domains/init/templates.ts
182079
182352
  var qawolfConfigTs = `export default {
@@ -182111,9 +182384,9 @@ function makeDefaultInitDeps() {
182111
182384
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
182112
182385
  ctx.ui.gap();
182113
182386
  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");
182387
+ const configPath = join36(deps.cwd, "qawolf.config.ts");
182388
+ const flowPath = join36(deps.cwd, "src", "flows", "example.flow.ts");
182389
+ const gitignorePath = join36(deps.cwd, ".qawolf", ".gitignore");
182117
182390
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
182118
182391
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
182119
182392
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -182137,7 +182410,7 @@ async function writeWithPrompt(ctx, filePath, content, yes, deps) {
182137
182410
  ctx.ui.step(initMessages.createdFile(relPath));
182138
182411
  }
182139
182412
  async function ensurePackageJson(ctx, yes, deps) {
182140
- const pkgPath = join35(deps.cwd, "package.json");
182413
+ const pkgPath = join36(deps.cwd, "package.json");
182141
182414
  if (!await deps.fs.pathExists(pkgPath)) {
182142
182415
  const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
182143
182416
  if (!confirmed2.ok || !confirmed2.value) {
@@ -182196,19 +182469,19 @@ Examples:
182196
182469
  }
182197
182470
 
182198
182471
  // src/commands/install/android.ts
182199
- import { join as join38 } from "node:path";
182472
+ import { join as join39 } from "node:path";
182200
182473
 
182201
182474
  // src/domains/install/android/index.ts
182202
182475
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
182203
182476
 
182204
182477
  // src/domains/install/android/avd.ts
182205
- import { join as join36 } from "node:path";
182478
+ import { join as join37 } from "node:path";
182206
182479
  async function installAvds(ctx, specs, deps) {
182207
182480
  const versionResult = await deps.spawn(deps.sdkManagerPath, ["--version"]);
182208
182481
  if (versionResult.exitCode !== 0) {
182209
182482
  throw new Error(installMessages.android.sdkmanagerNotFound(deps.sdkManagerPath));
182210
182483
  }
182211
- const licenseFile = join36(deps.androidHome, "licenses", "android-sdk-license");
182484
+ const licenseFile = join37(deps.androidHome, "licenses", "android-sdk-license");
182212
182485
  if (deps.checkExists(licenseFile)) {
182213
182486
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
182214
182487
  } else {
@@ -182222,7 +182495,7 @@ async function installAvds(ctx, specs, deps) {
182222
182495
  for (const spec of specs) {
182223
182496
  if (installedImages.has(spec.systemImage))
182224
182497
  continue;
182225
- const systemImg = join36(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182498
+ const systemImg = join37(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
182226
182499
  if (deps.checkExists(systemImg)) {
182227
182500
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
182228
182501
  installedImages.add(spec.systemImage);
@@ -182270,9 +182543,9 @@ async function installAvds(ctx, specs, deps) {
182270
182543
  }
182271
182544
 
182272
182545
  // src/domains/install/android/driver.ts
182273
- import { join as join37 } from "node:path";
182546
+ import { join as join38 } from "node:path";
182274
182547
  var appiumEnv = {
182275
- APPIUM_HOME: join37(envPaths("qawolf").data, "appium")
182548
+ APPIUM_HOME: join38(envPaths("qawolf").data, "appium")
182276
182549
  };
182277
182550
  async function installUiautomator2Driver(ctx, deps) {
182278
182551
  const listResult = await deps.spawn(deps.appiumBinPath, ["driver", "list", "--installed"], { env: appiumEnv });
@@ -182359,8 +182632,8 @@ async function handleInstallAndroid(ctx, pattern, envDir) {
182359
182632
  arch: process.arch,
182360
182633
  androidHome: androidHome2,
182361
182634
  checkExists: (path9) => fs9.existsSync(path9),
182362
- sdkManagerPath: join38(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182363
- avdManagerPath: join38(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182635
+ sdkManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "sdkmanager"),
182636
+ avdManagerPath: join39(androidHome2, "cmdline-tools", "latest", "bin", "avdmanager"),
182364
182637
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd ?? process.cwd(), undefined, fs9),
182365
182638
  peekFlowMeta: makePeekFlowMeta(fs9),
182366
182639
  resolveDepsRoot: async (files) => envDir ?? (await resolveDepsRoot({ files, fs: fs9 })).depsRoot,
@@ -182773,4 +183046,4 @@ createProgram({ signals }).parseAsync().catch(() => {
182773
183046
  process.exitCode = 1;
182774
183047
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
182775
183048
 
182776
- //# debugId=1EAFDAECED4A4A1164756E2164756E21
183049
+ //# debugId=2C8E0221ECEF946764756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qawolf/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.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,6 +58,7 @@
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
63
  "@qawolf/api-contracts": "0.1.0",
63
64
  "@qawolf/emails": "1.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
  }