@qawolf/cli 1.19.2 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.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 join36(s3) {
8517
+ BufferList.prototype.join = function join38(s3) {
8518
8518
  if (this.length === 0)
8519
8519
  return "";
8520
8520
  var p3 = this.head;
@@ -20220,8 +20220,8 @@ var require_readdir_glob = __commonJS((exports, module) => {
20220
20220
  useStat = true;
20221
20221
  }
20222
20222
  const filename = dir + "/" + name;
20223
- const relative6 = filename.slice(1);
20224
- const absolute = path3 + "/" + relative6;
20223
+ const relative8 = filename.slice(1);
20224
+ const absolute = path3 + "/" + relative8;
20225
20225
  let stats = null;
20226
20226
  if (useStat || followSymlinks) {
20227
20227
  stats = await stat2(absolute, followSymlinks);
@@ -20233,12 +20233,12 @@ var require_readdir_glob = __commonJS((exports, module) => {
20233
20233
  stats = { isDirectory: () => false };
20234
20234
  }
20235
20235
  if (stats.isDirectory()) {
20236
- if (!shouldSkip(relative6)) {
20237
- yield { relative: relative6, absolute, stats };
20236
+ if (!shouldSkip(relative8)) {
20237
+ yield { relative: relative8, absolute, stats };
20238
20238
  yield* exploreWalkAsync(filename, path3, followSymlinks, useStat, shouldSkip, false);
20239
20239
  }
20240
20240
  } else {
20241
- yield { relative: relative6, absolute, stats };
20241
+ yield { relative: relative8, absolute, stats };
20242
20242
  }
20243
20243
  }
20244
20244
  }
@@ -20303,11 +20303,11 @@ var require_readdir_glob = __commonJS((exports, module) => {
20303
20303
  }
20304
20304
  setTimeout(() => this._next(), 0);
20305
20305
  }
20306
- _shouldSkipDirectory(relative6) {
20307
- return this.skipMatchers.some((m4) => m4.match(relative6));
20306
+ _shouldSkipDirectory(relative8) {
20307
+ return this.skipMatchers.some((m4) => m4.match(relative8));
20308
20308
  }
20309
- _fileMatches(relative6, isDirectory) {
20310
- const file2 = relative6 + (isDirectory ? "/" : "");
20309
+ _fileMatches(relative8, isDirectory) {
20310
+ const file2 = relative8 + (isDirectory ? "/" : "");
20311
20311
  return (this.matchers.length === 0 || this.matchers.some((m4) => m4.match(file2))) && !this.ignoreMatchers.some((m4) => m4.match(file2)) && (!this.options.nodir || !isDirectory);
20312
20312
  }
20313
20313
  _next() {
@@ -20316,16 +20316,16 @@ var require_readdir_glob = __commonJS((exports, module) => {
20316
20316
  if (!obj.done) {
20317
20317
  const isDirectory = obj.value.stats.isDirectory();
20318
20318
  if (this._fileMatches(obj.value.relative, isDirectory)) {
20319
- let relative6 = obj.value.relative;
20319
+ let relative8 = obj.value.relative;
20320
20320
  let absolute = obj.value.absolute;
20321
20321
  if (this.options.mark && isDirectory) {
20322
- relative6 += "/";
20322
+ relative8 += "/";
20323
20323
  absolute += "/";
20324
20324
  }
20325
20325
  if (this.options.stat) {
20326
- this.emit("match", { relative: relative6, absolute, stat: obj.value.stats });
20326
+ this.emit("match", { relative: relative8, absolute, stat: obj.value.stats });
20327
20327
  } else {
20328
- this.emit("match", { relative: relative6, absolute });
20328
+ this.emit("match", { relative: relative8, absolute });
20329
20329
  }
20330
20330
  }
20331
20331
  this._next(this.iterator);
@@ -23182,7 +23182,7 @@ var require_BufferList2 = __commonJS((exports, module) => {
23182
23182
  this.head = this.tail = null;
23183
23183
  this.length = 0;
23184
23184
  };
23185
- BufferList.prototype.join = function join36(s3) {
23185
+ BufferList.prototype.join = function join38(s3) {
23186
23186
  if (this.length === 0)
23187
23187
  return "";
23188
23188
  var p3 = this.head;
@@ -36236,7 +36236,7 @@ var require_commonjs4 = __commonJS((exports) => {
36236
36236
  var TYPEMASK = 1023;
36237
36237
  var entToType = (s3) => s3.isFile() ? IFREG : s3.isDirectory() ? IFDIR : s3.isSymbolicLink() ? IFLNK : s3.isCharacterDevice() ? IFCHR : s3.isBlockDevice() ? IFBLK : s3.isSocket() ? IFSOCK : s3.isFIFO() ? IFIFO : UNKNOWN;
36238
36238
  var normalizeCache = new Map;
36239
- var normalize3 = (s3) => {
36239
+ var normalize4 = (s3) => {
36240
36240
  const c3 = normalizeCache.get(s3);
36241
36241
  if (c3)
36242
36242
  return c3;
@@ -36249,7 +36249,7 @@ var require_commonjs4 = __commonJS((exports) => {
36249
36249
  const c3 = normalizeNocaseCache.get(s3);
36250
36250
  if (c3)
36251
36251
  return c3;
36252
- const n2 = normalize3(s3.toLowerCase());
36252
+ const n2 = normalize4(s3.toLowerCase());
36253
36253
  normalizeNocaseCache.set(s3, n2);
36254
36254
  return n2;
36255
36255
  };
@@ -36370,7 +36370,7 @@ var require_commonjs4 = __commonJS((exports) => {
36370
36370
  }
36371
36371
  constructor(name, type = UNKNOWN, root, roots, nocase, children, opts) {
36372
36372
  this.name = name;
36373
- this.#matchName = nocase ? normalizeNocase(name) : normalize3(name);
36373
+ this.#matchName = nocase ? normalizeNocase(name) : normalize4(name);
36374
36374
  this.#type = type & TYPEMASK;
36375
36375
  this.nocase = nocase;
36376
36376
  this.roots = roots;
@@ -36431,7 +36431,7 @@ var require_commonjs4 = __commonJS((exports) => {
36431
36431
  return this.parent || this;
36432
36432
  }
36433
36433
  const children = this.children();
36434
- const name = this.nocase ? normalizeNocase(pathPart) : normalize3(pathPart);
36434
+ const name = this.nocase ? normalizeNocase(pathPart) : normalize4(pathPart);
36435
36435
  for (const p3 of children) {
36436
36436
  if (p3.#matchName === name) {
36437
36437
  return p3;
@@ -36568,7 +36568,7 @@ var require_commonjs4 = __commonJS((exports) => {
36568
36568
  return !!(this.#type & ENOENT);
36569
36569
  }
36570
36570
  isNamed(n2) {
36571
- return !this.nocase ? this.#matchName === normalize3(n2) : this.#matchName === normalizeNocase(n2);
36571
+ return !this.nocase ? this.#matchName === normalize4(n2) : this.#matchName === normalizeNocase(n2);
36572
36572
  }
36573
36573
  async readlink() {
36574
36574
  const target = this.#linkTarget;
@@ -36695,7 +36695,7 @@ var require_commonjs4 = __commonJS((exports) => {
36695
36695
  #readdirMaybePromoteChild(e, c3) {
36696
36696
  for (let p3 = c3.provisional;p3 < c3.length; p3++) {
36697
36697
  const pchild = c3[p3];
36698
- const name = this.nocase ? normalizeNocase(e.name) : normalize3(e.name);
36698
+ const name = this.nocase ? normalizeNocase(e.name) : normalize4(e.name);
36699
36699
  if (name !== pchild.#matchName) {
36700
36700
  continue;
36701
36701
  }
@@ -37688,10 +37688,10 @@ var require_ignore = __commonJS((exports) => {
37688
37688
  ignored(p3) {
37689
37689
  const fullpath = p3.fullpath();
37690
37690
  const fullpaths = `${fullpath}/`;
37691
- const relative6 = p3.relative() || ".";
37692
- const relatives = `${relative6}/`;
37691
+ const relative8 = p3.relative() || ".";
37692
+ const relatives = `${relative8}/`;
37693
37693
  for (const m4 of this.relative) {
37694
- if (m4.match(relative6) || m4.match(relatives))
37694
+ if (m4.match(relative8) || m4.match(relatives))
37695
37695
  return true;
37696
37696
  }
37697
37697
  for (const m4 of this.absolute) {
@@ -37702,9 +37702,9 @@ var require_ignore = __commonJS((exports) => {
37702
37702
  }
37703
37703
  childrenIgnored(p3) {
37704
37704
  const fullpath = p3.fullpath() + "/";
37705
- const relative6 = (p3.relative() || ".") + "/";
37705
+ const relative8 = (p3.relative() || ".") + "/";
37706
37706
  for (const m4 of this.relativeChildren) {
37707
- if (m4.match(relative6))
37707
+ if (m4.match(relative8))
37708
37708
  return true;
37709
37709
  }
37710
37710
  for (const m4 of this.absoluteChildren) {
@@ -101117,10 +101117,10 @@ function compareDocumentPosition(nodeA, nodeB) {
101117
101117
  function uniqueSort(nodes) {
101118
101118
  nodes = nodes.filter((node2, i2, arr) => !arr.includes(node2, i2 + 1));
101119
101119
  nodes.sort((a3, b4) => {
101120
- const relative6 = compareDocumentPosition(a3, b4);
101121
- if (relative6 & DocumentPosition.PRECEDING) {
101120
+ const relative8 = compareDocumentPosition(a3, b4);
101121
+ if (relative8 & DocumentPosition.PRECEDING) {
101122
101122
  return -1;
101123
- } else if (relative6 & DocumentPosition.FOLLOWING) {
101123
+ } else if (relative8 & DocumentPosition.FOLLOWING) {
101124
101124
  return 1;
101125
101125
  }
101126
101126
  return 0;
@@ -134710,7 +134710,7 @@ var require_snapshot_utils = __commonJS((exports, module) => {
134710
134710
  // node_modules/undici/lib/mock/snapshot-recorder.js
134711
134711
  var require_snapshot_recorder = __commonJS((exports, module) => {
134712
134712
  var { writeFile, readFile, mkdir } = __require("node:fs/promises");
134713
- var { dirname: dirname12, resolve: resolve10 } = __require("node:path");
134713
+ var { dirname: dirname13, resolve: resolve10 } = __require("node:path");
134714
134714
  var { setTimeout: setTimeout3, clearTimeout: clearTimeout2 } = __require("node:timers");
134715
134715
  var { InvalidArgumentError: InvalidArgumentError3, UndiciError } = require_errors7();
134716
134716
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -134905,7 +134905,7 @@ var require_snapshot_recorder = __commonJS((exports, module) => {
134905
134905
  throw new InvalidArgumentError3("Snapshot path is required");
134906
134906
  }
134907
134907
  const resolvedPath = resolve10(path4);
134908
- await mkdir(dirname12(resolvedPath), { recursive: true });
134908
+ await mkdir(dirname13(resolvedPath), { recursive: true });
134909
134909
  const data2 = Array.from(this.#snapshots.entries()).map(([hash2, snapshot]) => ({
134910
134910
  hash: hash2,
134911
134911
  snapshot
@@ -160719,11 +160719,11 @@ m2: ${this.mapper2.__debugToString().split(`
160719
160719
  return height2 + 1;
160720
160720
  }
160721
160721
  function computeColumnWidths(height2) {
160722
- const columns2 = fill(Array(height2), 0);
160722
+ const columns = fill(Array(height2), 0);
160723
160723
  for (const node2 of nodes) {
160724
- columns2[node2.level] = Math.max(columns2[node2.level], node2.text.length);
160724
+ columns[node2.level] = Math.max(columns[node2.level], node2.text.length);
160725
160725
  }
160726
- return columns2;
160726
+ return columns;
160727
160727
  }
160728
160728
  function computeLanes(node2, lane) {
160729
160729
  if (node2.lane === -1) {
@@ -165092,11 +165092,11 @@ ${lanes.join(`
165092
165092
  return toComponents;
165093
165093
  }
165094
165094
  const components = toComponents.slice(start);
165095
- const relative8 = [];
165095
+ const relative10 = [];
165096
165096
  for (;start < fromComponents.length; start++) {
165097
- relative8.push("..");
165097
+ relative10.push("..");
165098
165098
  }
165099
- return ["", ...relative8, ...components];
165099
+ return ["", ...relative10, ...components];
165100
165100
  }
165101
165101
  function getRelativePathFromDirectory(fromDirectory, to2, getCanonicalFileNameOrIgnoreCase) {
165102
165102
  Debug.assert(getRootLength(fromDirectory) > 0 === getRootLength(to2) > 0, "Paths must either both be absolute or both be relative");
@@ -202471,9 +202471,9 @@ ${lanes.join(`
202471
202471
  if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) {
202472
202472
  return;
202473
202473
  }
202474
- const relative8 = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
202474
+ const relative10 = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
202475
202475
  for (const symlinkDirectory of symlinkDirectories) {
202476
- const option = resolvePath(symlinkDirectory, relative8);
202476
+ const option = resolvePath(symlinkDirectory, relative10);
202477
202477
  const result2 = cb(option, target === referenceRedirect);
202478
202478
  shouldFilterIgnoredPaths = true;
202479
202479
  if (result2)
@@ -264788,10 +264788,10 @@ ${lanes.join(`
264788
264788
  }
264789
264789
  function getDefaultLibFilePriority(a3) {
264790
264790
  if (containsPath(defaultLibraryPath, a3.fileName, false)) {
264791
- const basename7 = getBaseFileName(a3.fileName);
264792
- if (basename7 === "lib.d.ts" || basename7 === "lib.es6.d.ts")
264791
+ const basename8 = getBaseFileName(a3.fileName);
264792
+ if (basename8 === "lib.d.ts" || basename8 === "lib.es6.d.ts")
264793
264793
  return 0;
264794
- const name = removeSuffix(removePrefix(basename7, "lib."), ".d.ts");
264794
+ const name = removeSuffix(removePrefix(basename8, "lib."), ".d.ts");
264795
264795
  const index2 = libs.indexOf(name);
264796
264796
  if (index2 !== -1)
264797
264797
  return index2 + 1;
@@ -317475,8 +317475,8 @@ ${options.prefix}` : `
317475
317475
  }
317476
317476
  };
317477
317477
  for (const file2 of files) {
317478
- const basename7 = getBaseFileName(file2);
317479
- if (basename7 === "package.json" || basename7 === "bower.json") {
317478
+ const basename8 = getBaseFileName(file2);
317479
+ if (basename8 === "package.json" || basename8 === "bower.json") {
317480
317480
  createProjectWatcher(file2, "FileWatcher");
317481
317481
  continue;
317482
317482
  }
@@ -320363,8 +320363,8 @@ All files are: ${JSON.stringify(names)}`, "Err");
320363
320363
  const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory));
320364
320364
  if (!fileOrDirectoryPath)
320365
320365
  return;
320366
- const basename7 = getBaseFileName(fileOrDirectoryPath);
320367
- if (((_a6 = result.affectedModuleSpecifierCacheProjects) == null ? undefined : _a6.size) && (basename7 === "package.json" || basename7 === "node_modules")) {
320366
+ const basename8 = getBaseFileName(fileOrDirectoryPath);
320367
+ if (((_a6 = result.affectedModuleSpecifierCacheProjects) == null ? undefined : _a6.size) && (basename8 === "package.json" || basename8 === "node_modules")) {
320368
320368
  result.affectedModuleSpecifierCacheProjects.forEach((project) => {
320369
320369
  var _a22;
320370
320370
  (_a22 = project.getModuleSpecifierCache()) == null || _a22.clear();
@@ -325953,6 +325953,7 @@ var environmentsMessages = {
325953
325953
  resolvedAlias: (ref, id) => `Environment ${ref} resolved to ${id}.`,
325954
325954
  couldNotResolve: (ref, detail) => `Could not resolve environment ${ref}: ${detail} If ${ref} is an alias, note that aliases require a team API key.`,
325955
325955
  aborted: "Aborted; no environment selected.",
325956
+ usingPulledEnv: (env) => `Could not reach the platform to resolve environment ${env}; using the copy pulled into .qawolf/${env}.`,
325956
325957
  tooManyPages: (pages) => `Stopped listing environments after ${pages} pages. Pass --env explicitly.`
325957
325958
  };
325958
325959
  // src/core/pluralize.ts
@@ -325967,7 +325968,23 @@ var flowsMessages = {
325967
325968
  flowCount: (count) => pluralize(count, "flow"),
325968
325969
  list: {
325969
325970
  remoteRequiresEnv: "--remote requires an environment. Pass --env <env> or set QAWOLF_ENVIRONMENT.",
325970
- flagsRequireRemote: "--env and --include-drafts require --remote"
325971
+ draftsRequireRemote: "--include-drafts requires --remote"
325972
+ },
325973
+ selectors: {
325974
+ tagsNotCached: "No cached tags found. Run 'qawolf flows pull --env <env>' to cache them, or pass --remote to read tags from the platform.",
325975
+ tagsUnavailable: (env) => `Could not reach the platform and no tags are cached for environment '${env}'. Run 'qawolf flows pull --env ${env}' while online.`,
325976
+ usingCachedTags: (fetchedAt) => `Could not reach the platform. Using tags cached at ${fetchedAt}; tag names were not validated.`,
325977
+ unknownTag: (name, suggestion) => suggestion === undefined ? `No tag named '${name}' on this team. Run 'qawolf tag list' to see available tags.` : `No tag named '${name}' on this team. Did you mean '${suggestion}'?`,
325978
+ unknownPulledEnv: (name, pulled, suggestion) => {
325979
+ const known = pulled.length === 0 ? "No environments have been pulled yet." : `Pulled environments: ${pulled.join(", ")}.`;
325980
+ const hint = suggestion === undefined ? "" : ` Did you mean '${suggestion}'?`;
325981
+ return `No pulled environment named '${name}'.${hint} ${known}`;
325982
+ },
325983
+ allEnvsWithEnv: "--all-envs has no effect with --env: the run is already scoped to that environment.",
325984
+ allEnvsWithoutTag: "--all-envs has no effect without --tag: a pattern run already includes every environment.",
325985
+ chooseEnv: "Which environment should these flows run against?",
325986
+ ambiguousEnvs: (labels) => `The selection matches flows in several pulled environments: ${labels.join(", ")}. Pass --env <name> to choose one, or --all-envs to run every match.`,
325987
+ noFlowsSelected: (selectors) => `No flows matched tags ${selectors.tags.join(", ")}.`
325971
325988
  },
325972
325989
  run: {
325973
325990
  requiresEnv: "An environment is required. Pass --env <env> or set QAWOLF_ENVIRONMENT."
@@ -325976,6 +325993,7 @@ var flowsMessages = {
325976
325993
  requiresEnv: "An environment is required. Pass --env <env> or set QAWOLF_ENVIRONMENT.",
325977
325994
  downloadingBundle: "Downloading flows bundle",
325978
325995
  fetchingEnvVars: "Fetching environment variables",
325996
+ fetchingTags: "Fetching flow tags",
325979
325997
  downloadComplete: "Downloaded flows bundle and environment variables",
325980
325998
  needsYesError: "Re-run with --yes to overwrite locally-modified files",
325981
325999
  aborted: "Aborted; no changes.",
@@ -341138,10 +341156,12 @@ async function requestWithRetry(args) {
341138
341156
  const retryable = result.error.kind === "network" || result.error.kind === "timeout";
341139
341157
  if (backoff === undefined || !retryable) {
341140
341158
  const mayHaveArrived = result.error.kind !== "http";
341159
+ const unreachable = result.error.kind === "network" || result.error.kind === "timeout";
341141
341160
  return {
341142
341161
  ok: false,
341143
341162
  ...args.describe(result.error),
341144
- ...mayHaveArrived ? { mayHaveArrived } : {}
341163
+ ...mayHaveArrived ? { mayHaveArrived } : {},
341164
+ ...unreachable ? { unreachable } : {}
341145
341165
  };
341146
341166
  }
341147
341167
  await sleep2(backoff);
@@ -346136,7 +346156,7 @@ function startUpdateCheck(deps) {
346136
346156
  // package.json
346137
346157
  var package_default = {
346138
346158
  name: "@qawolf/cli",
346139
- version: "1.19.2",
346159
+ version: "1.20.1",
346140
346160
  description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
346141
346161
  keywords: [
346142
346162
  "automation",
@@ -348625,9 +348645,280 @@ Examples:
348625
348645
  });
348626
348646
  }
348627
348647
 
348648
+ // src/domains/runner/runFlagParsers.ts
348649
+ function parseInteger(name, options = {}) {
348650
+ return (value) => {
348651
+ const n2 = Number.parseInt(value, 10);
348652
+ if (!Number.isFinite(n2) || String(n2) !== value) {
348653
+ throw new InvalidArgumentError(`${name} must be an integer`);
348654
+ }
348655
+ if (options.min !== undefined && n2 < options.min) {
348656
+ throw new InvalidArgumentError(`${name} must be >= ${options.min}`);
348657
+ }
348658
+ return n2;
348659
+ };
348660
+ }
348661
+ function parseEnum(name, values) {
348662
+ return (value) => {
348663
+ if (!values.includes(value)) {
348664
+ throw new InvalidArgumentError(`${name} must be one of: ${values.join(", ")}`);
348665
+ }
348666
+ return value;
348667
+ };
348668
+ }
348669
+ function collectValue(value, previous) {
348670
+ return [...previous, value];
348671
+ }
348672
+
348673
+ // src/shell/manifest/pulledEnv.ts
348674
+ import { join as join22 } from "node:path";
348675
+
348676
+ // src/shell/manifest/io.ts
348677
+ import { createHash as createHash2 } from "node:crypto";
348678
+ import { join as join21 } from "node:path";
348679
+ var manifestFilename = ".manifest.json";
348680
+ var flowEntrySchema = exports_external.object({
348681
+ path: exports_external.string(),
348682
+ contentHash: exports_external.string(),
348683
+ tags: exports_external.array(exports_external.string()).optional()
348684
+ });
348685
+ var manifestSchema = exports_external.object({
348686
+ envId: exports_external.string(),
348687
+ envSlug: exports_external.string().optional(),
348688
+ envName: exports_external.string().optional(),
348689
+ fetchedAt: exports_external.string(),
348690
+ envVarsFetchedAt: exports_external.string().optional(),
348691
+ cliFlowsVersion: exports_external.string(),
348692
+ qawolfCommitSha: exports_external.string().optional(),
348693
+ qawolfCommittedAt: exports_external.string().optional(),
348694
+ tagsFetchedAt: exports_external.string().optional(),
348695
+ flows: exports_external.array(flowEntrySchema)
348696
+ });
348697
+ async function readManifest(envDir, fs3 = makeDefaultFs()) {
348698
+ let raw;
348699
+ try {
348700
+ raw = await fs3.readFile(join21(envDir, manifestFilename));
348701
+ } catch (err) {
348702
+ if (isNoEntError(err))
348703
+ return "missing";
348704
+ throw err;
348705
+ }
348706
+ let parsed;
348707
+ try {
348708
+ parsed = JSON.parse(raw);
348709
+ } catch {
348710
+ return "malformed";
348711
+ }
348712
+ const result = manifestSchema.safeParse(parsed);
348713
+ if (!result.success)
348714
+ return "malformed";
348715
+ return {
348716
+ envId: result.data.envId,
348717
+ envSlug: result.data.envSlug,
348718
+ envName: result.data.envName,
348719
+ fetchedAt: result.data.fetchedAt,
348720
+ envVarsFetchedAt: result.data.envVarsFetchedAt,
348721
+ cliFlowsVersion: result.data.cliFlowsVersion,
348722
+ qawolfCommitSha: result.data.qawolfCommitSha,
348723
+ qawolfCommittedAt: result.data.qawolfCommittedAt,
348724
+ tagsFetchedAt: result.data.tagsFetchedAt,
348725
+ flows: result.data.flows.map((flow) => ({
348726
+ path: flow.path,
348727
+ contentHash: flow.contentHash,
348728
+ tags: flow.tags
348729
+ }))
348730
+ };
348731
+ }
348732
+ async function writeManifest(envDir, manifest, fs3 = makeDefaultFs()) {
348733
+ const body = JSON.stringify(manifest, undefined, 2);
348734
+ await fs3.writeFile(join21(envDir, manifestFilename), `${body}
348735
+ `);
348736
+ }
348737
+ function hashFile(absPath, fs3 = makeDefaultFs()) {
348738
+ return new Promise((resolve5, reject) => {
348739
+ const hash2 = createHash2("sha256");
348740
+ const stream = fs3.createReadStream(absPath);
348741
+ stream.on("data", (chunk) => hash2.update(chunk));
348742
+ stream.on("end", () => resolve5(hash2.digest("hex")));
348743
+ stream.on("error", reject);
348744
+ });
348745
+ }
348746
+
348747
+ // src/shell/manifest/pulledEnv.ts
348748
+ async function findPulledEnv(ref, cwd, fs3 = makeDefaultFs()) {
348749
+ for (const envDir of await listPulledEnvDirs(cwd, fs3)) {
348750
+ const manifest = await readManifest(envDir, fs3);
348751
+ if (typeof manifest === "string")
348752
+ continue;
348753
+ if (manifest.envId === ref || manifest.envSlug === ref || manifest.envName === ref) {
348754
+ return { dir: envDir, envId: manifest.envId };
348755
+ }
348756
+ }
348757
+ return;
348758
+ }
348759
+ async function listPulledEnvDirs(cwd, fs3 = makeDefaultFs()) {
348760
+ const root = join22(cwd, ".qawolf");
348761
+ let entries;
348762
+ try {
348763
+ entries = await fs3.readdirWithTypes(root);
348764
+ } catch (err) {
348765
+ if (isNoEntError(err))
348766
+ return [];
348767
+ throw err;
348768
+ }
348769
+ const dirs = [];
348770
+ for (const entry of entries) {
348771
+ if (!entry.isDirectory())
348772
+ continue;
348773
+ const envDir = join22(root, entry.name);
348774
+ const manifest = await readManifest(envDir, fs3);
348775
+ if (typeof manifest === "string")
348776
+ continue;
348777
+ dirs.push(envDir);
348778
+ }
348779
+ return dirs;
348780
+ }
348781
+
348628
348782
  // src/domains/flows/list.ts
348629
348783
  import path2 from "node:path";
348630
348784
 
348785
+ // src/core/flowSelectors.ts
348786
+ function hasSelectors(selectors) {
348787
+ return selectors.tags.length > 0;
348788
+ }
348789
+ function matchesSelectors(flow, selectors) {
348790
+ if (selectors.tags.length === 0)
348791
+ return true;
348792
+ return selectors.tags.some((tag) => flow.tags?.includes(tag));
348793
+ }
348794
+
348795
+ // src/core/repoRelativePath.ts
348796
+ import { basename as basename3, dirname as dirname6, relative as relative3 } from "node:path";
348797
+ var toPosix = (path2) => path2.replaceAll("\\", "/");
348798
+ function findPulledEnvDir(flowAbsPath) {
348799
+ let current = dirname6(flowAbsPath);
348800
+ while (current !== dirname6(current)) {
348801
+ const parent = dirname6(current);
348802
+ if (basename3(parent) === ".qawolf")
348803
+ return current;
348804
+ current = parent;
348805
+ }
348806
+ return;
348807
+ }
348808
+ function toRepoRelativePath(flowAbsPath, cwd) {
348809
+ const envDir = findPulledEnvDir(flowAbsPath);
348810
+ return toPosix(relative3(envDir ?? cwd, flowAbsPath));
348811
+ }
348812
+
348813
+ // src/domains/flows/envLabels.ts
348814
+ function envLabelFor(file2, labels) {
348815
+ const dir = findPulledEnvDir(file2);
348816
+ return dir === undefined ? undefined : labels.get(dir);
348817
+ }
348818
+ async function readEnvLabels(files, readEnvLabel) {
348819
+ const dirs = new Set(files.map(findPulledEnvDir).filter((dir) => dir !== undefined));
348820
+ const labels = new Map;
348821
+ for (const dir of dirs)
348822
+ labels.set(dir, await readEnvLabel(dir));
348823
+ return labels;
348824
+ }
348825
+
348826
+ // src/core/suggestNearMiss.ts
348827
+ function normalize3(value) {
348828
+ return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
348829
+ }
348830
+ function editDistance(a2, b2) {
348831
+ let row = Array.from({ length: b2.length + 1 }, (_3, j) => j);
348832
+ for (let i2 = 1;i2 <= a2.length; i2 += 1) {
348833
+ const next = [i2];
348834
+ for (let j = 1;j <= b2.length; j += 1) {
348835
+ const substitution = (row[j - 1] ?? 0) + (a2[i2 - 1] === b2[j - 1] ? 0 : 1);
348836
+ const deletion = (row[j] ?? 0) + 1;
348837
+ const insertion = (next[j - 1] ?? 0) + 1;
348838
+ next.push(Math.min(substitution, deletion, insertion));
348839
+ }
348840
+ row = next;
348841
+ }
348842
+ return row[b2.length] ?? 0;
348843
+ }
348844
+ function distanceBudget(length) {
348845
+ return length >= 4 ? 2 : 1;
348846
+ }
348847
+ function suggestNearMiss(input, candidates) {
348848
+ if (candidates.includes(input))
348849
+ return;
348850
+ const sorted = [...candidates].sort();
348851
+ const normalizedInput = normalize3(input);
348852
+ const presentationMatch = sorted.find((candidate) => normalize3(candidate) === normalizedInput);
348853
+ if (presentationMatch !== undefined)
348854
+ return presentationMatch;
348855
+ let best;
348856
+ let bestDistance = Number.POSITIVE_INFINITY;
348857
+ for (const candidate of sorted) {
348858
+ const distance = editDistance(normalizedInput, normalize3(candidate));
348859
+ if (distance < bestDistance) {
348860
+ bestDistance = distance;
348861
+ best = candidate;
348862
+ }
348863
+ }
348864
+ return bestDistance <= distanceBudget(normalizedInput.length) ? best : undefined;
348865
+ }
348866
+
348867
+ // src/domains/flows/selectPulledEnv.ts
348868
+ async function selectPulledEnv(args) {
348869
+ const found = await args.findPulledEnv(args.ref);
348870
+ if (found !== undefined) {
348871
+ return {
348872
+ kind: "selected",
348873
+ files: args.files.filter((file2) => findPulledEnvDir(file2) === found.dir)
348874
+ };
348875
+ }
348876
+ const dirs = await args.listPulledEnvDirs();
348877
+ const labels = (await Promise.all(dirs.map((dir) => args.readEnvLabel(dir)))).sort();
348878
+ return {
348879
+ kind: "unknown",
348880
+ result: {
348881
+ error: flowsMessages.selectors.unknownPulledEnv(args.ref, labels, suggestNearMiss(args.ref, labels)),
348882
+ exitCode: exitCodes.invalidArgs
348883
+ }
348884
+ };
348885
+ }
348886
+
348887
+ // src/domains/flows/explainEmptySelection.ts
348888
+ function explainEmptySelection(selectors, knownTags) {
348889
+ if (knownTags !== undefined) {
348890
+ const unknownTag = selectors.tags.find((tag) => !knownTags.includes(tag));
348891
+ if (unknownTag !== undefined) {
348892
+ return flowsMessages.selectors.unknownTag(unknownTag, suggestNearMiss(unknownTag, knownTags));
348893
+ }
348894
+ }
348895
+ return flowsMessages.selectors.noFlowsSelected(selectors);
348896
+ }
348897
+
348898
+ // src/domains/flows/selectorGuards.ts
348899
+ function tagsNotCachedResult(selectors, cachedTags) {
348900
+ if (!hasSelectors(selectors) || cachedTags.size > 0)
348901
+ return;
348902
+ return {
348903
+ error: flowsMessages.selectors.tagsNotCached,
348904
+ exitCode: exitCodes.network
348905
+ };
348906
+ }
348907
+ function tagsUnavailableResult(envId) {
348908
+ return {
348909
+ error: flowsMessages.selectors.tagsUnavailable(envId),
348910
+ exitCode: exitCodes.network
348911
+ };
348912
+ }
348913
+ async function emptySelectionResult(selectors, matched, fetchKnownTags) {
348914
+ if (matched > 0 || !hasSelectors(selectors))
348915
+ return;
348916
+ return {
348917
+ error: explainEmptySelection(selectors, await fetchKnownTags?.()),
348918
+ exitCode: exitCodes.invalidArgs
348919
+ };
348920
+ }
348921
+
348631
348922
  // src/core/renderTable.ts
348632
348923
  var ansiBold = "\x1B[1m";
348633
348924
  var ansiReset = "\x1B[0m";
@@ -348648,29 +348939,75 @@ function renderTable(options) {
348648
348939
  }
348649
348940
 
348650
348941
  // src/domains/flows/renderListTable.ts
348651
- var columns = [
348652
- { header: "name", value: (row) => row.name },
348653
- { header: "target", value: (row) => row.target },
348654
- { header: "file", value: (row) => row.file }
348655
- ];
348942
+ var nameColumn = {
348943
+ header: "name",
348944
+ value: (row) => row.name
348945
+ };
348946
+ var targetColumn = {
348947
+ header: "target",
348948
+ value: (row) => row.target
348949
+ };
348950
+ var envColumn = {
348951
+ header: "env",
348952
+ value: (row) => row.env ?? ""
348953
+ };
348954
+ var tagsColumn = {
348955
+ header: "tags",
348956
+ value: (row) => (row.tags ?? []).join(", ")
348957
+ };
348958
+ var fileColumn = {
348959
+ header: "file",
348960
+ value: (row) => row.file
348961
+ };
348656
348962
  function renderListTable(rows, boldHeader) {
348963
+ const someTagged = rows.some((row) => (row.tags ?? []).length > 0);
348964
+ const severalEnvs = new Set(rows.map((row) => row.env)).size > 1;
348965
+ const columns = [
348966
+ nameColumn,
348967
+ targetColumn,
348968
+ ...severalEnvs ? [envColumn] : [],
348969
+ ...someTagged ? [tagsColumn] : [],
348970
+ fileColumn
348971
+ ];
348657
348972
  return renderTable({ boldHeader, columns, rows });
348658
348973
  }
348659
348974
 
348660
348975
  // src/domains/flows/list.ts
348661
- async function flowsList(ctx, pattern, deps) {
348976
+ async function flowsList(ctx, pattern, deps, selectors = { tags: [] }) {
348662
348977
  const patterns = pattern ? [pattern] : [];
348663
- const files = await deps.expandPatterns(patterns, deps.cwd);
348664
- const items = [];
348978
+ let files = await deps.expandPatterns(patterns, deps.cwd);
348979
+ if (selectors.env !== undefined) {
348980
+ const selection = await selectPulledEnv({
348981
+ files,
348982
+ ref: selectors.env,
348983
+ findPulledEnv: deps.findPulledEnv,
348984
+ listPulledEnvDirs: deps.listPulledEnvDirs,
348985
+ readEnvLabel: deps.readEnvLabel
348986
+ });
348987
+ if (selection.kind === "unknown")
348988
+ return selection.result;
348989
+ files = selection.files;
348990
+ }
348991
+ const cachedTags = await deps.readCachedTags(files);
348992
+ const envLabels = await readEnvLabels(files, deps.readEnvLabel);
348993
+ const notCached = tagsNotCachedResult(selectors, cachedTags);
348994
+ if (notCached !== undefined)
348995
+ return notCached;
348996
+ const all = [];
348665
348997
  for await (const { file: file2, ...meta3 } of batchMap(files, async (f2) => ({ file: f2, ...await deps.peekFlowMeta(f2) }), flowBatchSize)) {
348666
- items.push({
348998
+ all.push({
348667
348999
  file: path2.relative(deps.cwd, file2),
348668
349000
  name: meta3.name ?? flowBasename(file2),
348669
- tags: [],
349001
+ env: envLabelFor(file2, envLabels),
349002
+ tags: cachedTags.get(file2),
348670
349003
  target: meta3.target,
348671
349004
  browser: meta3.target ? targetToBrowser(meta3.target) : undefined
348672
349005
  });
348673
349006
  }
349007
+ const items = all.filter((item) => matchesSelectors(item, selectors));
349008
+ const empty2 = await emptySelectionResult(selectors, items.length, undefined);
349009
+ if (empty2 !== undefined)
349010
+ return empty2;
348674
349011
  if (ctx.ui.mode === "json") {
348675
349012
  ctx.ui.json(items);
348676
349013
  return;
@@ -348682,6 +349019,8 @@ async function flowsList(ctx, pattern, deps) {
348682
349019
  const rows = items.map((it) => ({
348683
349020
  name: it.name,
348684
349021
  target: it.target ?? "",
349022
+ env: it.env,
349023
+ tags: it.tags,
348685
349024
  file: it.file
348686
349025
  }));
348687
349026
  if (ctx.ui.mode === "agent") {
@@ -348693,17 +349032,91 @@ async function flowsList(ctx, pattern, deps) {
348693
349032
  ctx.ui.write(renderListTable(rows, true));
348694
349033
  ctx.ui.outro(flowsMessages.flowCount(items.length));
348695
349034
  }
348696
- function handleFlowsList(ctx, pattern) {
349035
+
349036
+ // src/domains/flows/readCachedTags.ts
349037
+ import { relative as relative4 } from "node:path";
349038
+ async function readCachedTags(files, fs3 = makeDefaultFs()) {
349039
+ const filesByEnvDir = new Map;
349040
+ for (const file2 of files) {
349041
+ const envDir = findPulledEnvDir(file2);
349042
+ if (envDir === undefined)
349043
+ continue;
349044
+ const group = filesByEnvDir.get(envDir);
349045
+ if (group)
349046
+ group.push(file2);
349047
+ else
349048
+ filesByEnvDir.set(envDir, [file2]);
349049
+ }
349050
+ const tagsByFile = new Map;
349051
+ for (const [envDir, envFiles] of filesByEnvDir) {
349052
+ const manifest = await readManifest(envDir, fs3);
349053
+ if (typeof manifest === "string")
349054
+ continue;
349055
+ if (manifest.tagsFetchedAt === undefined)
349056
+ continue;
349057
+ const entryByPath = new Map(manifest.flows.map((f2) => [toPosix(f2.path), f2]));
349058
+ for (const file2 of envFiles) {
349059
+ const tags = entryByPath.get(toPosix(relative4(envDir, file2)))?.tags;
349060
+ if (tags !== undefined)
349061
+ tagsByFile.set(file2, tags);
349062
+ }
349063
+ }
349064
+ return tagsByFile;
349065
+ }
349066
+
349067
+ // src/domains/flows/readEnvLabel.ts
349068
+ import { basename as basename4 } from "node:path";
349069
+ async function readEnvLabel(envDir, fs3 = makeDefaultFs()) {
349070
+ const manifest = await readManifest(envDir, fs3);
349071
+ if (typeof manifest === "string")
349072
+ return basename4(envDir);
349073
+ return manifest.envSlug ?? manifest.envName ?? basename4(envDir);
349074
+ }
349075
+
349076
+ // src/domains/flows/listDefaults.ts
349077
+ function handleFlowsList(ctx, pattern, selectors) {
348697
349078
  const { fs: fs3 } = ctx;
348698
349079
  return flowsList(ctx, pattern, {
348699
349080
  cwd: process.cwd(),
348700
349081
  expandPatterns: (patterns, cwd) => expandPatterns(patterns, cwd, undefined, fs3),
348701
- peekFlowMeta: makePeekFlowMeta(fs3)
348702
- });
349082
+ peekFlowMeta: makePeekFlowMeta(fs3),
349083
+ readCachedTags: (files) => readCachedTags(files, fs3),
349084
+ readEnvLabel: (envDir) => readEnvLabel(envDir, fs3),
349085
+ findPulledEnv: (ref) => findPulledEnv(ref, process.cwd(), fs3),
349086
+ listPulledEnvDirs: () => listPulledEnvDirs(process.cwd(), fs3)
349087
+ }, selectors);
348703
349088
  }
348704
349089
 
348705
349090
  // src/domains/flows/listRemote.ts
348706
349091
  var import_picomatch2 = __toESM(require_picomatch2(), 1);
349092
+
349093
+ // src/domains/flows/fetchKnownTags.ts
349094
+ var pageSize = 100;
349095
+ var maxPages = 20;
349096
+ async function fetchKnownTags(ctx) {
349097
+ const names = [];
349098
+ let cursor3;
349099
+ try {
349100
+ for (let page = 0;page < maxPages; page += 1) {
349101
+ const result = await ctx.platformClient.callPublicApi(publicContractsV1.tag.list, {
349102
+ includeFlowIds: false,
349103
+ limit: pageSize,
349104
+ ...cursor3 === undefined ? {} : { cursor: cursor3 }
349105
+ });
349106
+ if (!result.ok)
349107
+ return;
349108
+ names.push(...result.value.tags.map((tag) => tag.name));
349109
+ cursor3 = result.value.nextCursor;
349110
+ if (cursor3 === undefined)
349111
+ return names;
349112
+ }
349113
+ } catch {
349114
+ return;
349115
+ }
349116
+ return;
349117
+ }
349118
+
349119
+ // src/domains/flows/listRemote.ts
348707
349120
  async function flowsListRemote(ctx, pattern, options) {
348708
349121
  const result = await ctx.platformClient.callPublicApi(publicContractsV1.flow.list, {
348709
349122
  environmentId: options.env,
@@ -348712,7 +349125,7 @@ async function flowsListRemote(ctx, pattern, options) {
348712
349125
  if (!result.ok)
348713
349126
  return failureFields(result);
348714
349127
  const matches = pattern ? import_picomatch2.default(pattern) : undefined;
348715
- const items = result.value.flows.filter((f2) => !matches || matches(f2.path)).map((f2) => ({
349128
+ const all = result.value.flows.filter((f2) => !matches || matches(f2.path)).map((f2) => ({
348716
349129
  flowId: f2.flowId,
348717
349130
  file: f2.path,
348718
349131
  name: f2.name,
@@ -348720,6 +349133,11 @@ async function flowsListRemote(ctx, pattern, options) {
348720
349133
  target: typeof f2.executionTarget === "string" ? f2.executionTarget : JSON.stringify(f2.executionTarget),
348721
349134
  url: f2.url
348722
349135
  }));
349136
+ const selectors = { tags: options.tags };
349137
+ const items = all.filter((item) => matchesSelectors(item, selectors));
349138
+ const empty2 = await emptySelectionResult(selectors, items.length, () => fetchKnownTags(ctx));
349139
+ if (empty2 !== undefined)
349140
+ return empty2;
348723
349141
  if (ctx.ui.mode === "json") {
348724
349142
  ctx.ui.json(items);
348725
349143
  return;
@@ -348731,6 +349149,8 @@ async function flowsListRemote(ctx, pattern, options) {
348731
349149
  const rows = items.map((it) => ({
348732
349150
  name: it.name,
348733
349151
  target: it.target,
349152
+ env: undefined,
349153
+ tags: it.tags,
348734
349154
  file: it.file
348735
349155
  }));
348736
349156
  if (ctx.ui.mode === "agent") {
@@ -348744,77 +349164,29 @@ async function flowsListRemote(ctx, pattern, options) {
348744
349164
  }
348745
349165
 
348746
349166
  // src/domains/flows/pull/handler.ts
348747
- import { dirname as dirname7, join as join25, resolve as resolve8 } from "node:path";
349167
+ import { dirname as dirname8, join as join27, resolve as resolve8 } from "node:path";
348748
349168
 
348749
- // src/shell/manifest/io.ts
348750
- import { createHash as createHash2 } from "node:crypto";
348751
- import { join as join21 } from "node:path";
348752
- var manifestFilename = ".manifest.json";
348753
- var flowEntrySchema = exports_external.object({
348754
- path: exports_external.string(),
348755
- contentHash: exports_external.string()
348756
- });
348757
- var manifestSchema = exports_external.object({
348758
- envId: exports_external.string(),
348759
- envSlug: exports_external.string().optional(),
348760
- fetchedAt: exports_external.string(),
348761
- envVarsFetchedAt: exports_external.string().optional(),
348762
- cliFlowsVersion: exports_external.string(),
348763
- qawolfCommitSha: exports_external.string().optional(),
348764
- qawolfCommittedAt: exports_external.string().optional(),
348765
- flows: exports_external.array(flowEntrySchema)
348766
- });
348767
- async function readManifest(envDir, fs3 = makeDefaultFs()) {
348768
- let raw;
348769
- try {
348770
- raw = await fs3.readFile(join21(envDir, manifestFilename));
348771
- } catch (err) {
348772
- if (isNoEntError(err))
348773
- return "missing";
348774
- throw err;
348775
- }
348776
- let parsed;
349169
+ // src/domains/flows/pull/fetchPhase.ts
349170
+ async function fetchTags(ctx, envId) {
348777
349171
  try {
348778
- parsed = JSON.parse(raw);
349172
+ const result = await ctx.platformClient.callPublicApi(publicContractsV1.flow.list, { environmentId: envId, includeDrafts: true });
349173
+ if (!result.ok)
349174
+ return;
349175
+ return {
349176
+ fetchedAt: new Date,
349177
+ byPath: new Map(result.value.flows.map((f2) => [f2.path, [...f2.tags]]))
349178
+ };
348779
349179
  } catch {
348780
- return "malformed";
349180
+ return;
348781
349181
  }
348782
- const result = manifestSchema.safeParse(parsed);
348783
- if (!result.success)
348784
- return "malformed";
348785
- return {
348786
- envId: result.data.envId,
348787
- envSlug: result.data.envSlug,
348788
- fetchedAt: result.data.fetchedAt,
348789
- envVarsFetchedAt: result.data.envVarsFetchedAt,
348790
- cliFlowsVersion: result.data.cliFlowsVersion,
348791
- qawolfCommitSha: result.data.qawolfCommitSha,
348792
- qawolfCommittedAt: result.data.qawolfCommittedAt,
348793
- flows: result.data.flows
348794
- };
348795
- }
348796
- async function writeManifest(envDir, manifest, fs3 = makeDefaultFs()) {
348797
- const body = JSON.stringify(manifest, undefined, 2);
348798
- await fs3.writeFile(join21(envDir, manifestFilename), `${body}
348799
- `);
348800
349182
  }
348801
- function hashFile(absPath, fs3 = makeDefaultFs()) {
348802
- return new Promise((resolve5, reject) => {
348803
- const hash2 = createHash2("sha256");
348804
- const stream = fs3.createReadStream(absPath);
348805
- stream.on("data", (chunk) => hash2.update(chunk));
348806
- stream.on("end", () => resolve5(hash2.digest("hex")));
348807
- stream.on("error", reject);
348808
- });
348809
- }
348810
-
348811
- // src/domains/flows/pull/fetchPhase.ts
348812
349183
  async function fetchBundleAndEnvVars(ctx, envId) {
348813
349184
  const { platformClient } = ctx;
348814
349185
  let tmpArchive;
348815
349186
  let bundleFetchedAt;
348816
349187
  let envVars;
348817
349188
  let envVarsFetchedAt;
349189
+ let tags;
348818
349190
  await ctx.ui.withProgress([
348819
349191
  {
348820
349192
  message: flowsMessages.pull.downloadingBundle,
@@ -348835,19 +349207,25 @@ async function fetchBundleAndEnvVars(ctx, envId) {
348835
349207
  envVars = result.value;
348836
349208
  envVarsFetchedAt = new Date;
348837
349209
  }
349210
+ },
349211
+ {
349212
+ message: flowsMessages.pull.fetchingTags,
349213
+ task: async () => {
349214
+ tags = await fetchTags(ctx, envId);
349215
+ }
348838
349216
  }
348839
349217
  ], flowsMessages.pull.downloadComplete);
348840
349218
  if (tmpArchive === undefined || bundleFetchedAt === undefined || envVars === undefined || envVarsFetchedAt === undefined) {
348841
349219
  throw new Error("Unexpected state: the bundle and env vars were not fetched. " + "This is a bug - please report it at https://github.com/qawolf/cli/issues");
348842
349220
  }
348843
- return { tmpArchive, bundleFetchedAt, envVars, envVarsFetchedAt };
349221
+ return { tmpArchive, bundleFetchedAt, envVars, envVarsFetchedAt, tags };
348844
349222
  }
348845
349223
 
348846
349224
  // src/domains/flows/pull/safety.ts
348847
349225
  import { resolve as resolve6 } from "node:path";
348848
349226
 
348849
349227
  // src/domains/flows/pull/entryPath.ts
348850
- import { isAbsolute as isAbsolute3, posix as posix2, relative as relative3, resolve as resolve5 } from "node:path";
349228
+ import { isAbsolute as isAbsolute3, posix as posix2, relative as relative5, resolve as resolve5 } from "node:path";
348851
349229
  function validateEntryPath(name, destResolved) {
348852
349230
  if (!name)
348853
349231
  throw new Error("empty entry name");
@@ -348859,7 +349237,7 @@ function validateEntryPath(name, destResolved) {
348859
349237
  throw new Error(`path traversal: entry escapes destination (${name})`);
348860
349238
  }
348861
349239
  const target = resolve5(destResolved, name);
348862
- const rel = relative3(destResolved, target);
349240
+ const rel = relative5(destResolved, target);
348863
349241
  if (rel.startsWith("..") || isAbsolute3(rel)) {
348864
349242
  throw new Error(`path traversal: entry resolves outside destination (${name})`);
348865
349243
  }
@@ -348929,8 +349307,28 @@ async function checkSafety(args) {
348929
349307
  return promptOverwriteIfModified({ ...args, manifest: existing });
348930
349308
  }
348931
349309
 
349310
+ // src/domains/flows/pull/reportPull.ts
349311
+ import { join as join23 } from "node:path";
349312
+ function reportPullResult(ui, args) {
349313
+ if (ui.mode !== "json")
349314
+ return;
349315
+ ui.output({
349316
+ env: args.env,
349317
+ envDir: args.stage.envDir,
349318
+ assetsDir: args.assetsAbs,
349319
+ fetchedAt: args.fetchedAt.toISOString(),
349320
+ flowCount: args.stage.flowCount,
349321
+ envVarCount: args.stage.envVarCount,
349322
+ flowsWithTeamStorageRefs: args.stage.flowsWithTeamStorageRefs,
349323
+ assetDownloadedCount: args.assets.downloadedCount,
349324
+ assetReusedCount: args.assets.reusedCount,
349325
+ assetSkippedCount: args.assets.skippedCount,
349326
+ manifestPath: join23(args.stage.envDir, manifestFilename)
349327
+ }, "");
349328
+ }
349329
+
348932
349330
  // src/domains/flows/pull/bundle.ts
348933
- import { join as join22, relative as relative4 } from "node:path";
349331
+ import { join as join24, relative as relative6 } from "node:path";
348934
349332
  async function flattenSingleWrapper(dir, fs3 = makeDefaultFs()) {
348935
349333
  const entries = await fs3.readdir(dir);
348936
349334
  if (entries.length !== 1)
@@ -348938,12 +349336,12 @@ async function flattenSingleWrapper(dir, fs3 = makeDefaultFs()) {
348938
349336
  const innerName = entries[0];
348939
349337
  if (!innerName)
348940
349338
  return;
348941
- const inner = join22(dir, innerName);
349339
+ const inner = join24(dir, innerName);
348942
349340
  const innerStat = await fs3.stat(inner);
348943
349341
  if (!innerStat.isDirectory())
348944
349342
  return;
348945
349343
  for (const e of await fs3.readdir(inner)) {
348946
- await fs3.rename(join22(inner, e), join22(dir, e));
349344
+ await fs3.rename(join24(inner, e), join24(dir, e));
348947
349345
  }
348948
349346
  const leftovers = await fs3.readdir(inner);
348949
349347
  if (leftovers.length > 0) {
@@ -348961,17 +349359,20 @@ var flowExtensions = [".flow.ts", ".flow.js"];
348961
349359
  async function buildManifest(args, fs3 = makeDefaultFs()) {
348962
349360
  const flowPaths = await walkForFlows(args.bundleDir, fs3);
348963
349361
  const flows = await Promise.all(flowPaths.map(async (rel) => ({
348964
- path: rel,
348965
- contentHash: await hashFile(join22(args.bundleDir, rel), fs3)
349362
+ path: toPosix(rel),
349363
+ contentHash: await hashFile(join24(args.bundleDir, rel), fs3),
349364
+ tags: args.tags?.byPath.get(toPosix(rel))
348966
349365
  })));
348967
349366
  return {
348968
349367
  envId: args.envId,
348969
- envSlug: undefined,
349368
+ envSlug: args.envSlug,
349369
+ envName: args.envName,
348970
349370
  fetchedAt: args.now.toISOString(),
348971
349371
  envVarsFetchedAt: args.envVarsFetchedAt?.toISOString(),
348972
349372
  cliFlowsVersion: args.cliFlowsVersion,
348973
349373
  qawolfCommitSha: extractQawolfCommitSha(args.wrapperName),
348974
349374
  qawolfCommittedAt: args.qawolfCommittedAt,
349375
+ tagsFetchedAt: args.tags?.fetchedAt.toISOString(),
348975
349376
  flows
348976
349377
  };
348977
349378
  }
@@ -348983,11 +349384,11 @@ async function walkForFlows(root, fs3) {
348983
349384
  async function walk(current, root, out, fs3) {
348984
349385
  const entries = await fs3.readdirWithTypes(current);
348985
349386
  for (const e of entries) {
348986
- const abs = join22(current, e.name);
349387
+ const abs = join24(current, e.name);
348987
349388
  if (e.isDirectory()) {
348988
349389
  await walk(abs, root, out, fs3);
348989
349390
  } else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
348990
- out.push(relative4(root, abs));
349391
+ out.push(relative6(root, abs));
348991
349392
  }
348992
349393
  }
348993
349394
  }
@@ -348996,11 +349397,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs3 = makeDefaultFs()) {
348996
349397
  const sample = flowPaths[0];
348997
349398
  if (!sample)
348998
349399
  return;
348999
- return (await fs3.stat(join22(bundleDir, sample))).mtime.toISOString();
349400
+ return (await fs3.stat(join24(bundleDir, sample))).mtime.toISOString();
349000
349401
  }
349001
349402
 
349002
349403
  // src/domains/flows/pull/applyTeamStorageRewrite.ts
349003
- import { join as join23, relative as relative5 } from "node:path";
349404
+ import { join as join25, relative as relative7 } from "node:path";
349004
349405
 
349005
349406
  // src/domains/flows/pull/rewriteTeamStorage.ts
349006
349407
  var runnerMountPrefix = "/home/wolf/team-storage/";
@@ -349036,7 +349437,7 @@ function isFlowFile(name) {
349036
349437
  async function walk2(dir, out, fs3) {
349037
349438
  const entries = await fs3.readdirWithTypes(dir);
349038
349439
  for (const e of entries) {
349039
- const abs = join23(dir, e.name);
349440
+ const abs = join25(dir, e.name);
349040
349441
  if (e.isDirectory()) {
349041
349442
  await walk2(abs, out, fs3);
349042
349443
  } else if (e.isFile() && isSourceFile(e.name)) {
@@ -349055,7 +349456,7 @@ async function applyTeamStorageRewrite(rootDir, fs3 = makeDefaultFs()) {
349055
349456
  await fs3.writeFile(file2, finalSource);
349056
349457
  }
349057
349458
  if (isFlowFile(file2) && finalSource.includes("process.env.TEAM_STORAGE_DIR")) {
349058
- return relative5(rootDir, file2);
349459
+ return relative7(rootDir, file2);
349059
349460
  }
349060
349461
  return;
349061
349462
  }));
@@ -349064,7 +349465,7 @@ async function applyTeamStorageRewrite(rootDir, fs3 = makeDefaultFs()) {
349064
349465
  }
349065
349466
 
349066
349467
  // src/domains/flows/pull/envVars.ts
349067
- import { join as join24 } from "node:path";
349468
+ import { join as join26 } from "node:path";
349068
349469
 
349069
349470
  // src/core/dotenv.ts
349070
349471
  var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -349129,13 +349530,13 @@ function unquote(escaped) {
349129
349530
  async function writeEnvFile(envDir, vars, fs3 = makeDefaultFs()) {
349130
349531
  if (Object.keys(vars).length === 0)
349131
349532
  return;
349132
- await fs3.writeFile(join24(envDir, ".env"), serializeDotenv(vars), {
349533
+ await fs3.writeFile(join26(envDir, ".env"), serializeDotenv(vars), {
349133
349534
  mode: 384
349134
349535
  });
349135
349536
  }
349136
349537
 
349137
349538
  // src/domains/flows/pull/extract.ts
349138
- import { dirname as dirname6, resolve as resolve7 } from "node:path";
349539
+ import { dirname as dirname7, resolve as resolve7 } from "node:path";
349139
349540
  import { createGunzip } from "node:zlib";
349140
349541
 
349141
349542
  // node_modules/tar/dist/esm/index.min.js
@@ -352452,7 +352853,7 @@ async function handleEntry(args) {
352452
352853
  total: args.getTotal()
352453
352854
  });
352454
352855
  args.addTotal(size);
352455
- args.fs.mkdirSync(dirname6(target), { recursive: true });
352856
+ args.fs.mkdirSync(dirname7(target), { recursive: true });
352456
352857
  const chunks = [];
352457
352858
  await new Promise((res, rej) => {
352458
352859
  entry.on("data", (chunk) => chunks.push(chunk));
@@ -352510,6 +352911,9 @@ async function stageBundle(args, fs4 = makeDefaultFs()) {
352510
352911
  await writeEnvFile(tmpDir, effectiveEnvVars, fs4);
352511
352912
  const manifest = await buildManifest({
352512
352913
  envId: args.envId,
352914
+ tags: args.tags ?? await carriedTags(args.destAbs, fs4),
352915
+ envSlug: args.envSlug,
352916
+ envName: args.envName,
352513
352917
  bundleDir: tmpDir,
352514
352918
  cliFlowsVersion: args.cliFlowsVersion,
352515
352919
  now: args.now,
@@ -352543,6 +352947,19 @@ async function stageBundle(args, fs4 = makeDefaultFs()) {
352543
352947
  throw err;
352544
352948
  }
352545
352949
  }
352950
+ async function carriedTags(envDir, fs4) {
352951
+ const previous = await readManifest(envDir, fs4);
352952
+ if (typeof previous === "string")
352953
+ return;
352954
+ if (previous.tagsFetchedAt === undefined)
352955
+ return;
352956
+ const byPath = new Map;
352957
+ for (const flow of previous.flows) {
352958
+ if (flow.tags !== undefined)
352959
+ byPath.set(toPosix(flow.path), [...flow.tags]);
352960
+ }
352961
+ return { fetchedAt: new Date(previous.tagsFetchedAt), byPath };
352962
+ }
352546
352963
 
352547
352964
  // src/domains/flows/pull/handler.ts
352548
352965
  async function handleFlowsPull(ctx, opts, deps) {
@@ -352555,8 +352972,8 @@ async function handleFlowsPull(ctx, opts, deps) {
352555
352972
  ctx.ui.error(validation.error);
352556
352973
  return { error: validation.error };
352557
352974
  }
352558
- const destAbs = resolve8(opts.out ?? join25(".qawolf", opts.env));
352559
- const assetsAbs = join25(dirname7(destAbs), "assets");
352975
+ const destAbs = resolve8(opts.out ?? join27(".qawolf", opts.env));
352976
+ const assetsAbs = join27(dirname8(destAbs), "assets");
352560
352977
  await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
352561
352978
  const yes = opts.yes ?? false;
352562
352979
  let archive;
@@ -352589,10 +353006,13 @@ async function handleFlowsPull(ctx, opts, deps) {
352589
353006
  destAbs,
352590
353007
  assetsAbs,
352591
353008
  envId: opts.env,
353009
+ envSlug: opts.envSlug,
353010
+ envName: opts.envName,
352592
353011
  cliFlowsVersion: resolvedDeps.flowsVersion,
352593
353012
  now: fetched.bundleFetchedAt,
352594
353013
  envVars: fetched.envVars,
352595
- envVarsFetchedAt: fetched.envVarsFetchedAt
353014
+ envVarsFetchedAt: fetched.envVarsFetchedAt,
353015
+ tags: fetched.tags
352596
353016
  }, resolvedDeps.fs)
352597
353017
  },
352598
353018
  {
@@ -352612,21 +353032,13 @@ async function handleFlowsPull(ctx, opts, deps) {
352612
353032
  assetReusedCount: results[1].reusedCount,
352613
353033
  assetSkippedCount: results[1].skippedCount
352614
353034
  }, assetsAbs));
352615
- if (ctx.ui.mode === "json") {
352616
- ctx.ui.output({
352617
- env: opts.env,
352618
- envDir: result.envDir,
352619
- assetsDir: assetsAbs,
352620
- fetchedAt: fetched.bundleFetchedAt.toISOString(),
352621
- flowCount: result.flowCount,
352622
- envVarCount: result.envVarCount,
352623
- flowsWithTeamStorageRefs: result.flowsWithTeamStorageRefs,
352624
- assetDownloadedCount: assetResult.downloadedCount,
352625
- assetReusedCount: assetResult.reusedCount,
352626
- assetSkippedCount: assetResult.skippedCount,
352627
- manifestPath: join25(result.envDir, manifestFilename)
352628
- }, "");
352629
- }
353035
+ reportPullResult(ctx.ui, {
353036
+ env: opts.env,
353037
+ assetsAbs,
353038
+ fetchedAt: fetched.bundleFetchedAt,
353039
+ stage: result,
353040
+ assets: assetResult
353041
+ });
352630
353042
  } finally {
352631
353043
  if (archive !== undefined)
352632
353044
  await resolvedDeps.fs.unlink(archive).catch(() => {});
@@ -352637,16 +353049,25 @@ async function handleFlowsPull(ctx, opts, deps) {
352637
353049
  var findContract = publicContractsV1.environment.find;
352638
353050
  async function pickEnvironment(deps) {
352639
353051
  const fetched = await fetchAllEnvironments(deps.platformClient);
352640
- if (!fetched.ok)
352641
- return { ...failureFields(fetched), kind: "error" };
353052
+ if (!fetched.ok) {
353053
+ return {
353054
+ ...failureFields(fetched),
353055
+ kind: "error",
353056
+ unreachable: fetched.unreachable === true
353057
+ };
353058
+ }
352642
353059
  const environments = fetched.environments;
352643
353060
  if (environments.length === 0) {
352644
- return { kind: "error", error: environmentsMessages.noEnvironments };
353061
+ return {
353062
+ kind: "error",
353063
+ error: environmentsMessages.noEnvironments,
353064
+ unreachable: false
353065
+ };
352645
353066
  }
352646
353067
  const sole = environments.length === 1 ? environments[0] : undefined;
352647
353068
  if (sole) {
352648
353069
  deps.ui.info(environmentsMessages.usingEnvironment(sole.name));
352649
- return { kind: "resolved", env: sole.id };
353070
+ return resolvedFrom(sole);
352650
353071
  }
352651
353072
  const narrowed = await narrowByKind(deps.ui, environments);
352652
353073
  if (narrowed === "cancelled")
@@ -352654,7 +353075,7 @@ async function pickEnvironment(deps) {
352654
353075
  const soleOfKind = narrowed.length === 1 ? narrowed[0] : undefined;
352655
353076
  if (soleOfKind) {
352656
353077
  deps.ui.info(environmentsMessages.usingEnvironment(soleOfKind.name));
352657
- return { kind: "resolved", env: soleOfKind.id };
353078
+ return resolvedFrom(soleOfKind);
352658
353079
  }
352659
353080
  const picked = await deps.ui.select(environmentsMessages.whichEnvironment, narrowed.map((e) => ({
352660
353081
  value: e.id,
@@ -352664,7 +353085,16 @@ async function pickEnvironment(deps) {
352664
353085
  if (!picked.ok)
352665
353086
  return { kind: "cancelled" };
352666
353087
  deps.ui.info(environmentsMessages.exportHint(picked.value));
352667
- return { kind: "resolved", env: picked.value };
353088
+ const chosen = narrowed.find((e) => e.id === picked.value);
353089
+ return chosen ? resolvedFrom(chosen) : { kind: "resolved", env: picked.value };
353090
+ }
353091
+ function resolvedFrom(environment) {
353092
+ return {
353093
+ kind: "resolved",
353094
+ env: environment.id,
353095
+ slug: environment.alias ?? undefined,
353096
+ name: environment.name
353097
+ };
352668
353098
  }
352669
353099
  async function narrowByKind(ui2, environments) {
352670
353100
  const byKind = (kind) => environments.filter((e) => e.kind === kind);
@@ -352689,11 +353119,11 @@ async function narrowByKind(ui2, environments) {
352689
353119
  return "cancelled";
352690
353120
  return picked.value === "static" ? statics : previews;
352691
353121
  }
352692
- var maxPages = 10;
353122
+ var maxPages2 = 10;
352693
353123
  async function fetchAllEnvironments(platformClient) {
352694
353124
  const environments = [];
352695
353125
  let cursor3;
352696
- for (let page = 0;page < maxPages; page += 1) {
353126
+ for (let page = 0;page < maxPages2; page += 1) {
352697
353127
  const result = await platformClient.callPublicApi(findContract, {
352698
353128
  limit: 100,
352699
353129
  cursor: cursor3
@@ -352705,7 +353135,7 @@ async function fetchAllEnvironments(platformClient) {
352705
353135
  if (cursor3 === undefined)
352706
353136
  return { ok: true, environments };
352707
353137
  }
352708
- return { ok: false, error: environmentsMessages.tooManyPages(maxPages) };
353138
+ return { ok: false, error: environmentsMessages.tooManyPages(maxPages2) };
352709
353139
  }
352710
353140
 
352711
353141
  // src/domains/environments/resolveEnvironment.ts
@@ -352721,7 +353151,7 @@ async function resolveEnvironment(deps, opts) {
352721
353151
  return resolveRef2(deps, fromEnvVar);
352722
353152
  }
352723
353153
  if (deps.ui.mode !== "human") {
352724
- return { kind: "error", error: opts.requiredMessage };
353154
+ return { kind: "error", error: opts.requiredMessage, unreachable: false };
352725
353155
  }
352726
353156
  return pickEnvironment(deps);
352727
353157
  }
@@ -352733,16 +353163,27 @@ async function resolveRef2(deps, ref) {
352733
353163
  return {
352734
353164
  ...failureFields(result),
352735
353165
  kind: "error",
352736
- error: environmentsMessages.couldNotResolve(ref, result.error)
353166
+ error: environmentsMessages.couldNotResolve(ref, result.error),
353167
+ unreachable: result.unreachable === true
352737
353168
  };
352738
353169
  }
352739
353170
  if (result.value.id !== ref) {
352740
353171
  deps.ui.info(environmentsMessages.resolvedAlias(ref, result.value.id));
352741
353172
  }
352742
- return { kind: "resolved", env: result.value.id };
353173
+ return {
353174
+ kind: "resolved",
353175
+ env: result.value.id,
353176
+ slug: result.value.alias ?? undefined,
353177
+ name: result.value.name
353178
+ };
352743
353179
  }
352744
353180
 
352745
353181
  // src/commands/flows/withResolvedEnv.ts
353182
+ async function pulledEnvFallback(outcome, explicit, offlineFallback) {
353183
+ if (!outcome.unreachable)
353184
+ return;
353185
+ return offlineFallback?.(explicit);
353186
+ }
352746
353187
  function withResolvedEnv(signals, args, fn) {
352747
353188
  return (opts, command) => {
352748
353189
  const pickerNeeded = !args.explicit?.trim() && !(process.env["QAWOLF_ENVIRONMENT"] ?? "").trim();
@@ -352765,9 +353206,17 @@ function withResolvedEnv(signals, args, fn) {
352765
353206
  return;
352766
353207
  }
352767
353208
  if (outcome.kind === "error") {
353209
+ const pulled = await pulledEnvFallback(outcome, args.explicit, args.offlineFallback);
353210
+ if (pulled !== undefined) {
353211
+ ctx.ui.warn(environmentsMessages.usingPulledEnv(pulled));
353212
+ return fn(ctx, pulled, { slug: undefined, name: undefined });
353213
+ }
352768
353214
  return failureFields(outcome);
352769
353215
  }
352770
- return fn(ctx, outcome.env);
353216
+ return fn(ctx, outcome.env, {
353217
+ slug: outcome.slug,
353218
+ name: outcome.name
353219
+ });
352771
353220
  })(opts, command);
352772
353221
  };
352773
353222
  }
@@ -352780,30 +353229,37 @@ Examples:
352780
353229
  $ qawolf flows pull --env staging --yes`;
352781
353230
  function registerFlowsPullCommand(flows, signals) {
352782
353231
  declareCommandKind(flows.command("pull"), "read").description("Download an environment's flows into the local .qawolf/<env>/ cache").option("--env <env>", "Environment to pull from (UUID or kebab-case slug); defaults to QAWOLF_ENVIRONMENT, or an interactive picker").option("--out <path>", "Destination directory (defaults to .qawolf/<env>/)").option("--yes", "Overwrite locally-modified files without prompting", false).addHelpText("after", pullExamples).action((opts, command) => {
352783
- return withResolvedEnv(signals, { explicit: opts.env, requiredMessage: flowsMessages.pull.requiresEnv }, (ctx, env2) => handleFlowsPull(ctx, { ...opts, env: env2 }))(opts, command);
353232
+ return withResolvedEnv(signals, { explicit: opts.env, requiredMessage: flowsMessages.pull.requiresEnv }, (ctx, env2, identity) => handleFlowsPull(ctx, {
353233
+ ...opts,
353234
+ env: env2,
353235
+ envSlug: identity.slug,
353236
+ envName: identity.name
353237
+ }))(opts, command);
352784
353238
  });
352785
353239
  }
352786
353240
 
352787
- // src/domains/runner/runFlagParsers.ts
352788
- function parseInteger(name, options = {}) {
352789
- return (value) => {
352790
- const n2 = Number.parseInt(value, 10);
352791
- if (!Number.isFinite(n2) || String(n2) !== value) {
352792
- throw new InvalidArgumentError(`${name} must be an integer`);
352793
- }
352794
- if (options.min !== undefined && n2 < options.min) {
352795
- throw new InvalidArgumentError(`${name} must be >= ${options.min}`);
352796
- }
352797
- return n2;
352798
- };
353241
+ // src/commands/flows/runArtifactOptions.ts
353242
+ var videoModes = ["on", "off", "retain-on-failure"];
353243
+ var traceModes = ["on", "off", "retain-on-failure"];
353244
+ var harModes = ["on", "off", "retain-on-failure"];
353245
+ var harContentModes = ["full", "omit"];
353246
+ var videoDefault = "off";
353247
+ var traceDefault = "off";
353248
+ var harDefault = "off";
353249
+ var harContentDefault = "omit";
353250
+ function addRunArtifactOptions(command) {
353251
+ return command.option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)");
352799
353252
  }
352800
- function parseEnum(name, values) {
352801
- return (value) => {
352802
- if (!values.includes(value)) {
352803
- throw new InvalidArgumentError(`${name} must be one of: ${values.join(", ")}`);
352804
- }
352805
- return value;
352806
- };
353253
+
353254
+ // src/domains/flows/allEnvsWarning.ts
353255
+ function allEnvsNoEffectWarning(args) {
353256
+ if (!args.allEnvs)
353257
+ return;
353258
+ if (args.env !== undefined)
353259
+ return flowsMessages.selectors.allEnvsWithEnv;
353260
+ if (args.tags.length === 0)
353261
+ return flowsMessages.selectors.allEnvsWithoutTag;
353262
+ return;
352807
353263
  }
352808
353264
 
352809
353265
  // src/core/patternArgs.ts
@@ -352811,6 +353267,44 @@ function buildPatternArgs(pattern) {
352811
353267
  return pattern ? [pattern] : [];
352812
353268
  }
352813
353269
 
353270
+ // src/domains/flows/resolveEnvChoice.ts
353271
+ var allValue = "all";
353272
+ async function resolveEnvChoice(args) {
353273
+ const byEnv = new Map;
353274
+ for (const file2 of args.files) {
353275
+ const key = findPulledEnvDir(file2) ?? "";
353276
+ const group = byEnv.get(key);
353277
+ if (group)
353278
+ group.push(file2);
353279
+ else
353280
+ byEnv.set(key, [file2]);
353281
+ }
353282
+ const envDirs = [...byEnv.keys()].filter((key) => key !== "");
353283
+ if (envDirs.length <= 1 || args.allEnvs) {
353284
+ return { kind: "proceed", files: [...args.files] };
353285
+ }
353286
+ const labels = await Promise.all(envDirs.map(async (dir) => ({ dir, label: await args.readLabel(dir) })));
353287
+ if (args.mode !== "human") {
353288
+ return {
353289
+ kind: "error",
353290
+ error: flowsMessages.selectors.ambiguousEnvs(labels.map((l2) => l2.label))
353291
+ };
353292
+ }
353293
+ const picked = await args.select(flowsMessages.selectors.chooseEnv, [
353294
+ ...labels.map(({ dir, label }) => ({ value: dir, label })),
353295
+ { value: allValue, label: "All" }
353296
+ ]);
353297
+ if (!picked.ok)
353298
+ return { kind: "cancelled" };
353299
+ if (picked.value === allValue) {
353300
+ return { kind: "proceed", files: [...args.files] };
353301
+ }
353302
+ return {
353303
+ kind: "proceed",
353304
+ files: [...byEnv.get("") ?? [], ...byEnv.get(picked.value) ?? []]
353305
+ };
353306
+ }
353307
+
352814
353308
  // src/core/flowFailureHint.ts
352815
353309
  function flowFailureHint(errText, projectDir) {
352816
353310
  const missingPackage = extractMissingPackage(errText);
@@ -353354,15 +353848,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
353354
353848
 
353355
353849
  // src/domains/runtimeEnv/prepareRunDir.ts
353356
353850
  import { createHash as createHash4 } from "node:crypto";
353357
- import { join as join33, resolve as resolve9 } from "node:path";
353851
+ import { join as join35, resolve as resolve9 } from "node:path";
353358
353852
 
353359
353853
  // src/domains/runtimeEnv/execSubpathImports.ts
353360
- import { join as join26 } from "node:path";
353854
+ import { join as join28 } from "node:path";
353361
353855
  var flowSubpathImports = { "#playwright": "playwright" };
353362
353856
  var packageJsonSchema = exports_external.looseObject({ imports: exports_external.record(exports_external.string(), exports_external.unknown()).catch({}) }).catch({ imports: {} });
353363
353857
  async function writeExecSubpathImports(args) {
353364
353858
  const { execDir, fs: fs4 } = args;
353365
- const pkgPath = join26(execDir, "package.json");
353859
+ const pkgPath = join28(execDir, "package.json");
353366
353860
  const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs4));
353367
353861
  const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
353368
353862
  await fs4.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
@@ -353384,10 +353878,10 @@ async function readPackageJson(pkgPath, fs4) {
353384
353878
  }
353385
353879
 
353386
353880
  // src/domains/runtimeEnv/innerHop.ts
353387
- import { join as join28 } from "node:path";
353881
+ import { join as join30 } from "node:path";
353388
353882
 
353389
353883
  // src/domains/runtimeEnv/linkPinnedPackages.ts
353390
- import { dirname as dirname8, join as join27 } from "node:path";
353884
+ import { dirname as dirname9, join as join29 } from "node:path";
353391
353885
 
353392
353886
  // src/domains/runtimeEnv/symlinkDir.ts
353393
353887
  import { symlink } from "node:fs/promises";
@@ -353399,16 +353893,16 @@ async function createDirSymlink(source, target) {
353399
353893
  // src/domains/runtimeEnv/linkPinnedPackages.ts
353400
353894
  async function linkPinnedPackages(args) {
353401
353895
  const { depsRoot, nodeModulesDir, fs: fs4 } = args;
353402
- const managedModules = join27(depsRoot, "node_modules");
353896
+ const managedModules = join29(depsRoot, "node_modules");
353403
353897
  await fs4.mkdir(nodeModulesDir, { recursive: true });
353404
353898
  for (const { name } of pinnedPackages) {
353405
353899
  const segments = name.split("/");
353406
- const target = join27(nodeModulesDir, ...segments);
353900
+ const target = join29(nodeModulesDir, ...segments);
353407
353901
  if (segments.length > 1) {
353408
- await fs4.mkdir(dirname8(target), { recursive: true });
353902
+ await fs4.mkdir(dirname9(target), { recursive: true });
353409
353903
  }
353410
353904
  await fs4.rm(target, { recursive: true, force: true });
353411
- await createDirSymlink(join27(managedModules, ...segments), target);
353905
+ await createDirSymlink(join29(managedModules, ...segments), target);
353412
353906
  }
353413
353907
  }
353414
353908
 
@@ -353417,18 +353911,18 @@ async function populateInnerHop(args) {
353417
353911
  const { depsRoot, execDir, fs: fs4 } = args;
353418
353912
  await linkPinnedPackages({
353419
353913
  depsRoot,
353420
- nodeModulesDir: join28(execDir, "node_modules"),
353914
+ nodeModulesDir: join30(execDir, "node_modules"),
353421
353915
  fs: fs4
353422
353916
  });
353423
353917
  }
353424
353918
 
353425
353919
  // src/domains/runtimeEnv/outerHop.ts
353426
353920
  import { lstat as lstat2 } from "node:fs/promises";
353427
- import { dirname as dirname10, join as join30 } from "node:path";
353921
+ import { dirname as dirname11, join as join32 } from "node:path";
353428
353922
 
353429
353923
  // src/domains/runtimeEnv/carryOverPackages.ts
353430
353924
  import { lstat } from "node:fs/promises";
353431
- import { dirname as dirname9, join as join29 } from "node:path";
353925
+ import { dirname as dirname10, join as join31 } from "node:path";
353432
353926
 
353433
353927
  // src/domains/runtimeEnv/carryOverPlan.ts
353434
353928
  var executorPackageNames = new Set(pinnedPackages.map((p3) => p3.name));
@@ -353450,12 +353944,12 @@ async function carryOverPackages(args) {
353450
353944
  const linked = [];
353451
353945
  for (const name of names) {
353452
353946
  const segments = name.split("/");
353453
- const target = join29(nodeModulesDir, ...segments);
353947
+ const target = join31(nodeModulesDir, ...segments);
353454
353948
  if (await pathOccupied(target))
353455
353949
  continue;
353456
353950
  if (segments.length > 1)
353457
- await fs4.mkdir(dirname9(target), { recursive: true });
353458
- await createDirSymlink(join29(projectModulesDir, ...segments), target);
353951
+ await fs4.mkdir(dirname10(target), { recursive: true });
353952
+ await createDirSymlink(join31(projectModulesDir, ...segments), target);
353459
353953
  linked.push(name);
353460
353954
  }
353461
353955
  return linked;
@@ -353478,18 +353972,18 @@ async function listPackageNames(dir, fs4) {
353478
353972
  const names = [];
353479
353973
  for (const entry of entries) {
353480
353974
  if (!entry.startsWith("@")) {
353481
- if (fs4.existsSync(join29(dir, entry, "package.json")))
353975
+ if (fs4.existsSync(join31(dir, entry, "package.json")))
353482
353976
  names.push(entry);
353483
353977
  continue;
353484
353978
  }
353485
353979
  let scoped;
353486
353980
  try {
353487
- scoped = await fs4.readdir(join29(dir, entry));
353981
+ scoped = await fs4.readdir(join31(dir, entry));
353488
353982
  } catch {
353489
353983
  continue;
353490
353984
  }
353491
353985
  for (const inner of scoped) {
353492
- if (fs4.existsSync(join29(dir, entry, inner, "package.json"))) {
353986
+ if (fs4.existsSync(join31(dir, entry, inner, "package.json"))) {
353493
353987
  names.push(`${entry}/${inner}`);
353494
353988
  }
353495
353989
  }
@@ -353507,7 +354001,7 @@ async function populateOuterHop(args) {
353507
354001
  const depNames = Object.keys(installableDeps);
353508
354002
  const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs4);
353509
354003
  if (satisfying !== undefined) {
353510
- await createDirSymlink(satisfying, join30(runDir, "node_modules"));
354004
+ await createDirSymlink(satisfying, join32(runDir, "node_modules"));
353511
354005
  return { mode: "symlink", nodeModulesDir: satisfying };
353512
354006
  }
353513
354007
  if (depNames.length === 0)
@@ -353516,12 +354010,12 @@ async function populateOuterHop(args) {
353516
354010
  await installOuterDeps(runDir, installableDeps, fs4, args.install ?? spawnNpmInstall);
353517
354011
  await linkPinnedPackages({
353518
354012
  depsRoot: args.depsRoot,
353519
- nodeModulesDir: join30(runDir, "node_modules"),
354013
+ nodeModulesDir: join32(runDir, "node_modules"),
353520
354014
  fs: fs4
353521
354015
  });
353522
354016
  const carriedOver = await carryOverPackages({
353523
- projectModulesDir: join30(projectDir, "node_modules"),
353524
- nodeModulesDir: join30(runDir, "node_modules"),
354017
+ projectModulesDir: join32(projectDir, "node_modules"),
354018
+ nodeModulesDir: join32(runDir, "node_modules"),
353525
354019
  fs: fs4
353526
354020
  });
353527
354021
  return { mode: "install", depCount: depNames.length, rejected, carriedOver };
@@ -353530,15 +354024,15 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs4) {
353530
354024
  const rejected = [];
353531
354025
  let dir = startDir;
353532
354026
  while (true) {
353533
- const candidate = join30(dir, "node_modules");
354027
+ const candidate = join32(dir, "node_modules");
353534
354028
  const stats = await lstatSafe(candidate);
353535
354029
  if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
353536
- const missing = depNames.filter((name) => !fs4.existsSync(join30(candidate, name, "package.json")));
354030
+ const missing = depNames.filter((name) => !fs4.existsSync(join32(candidate, name, "package.json")));
353537
354031
  if (missing.length === 0)
353538
354032
  return { satisfying: candidate, rejected };
353539
354033
  rejected.push({ dir: candidate, missing });
353540
354034
  }
353541
- const parent = dirname10(dir);
354035
+ const parent = dirname11(dir);
353542
354036
  if (parent === dir)
353543
354037
  return { satisfying: undefined, rejected };
353544
354038
  dir = parent;
@@ -353554,7 +354048,7 @@ async function lstatSafe(path3) {
353554
354048
  async function readInstallableDeps(projectDir, fs4) {
353555
354049
  let content;
353556
354050
  try {
353557
- content = await fs4.readFile(join30(projectDir, "package.json"));
354051
+ content = await fs4.readFile(join32(projectDir, "package.json"));
353558
354052
  } catch {
353559
354053
  return {};
353560
354054
  }
@@ -353572,8 +354066,8 @@ async function readInstallableDeps(projectDir, fs4) {
353572
354066
  return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames2.has(name)).filter((entry) => typeof entry[1] === "string"));
353573
354067
  }
353574
354068
  async function installOuterDeps(runDir, deps, fs4, install) {
353575
- await fs4.writeFile(join30(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
353576
- await fs4.writeFile(join30(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
354069
+ await fs4.writeFile(join32(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
354070
+ await fs4.writeFile(join32(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
353577
354071
  `);
353578
354072
  const result = await install(runDir);
353579
354073
  if (result.exitCode !== 0) {
@@ -353583,16 +354077,16 @@ async function installOuterDeps(runDir, deps, fs4, install) {
353583
354077
 
353584
354078
  // src/domains/runtimeEnv/stageFlowFiles.ts
353585
354079
  import { createHash as createHash3 } from "node:crypto";
353586
- import { basename as basename4, dirname as dirname11, join as join32, sep as sep4 } from "node:path";
354080
+ import { basename as basename6, dirname as dirname12, join as join34, sep as sep4 } from "node:path";
353587
354081
 
353588
354082
  // src/shell/copyDir.ts
353589
354083
  import { cp, readdir as readdir2 } from "node:fs/promises";
353590
- import { basename as basename3, join as join31 } from "node:path";
354084
+ import { basename as basename5, join as join33 } from "node:path";
353591
354085
  async function copyDirExcluding(source, destination, excludedNames) {
353592
354086
  const entries = await readdir2(source, { withFileTypes: true });
353593
- await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join31(source, entry.name), join31(destination, entry.name), {
354087
+ await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join33(source, entry.name), join33(destination, entry.name), {
353594
354088
  recursive: true,
353595
- filter: (path3) => !excludedNames.has(basename3(path3))
354089
+ filter: (path3) => !excludedNames.has(basename5(path3))
353596
354090
  })));
353597
354091
  }
353598
354092
 
@@ -353607,14 +354101,14 @@ async function stageFlowFiles(args) {
353607
354101
  if (files.length > 1) {
353608
354102
  return Promise.all(files.map((f3) => stageOneFile(f3, execDir, fs4)));
353609
354103
  }
353610
- await Promise.all(files.map((f3) => fs4.copyFile(f3, join32(execDir, basename4(f3)))));
353611
- return files.map((f3) => join32(execDir, basename4(f3)));
354104
+ await Promise.all(files.map((f3) => fs4.copyFile(f3, join34(execDir, basename6(f3)))));
354105
+ return files.map((f3) => join34(execDir, basename6(f3)));
353612
354106
  }
353613
354107
  async function stageOneFile(file2, execDir, fs4) {
353614
- const dirHash = createHash3("sha256").update(dirname11(file2)).digest("hex").slice(0, 8);
353615
- const subDir = join32(execDir, dirHash);
354108
+ const dirHash = createHash3("sha256").update(dirname12(file2)).digest("hex").slice(0, 8);
354109
+ const subDir = join34(execDir, dirHash);
353616
354110
  await fs4.mkdir(subDir, { recursive: true });
353617
- const dest = join32(subDir, basename4(file2));
354111
+ const dest = join34(subDir, basename6(file2));
353618
354112
  await fs4.copyFile(file2, dest);
353619
354113
  return dest;
353620
354114
  }
@@ -353622,7 +354116,7 @@ function remapPath(file2, projectDir, execDir) {
353622
354116
  if (file2 === projectDir)
353623
354117
  return execDir;
353624
354118
  if (file2.startsWith(projectDir + sep4)) {
353625
- return join32(execDir, file2.slice(projectDir.length + 1));
354119
+ return join34(execDir, file2.slice(projectDir.length + 1));
353626
354120
  }
353627
354121
  return;
353628
354122
  }
@@ -353632,10 +354126,10 @@ async function prepareRunDir(args) {
353632
354126
  const { files, projectDir, depsRoot, runRoot } = args;
353633
354127
  const fs4 = args.fs ?? makeDefaultFs();
353634
354128
  const runId = buildRunId(projectDir, files);
353635
- const runDir = join33(runRoot, runId);
354129
+ const runDir = join35(runRoot, runId);
353636
354130
  await fs4.rm(runDir, { recursive: true, force: true });
353637
354131
  await fs4.mkdir(runDir, { recursive: true });
353638
- const execDir = join33(runDir, "exec");
354132
+ const execDir = join35(runDir, "exec");
353639
354133
  await fs4.mkdir(execDir, { recursive: true });
353640
354134
  await populateInnerHop({ depsRoot, execDir, fs: fs4 });
353641
354135
  const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs: fs4 });
@@ -353666,17 +354160,17 @@ function buildRunId(projectDir, files) {
353666
354160
  }
353667
354161
 
353668
354162
  // src/shell/testkit.ts
353669
- import { join as join34 } from "node:path";
354163
+ import { join as join36 } from "node:path";
353670
354164
  function notAvailableLocally(name) {
353671
354165
  throw new Error(runnerMessages.notAvailableLocally(name));
353672
354166
  }
353673
354167
  async function loadSdkDeps(cwd) {
353674
- const testkitDir = join34(cwd, "node_modules", "@qawolf", "testkit");
354168
+ const testkitDir = join36(cwd, "node_modules", "@qawolf", "testkit");
353675
354169
  try {
353676
354170
  const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
353677
354171
  if (typeof clientMod.createTestkitClient !== "function")
353678
354172
  throw new Error("createTestkitClient is not a function");
353679
- const scopeMod = await importFromPath(join34(testkitDir, "dist", "clientScope.js"));
354173
+ const scopeMod = await importFromPath(join36(testkitDir, "dist", "clientScope.js"));
353680
354174
  if (typeof scopeMod.configureTestkitClient !== "function")
353681
354175
  throw new Error("configureTestkitClient is not a function");
353682
354176
  return {
@@ -353734,9 +354228,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
353734
354228
  }
353735
354229
 
353736
354230
  // src/commands/flows/envVarDeps.ts
353737
- import { join as join35 } from "node:path";
354231
+ import { join as join37 } from "node:path";
353738
354232
  function envFilePath(envDir) {
353739
- return join35(envDir, ".env");
354233
+ return join37(envDir, ".env");
353740
354234
  }
353741
354235
  async function readEnv(envDir, fs4) {
353742
354236
  try {
@@ -353840,8 +354334,27 @@ async function createFlowRuntimeDeps({
353840
354334
  };
353841
354335
  }
353842
354336
 
354337
+ // src/domains/flows/selectRunByTag.ts
354338
+ async function selectRunByTag(args) {
354339
+ if (!hasSelectors(args.selectors))
354340
+ return { ok: true, files: args.files };
354341
+ const cachedTags = await args.readCachedTags(args.files);
354342
+ const notCached = tagsNotCachedResult(args.selectors, cachedTags);
354343
+ if (notCached !== undefined)
354344
+ return { ok: false, result: notCached };
354345
+ const matched = args.files.filter((file2) => matchesSelectors({ tags: cachedTags.get(file2) }, args.selectors));
354346
+ if (matched.length === 0) {
354347
+ return {
354348
+ ok: false,
354349
+ result: args.noMatch(explainEmptySelection(args.selectors))
354350
+ };
354351
+ }
354352
+ const choice = await args.chooseEnv(matched);
354353
+ return "proceed" in choice ? { ok: true, files: choice.proceed } : { ok: false, result: choice.stop };
354354
+ }
354355
+
353843
354356
  // src/commands/flows/runStagedFlows.ts
353844
- import { join as join39 } from "node:path";
354357
+ import { join as join41 } from "node:path";
353845
354358
 
353846
354359
  // src/shell/appium/createAppiumServer.ts
353847
354360
  import { existsSync as existsSync2 } from "node:fs";
@@ -354206,25 +354719,16 @@ Install Android Studio and open Tools > SDK Manager.`);
354206
354719
  }
354207
354720
 
354208
354721
  // src/shell/manifest/lookup.ts
354209
- import { basename as basename5, dirname as dirname12, relative as relative6 } from "node:path";
354722
+ import { relative as relative8 } from "node:path";
354210
354723
  async function findFlowStamp(flowAbsPath) {
354211
- let envDir;
354212
- let current = dirname12(flowAbsPath);
354213
- while (current !== dirname12(current)) {
354214
- const parent2 = dirname12(current);
354215
- if (basename5(parent2) === ".qawolf") {
354216
- envDir = current;
354217
- break;
354218
- }
354219
- current = parent2;
354220
- }
354724
+ const envDir = findPulledEnvDir(flowAbsPath);
354221
354725
  if (!envDir)
354222
354726
  return;
354223
354727
  const manifest = await readManifest(envDir);
354224
354728
  if (typeof manifest === "string")
354225
354729
  return;
354226
- const relPath = relative6(envDir, flowAbsPath);
354227
- const entry = manifest.flows.find((f3) => f3.path === relPath);
354730
+ const relPath = toPosix(relative8(envDir, flowAbsPath));
354731
+ const entry = manifest.flows.find((f3) => toPosix(f3.path) === relPath);
354228
354732
  if (!entry)
354229
354733
  return;
354230
354734
  return {
@@ -355143,14 +355647,14 @@ async function runWebFlow({
355143
355647
 
355144
355648
  // src/shell/embeddedWorkerCli.ts
355145
355649
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
355146
- import { basename as basename6, join as join36 } from "node:path";
355650
+ import { basename as basename7, join as join38 } from "node:path";
355147
355651
  var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
355148
355652
  function extractEmbeddedWorkerCli() {
355149
355653
  const assetPath = process.env[embeddedCliPathEnv];
355150
355654
  if (assetPath === undefined || assetPath === "")
355151
355655
  return;
355152
- const dir = join36(getDataDir(), "worker");
355153
- const dest = join36(dir, basename6(assetPath));
355656
+ const dir = join38(getDataDir(), "worker");
355657
+ const dest = join38(dir, basename7(assetPath));
355154
355658
  if (existsSync3(dest))
355155
355659
  return dest;
355156
355660
  mkdirSync3(dir, { recursive: true });
@@ -355388,7 +355892,7 @@ ${styleText3("dim", "─".repeat(width))}
355388
355892
  };
355389
355893
  }
355390
355894
  // src/shell/reporter/createJUnitReporter.ts
355391
- import { join as join37 } from "node:path";
355895
+ import { join as join39 } from "node:path";
355392
355896
 
355393
355897
  // src/shell/reporter/junitXml.ts
355394
355898
  function escapeXml(str) {
@@ -355427,7 +355931,7 @@ ${suites}`;
355427
355931
 
355428
355932
  // src/shell/reporter/createJUnitReporter.ts
355429
355933
  function resolveJUnitOutputPath(junit, outputDir) {
355430
- return typeof junit === "string" && junit.trim() !== "" ? junit : join37(outputDir, "junit-report.xml");
355934
+ return typeof junit === "string" && junit.trim() !== "" ? junit : join39(outputDir, "junit-report.xml");
355431
355935
  }
355432
355936
  function createJUnitReporter(deps) {
355433
355937
  const flows = [];
@@ -355518,11 +356022,11 @@ function buildFlowsRunDeps(args) {
355518
356022
 
355519
356023
  // src/commands/flows/loadEnvFile.ts
355520
356024
  import { readFile } from "node:fs/promises";
355521
- import { join as join38 } from "node:path";
356025
+ import { join as join40 } from "node:path";
355522
356026
  async function loadEnvFile(envDir) {
355523
356027
  let content;
355524
356028
  try {
355525
- content = await readFile(join38(envDir, ".env"), "utf8");
356029
+ content = await readFile(join40(envDir, ".env"), "utf8");
355526
356030
  } catch (err) {
355527
356031
  if (isNoEntError(err))
355528
356032
  return;
@@ -355567,7 +356071,7 @@ async function runStagedFlows(args) {
355567
356071
  for (const candidate of staged.outerHop.rejected) {
355568
356072
  log31.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
355569
356073
  }
355570
- const ownModules = projectDir === undefined ? undefined : join39(projectDir, "node_modules");
356074
+ const ownModules = projectDir === undefined ? undefined : join41(projectDir, "node_modules");
355571
356075
  const own = staged.outerHop.rejected.find((c3) => c3.dir === ownModules);
355572
356076
  if (own !== undefined) {
355573
356077
  ctx.ui.warn(runnerMessages.outerHopFallbackNotice(own.dir, own.missing));
@@ -355613,7 +356117,7 @@ function makeDefaultDeps2(fs9) {
355613
356117
  flowsRun
355614
356118
  };
355615
356119
  }
355616
- async function handleFlowsRun(ctx, pattern, flags, deps) {
356120
+ async function handleFlowsRun(ctx, pattern, flags, deps, selectors = { tags: [] }, allEnvs = false) {
355617
356121
  const resolvedDeps = deps ?? makeDefaultDeps2(ctx.fs);
355618
356122
  const cwd = process.cwd();
355619
356123
  const expandedFiles = await resolvedDeps.expandPatterns(buildPatternArgs(pattern), cwd, ctx.log("flows"));
@@ -355625,20 +356129,133 @@ async function handleFlowsRun(ctx, pattern, flags, deps) {
355625
356129
  notice: runnerMessages.noFlowsMatched
355626
356130
  });
355627
356131
  }
356132
+ const selected = await selectRunByTag({
356133
+ files: expandedFiles,
356134
+ selectors,
356135
+ readCachedTags: (files) => readCachedTags(files, ctx.fs),
356136
+ chooseEnv: (files) => chooseEnv(ctx, files, allEnvs),
356137
+ noMatch: (error51) => noMatchResult(ctx, {
356138
+ allowNoMatch: flags.allowNoMatch,
356139
+ error: error51,
356140
+ notice: runnerMessages.noFlowsMatched
356141
+ })
356142
+ });
356143
+ if (!selected.ok)
356144
+ return selected.result;
355628
356145
  return runStagedFlows({
355629
356146
  ctx,
355630
- files: expandedFiles,
356147
+ files: selected.files,
355631
356148
  flags,
355632
356149
  deps: resolvedDeps
355633
356150
  });
355634
356151
  }
356152
+ async function chooseEnv(ctx, files, allEnvs) {
356153
+ const choice = await resolveEnvChoice({
356154
+ files,
356155
+ allEnvs,
356156
+ mode: ctx.ui.mode,
356157
+ select: ctx.ui.select,
356158
+ readLabel: (dir) => readEnvLabel(dir, ctx.fs)
356159
+ });
356160
+ if (choice.kind === "proceed")
356161
+ return { proceed: choice.files };
356162
+ if (choice.kind === "cancelled") {
356163
+ ctx.ui.info(environmentsMessages.aborted);
356164
+ return { stop: undefined };
356165
+ }
356166
+ return { stop: { error: choice.error, exitCode: exitCodes.invalidArgs } };
356167
+ }
356168
+
356169
+ // src/commands/flows/hybridRunDefaults.ts
356170
+ import { join as join42, resolve as resolve10 } from "node:path";
356171
+
356172
+ // src/core/selectFlowFiles.ts
356173
+ function selectFlowFiles(args) {
356174
+ if (!hasSelectors(args.selectors)) {
356175
+ return { kind: "selected", files: [...args.files] };
356176
+ }
356177
+ const matched = args.files.filter((file2) => matchesSelectors({ tags: args.tagsByPath?.get(toRepoRelativePath(file2, args.cwd)) }, args.selectors));
356178
+ if (matched.length === 0)
356179
+ return { kind: "empty" };
356180
+ return { kind: "selected", files: matched };
356181
+ }
356182
+
356183
+ // src/domains/flows/applyFlowSelectors.ts
356184
+ async function applyFlowSelectors(args) {
356185
+ if (!hasSelectors(args.selectors)) {
356186
+ return { ok: true, files: [...args.files] };
356187
+ }
356188
+ const resolution = await args.resolveTags();
356189
+ if (resolution.kind === "unavailable") {
356190
+ return { ok: false, result: tagsUnavailableResult(args.envId) };
356191
+ }
356192
+ if (resolution.kind === "cached") {
356193
+ args.warn(flowsMessages.selectors.usingCachedTags(resolution.fetchedAt));
356194
+ }
356195
+ const selection = selectFlowFiles({
356196
+ files: args.files,
356197
+ cwd: args.cwd,
356198
+ selectors: args.selectors,
356199
+ tagsByPath: resolution.byPath
356200
+ });
356201
+ if (selection.kind === "selected") {
356202
+ return { ok: true, files: selection.files };
356203
+ }
356204
+ return {
356205
+ ok: false,
356206
+ result: args.onEmpty(explainEmptySelection(args.selectors, await args.fetchKnownTags()))
356207
+ };
356208
+ }
356209
+
356210
+ // src/domains/flows/resolveTags.ts
356211
+ async function fetchLive(ctx, envId) {
356212
+ try {
356213
+ const result = await ctx.platformClient.callPublicApi(publicContractsV1.flow.list, { environmentId: envId, includeDrafts: true });
356214
+ if (!result.ok)
356215
+ return;
356216
+ return new Map(result.value.flows.map((f3) => [f3.path, f3.tags]));
356217
+ } catch {
356218
+ return;
356219
+ }
356220
+ }
356221
+ async function readCache(envDir, fs9) {
356222
+ const manifest = await readManifest(envDir, fs9);
356223
+ if (typeof manifest === "string")
356224
+ return;
356225
+ if (manifest.tagsFetchedAt === undefined)
356226
+ return;
356227
+ const byPath = new Map;
356228
+ for (const flow of manifest.flows) {
356229
+ if (flow.tags !== undefined)
356230
+ byPath.set(toPosix(flow.path), flow.tags);
356231
+ }
356232
+ return { byPath, fetchedAt: manifest.tagsFetchedAt };
356233
+ }
356234
+ async function resolveTags(ctx, envId, envDir, fs9 = makeDefaultFs()) {
356235
+ const live = await fetchLive(ctx, envId);
356236
+ if (live)
356237
+ return { kind: "live", byPath: live };
356238
+ const cached2 = await readCache(envDir, fs9);
356239
+ if (cached2) {
356240
+ return {
356241
+ kind: "cached",
356242
+ byPath: cached2.byPath,
356243
+ fetchedAt: cached2.fetchedAt
356244
+ };
356245
+ }
356246
+ return { kind: "unavailable" };
356247
+ }
355635
356248
 
355636
356249
  // src/commands/flows/hybridRunDefaults.ts
355637
- import { join as join40, resolve as resolve10 } from "node:path";
355638
356250
  function makeDefaultHybridDeps(fs9) {
355639
356251
  return {
355640
356252
  expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs9),
355641
- pullEnv: (ctx, envId) => handleFlowsPull(ctx, { env: envId, yes: true }),
356253
+ pullEnv: (ctx, envId, identity) => handleFlowsPull(ctx, {
356254
+ env: envId,
356255
+ yes: true,
356256
+ envSlug: identity.slug,
356257
+ envName: identity.name
356258
+ }),
355642
356259
  resolveDepsRoot: (args) => resolveDepsRoot({ ...args, fs: fs9 }),
355643
356260
  prepareRunDir: (args) => prepareRunDir({ ...args, fs: fs9 }),
355644
356261
  configureTestkit,
@@ -355647,18 +356264,20 @@ function makeDefaultHybridDeps(fs9) {
355647
356264
  createFlowRuntimeDeps
355648
356265
  };
355649
356266
  }
355650
- async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
355651
- const resolvedDeps = deps ?? makeDefaultHybridDeps(ctx.fs);
356267
+ async function handleHybridFlowsRun(ctx, pattern, flags, options = {}) {
356268
+ const identity = options.identity ?? { slug: undefined, name: undefined };
356269
+ const selectors = options.selectors ?? { tags: [] };
356270
+ const resolvedDeps = options.deps ?? makeDefaultHybridDeps(ctx.fs);
355652
356271
  const validation = validateEnvId(flags.env);
355653
356272
  if (validation !== "ok") {
355654
356273
  return { error: validation.error, exitCode: 2 };
355655
356274
  }
355656
- const envDir = resolve10(join40(".qawolf", flags.env));
356275
+ const envDir = resolve10(join42(".qawolf", flags.env));
355657
356276
  const patternArgs = buildPatternArgs(pattern);
355658
356277
  const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
355659
356278
  let files = await globFlows();
355660
356279
  if (files.length === 0) {
355661
- const pullResult = await resolvedDeps.pullEnv(ctx, flags.env);
356280
+ const pullResult = await resolvedDeps.pullEnv(ctx, flags.env, identity);
355662
356281
  if (pullResult !== undefined)
355663
356282
  return pullResult;
355664
356283
  files = await globFlows();
@@ -355670,36 +356289,73 @@ async function handleHybridFlowsRun(ctx, pattern, flags, deps) {
355670
356289
  });
355671
356290
  }
355672
356291
  }
355673
- return runStagedFlows({ ctx, files, flags, envDir, deps: resolvedDeps });
356292
+ const selection = await applyFlowSelectors({
356293
+ files,
356294
+ cwd: envDir,
356295
+ selectors,
356296
+ envId: flags.env,
356297
+ warn: (message) => ctx.ui.warn(message),
356298
+ resolveTags: () => resolveTags(ctx, flags.env, envDir, ctx.fs),
356299
+ fetchKnownTags: () => fetchKnownTags(ctx),
356300
+ onEmpty: (error51) => noMatchResult(ctx, {
356301
+ allowNoMatch: flags.allowNoMatch,
356302
+ error: error51,
356303
+ notice: runnerMessages.noFlowsMatched
356304
+ })
356305
+ });
356306
+ if (!selection.ok)
356307
+ return selection.result;
356308
+ return runStagedFlows({
356309
+ ctx,
356310
+ files: selection.files,
356311
+ flags,
356312
+ envDir,
356313
+ deps: resolvedDeps
356314
+ });
356315
+ }
356316
+
356317
+ // src/commands/flows/runAction.ts
356318
+ function makeFlowsRunAction(signals) {
356319
+ return (pattern, opts, command) => {
356320
+ const selectors = { tags: opts.tag };
356321
+ const allEnvsWarning = allEnvsNoEffectWarning({
356322
+ allEnvs: opts.allEnvs ?? false,
356323
+ env: opts.env,
356324
+ tags: opts.tag
356325
+ });
356326
+ if (opts.env !== undefined) {
356327
+ return withResolvedEnv(signals, {
356328
+ explicit: opts.env,
356329
+ requiredMessage: flowsMessages.run.requiresEnv,
356330
+ offlineFallback: async (explicit) => explicit === undefined ? undefined : (await findPulledEnv(explicit, process.cwd()))?.envId
356331
+ }, (ctx, env3, identity) => {
356332
+ if (allEnvsWarning !== undefined)
356333
+ ctx.ui.warn(allEnvsWarning);
356334
+ return handleHybridFlowsRun(ctx, pattern, { ...opts, env: env3 }, { identity, selectors });
356335
+ })(opts, command);
356336
+ }
356337
+ return withContext(signals, (ctx) => {
356338
+ if (allEnvsWarning !== undefined)
356339
+ ctx.ui.warn(allEnvsWarning);
356340
+ return handleFlowsRun(ctx, pattern, opts, undefined, selectors, opts.allEnvs);
356341
+ })(opts, command);
356342
+ };
355674
356343
  }
355675
356344
 
355676
356345
  // src/commands/flows/run.register.ts
355677
- var videoModes = ["on", "off", "retain-on-failure"];
355678
- var traceModes = ["on", "off", "retain-on-failure"];
355679
- var harModes = ["on", "off", "retain-on-failure"];
355680
- var harContentModes = ["full", "omit"];
355681
- var videoDefault = "off";
355682
- var traceDefault = "off";
355683
- var harDefault = "off";
355684
- var harContentDefault = "omit";
355685
356346
  var runExamples = `
355686
356347
  Examples:
355687
356348
  $ qawolf flows run
355688
356349
  $ qawolf flows run "flows/checkout/**"
355689
356350
  $ qawolf flows run --retries 2 --video retain-on-failure
356351
+ $ qawolf flows run --tag auth
356352
+ $ qawolf flows run --tag auth --env staging
355690
356353
  $ qawolf flows run checkout --env staging --headed`;
355691
356354
  function registerFlowsRunCommand(flows, signals) {
355692
- declareCommandKind(flows.command("run [pattern]"), "local", {
356355
+ const command = declareCommandKind(flows.command("run [pattern]"), "local", {
355693
356356
  kindNote: "read with --env"
355694
- }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000).option("--video <mode>", "Record video: on | off | retain-on-failure", parseEnum("--video", videoModes), videoDefault).option("--trace <mode>", "Record Playwright trace: on | off | retain-on-failure", parseEnum("--trace", traceModes), traceDefault).option("--har <mode>", "Record HAR network log: on | off | retain-on-failure", parseEnum("--har", harModes), harDefault).option("--har-content <mode>", "HAR response bodies: omit | full (full uses more memory)", parseEnum("--har-content", harContentModes), harContentDefault).option("--output-dir <path>", "Directory for run artifacts (videos, traces, HAR)", defaultOutputDir).option("--junit [path]", "Write a JUnit XML report (default: <output-dir>/junit-report.xml)").option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").option("--allow-no-match", "Exit 0 instead of 2 when the pattern selects no runnable flow", false).option("--no-browser-deps", "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present").addHelpText("after", runExamples).action((pattern, opts, command) => {
355695
- if (opts.env !== undefined) {
355696
- return withResolvedEnv(signals, {
355697
- explicit: opts.env,
355698
- requiredMessage: flowsMessages.run.requiresEnv
355699
- }, (ctx, env3) => handleHybridFlowsRun(ctx, pattern, { ...opts, env: env3 }))(opts, command);
355700
- }
355701
- return withContext(signals, (ctx) => handleFlowsRun(ctx, pattern, opts))(opts, command);
355702
- });
356357
+ }).description("Run flows matching [pattern], or every flow when omitted; with --env, pull missing flows from that QA Wolf environment").option("--retries <n>", "Retry each failing flow up to this many times", parseInteger("--retries", { min: 0 }), 0).option("--bail", "Stop the run after the first failure", false).option("--workers <n>", "Parallel worker count for web flows", parseInteger("--workers", { min: 1 }), 1).option("--timeout <ms>", "Default timeout for actions and assertions, in milliseconds", parseInteger("--timeout", { min: 0 }), 30000);
356358
+ addRunArtifactOptions(command).option("--headed", "Show the browser window instead of headless", false).option("--env <env>", "Pull and run a flow from this environment (UUID or slug) if not cached locally").option("--deps <dir>", "Use this prepared dependency directory instead of auto-installing the runtime; or set QAWOLF_RUNTIME_DIR to relocate the managed runtime").option("--allow-no-match", "Exit 0 instead of 2 when the pattern selects no runnable flow", false).option("--no-browser-deps", "Skip installing OS-level browser dependencies (Linux --with-deps, which needs root); requires the system libraries to already be present").option("--tag <name>", "Only run flows carrying this tag; repeat for several. Without --env, matches against tags cached by the last pull", collectValue, []).option("--all-envs", "When a tag matches flows in several pulled environments, run every match instead of choosing one", false).addHelpText("after", runExamples).action(makeFlowsRunAction(signals));
355703
356359
  }
355704
356360
 
355705
356361
  // src/domains/runner/executeWorkerFlow.ts
@@ -355772,6 +356428,9 @@ Examples:
355772
356428
  $ qawolf flows list
355773
356429
  $ qawolf flows list "flows/checkout/**"
355774
356430
  $ qawolf flows list --remote --env staging
356431
+ $ qawolf flows list --tag auth
356432
+ $ qawolf flows list --env staging --tag auth
356433
+ $ qawolf flows list --remote --env staging --tag auth --tag smoke
355775
356434
  $ qawolf flows list "**/checkout/**" --remote --env staging --include-drafts`;
355776
356435
  function registerFlowsCommand(program2, signals) {
355777
356436
  const flows = program2.command("flows").description("Manage and run QA Wolf flows");
@@ -355779,33 +356438,35 @@ function registerFlowsCommand(program2, signals) {
355779
356438
  registerRunWorkerCommand(flows, signals);
355780
356439
  declareCommandKind(flows.command("list [pattern]"), "local", {
355781
356440
  kindNote: "read with --remote"
355782
- }).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 (with --remote; defaults to QAWOLF_ENVIRONMENT, or an interactive picker)").option("--include-drafts", "Include draft flows in the listing (requires --remote)", false).addHelpText("after", listExamples).action((pattern, opts, command) => {
356441
+ }).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: a QA Wolf environment with --remote, otherwise a pulled one by slug or id").option("--include-drafts", "Include draft flows in the listing (requires --remote)", false).option("--tag <name>", "Only list flows carrying this tag; repeat for several. Without --remote, matches against tags cached by the last pull", collectValue, []).addHelpText("after", listExamples).action((pattern, opts, command) => {
356442
+ const tags = opts.tag;
355783
356443
  if (opts.remote) {
355784
356444
  return withResolvedEnv(signals, {
355785
356445
  explicit: opts.env,
355786
356446
  requiredMessage: flowsMessages.list.remoteRequiresEnv
355787
356447
  }, (ctx, env3) => flowsListRemote(ctx, pattern, {
355788
356448
  env: env3,
355789
- includeDrafts: opts.includeDrafts
356449
+ includeDrafts: opts.includeDrafts,
356450
+ tags
355790
356451
  }))(opts, command);
355791
356452
  }
355792
- if (opts.env !== undefined || opts.includeDrafts) {
356453
+ if (opts.includeDrafts) {
355793
356454
  return withContext(signals, async () => ({
355794
- error: flowsMessages.list.flagsRequireRemote
356455
+ error: flowsMessages.list.draftsRequireRemote
355795
356456
  }))(opts, command);
355796
356457
  }
355797
- return withContext(signals, (ctx) => handleFlowsList(ctx, pattern))(opts, command);
356458
+ return withContext(signals, (ctx) => handleFlowsList(ctx, pattern, { tags, env: opts.env }))(opts, command);
355798
356459
  });
355799
356460
  registerFlowsPullCommand(flows, signals);
355800
356461
  }
355801
356462
 
355802
356463
  // src/domains/init/init.ts
355803
- import { dirname as dirname14, join as join42, relative as relative7 } from "node:path";
356464
+ import { dirname as dirname14, join as join44, relative as relative9 } from "node:path";
355804
356465
 
355805
356466
  // src/domains/init/ensurePackageJson.ts
355806
- import { join as join41 } from "node:path";
356467
+ import { join as join43 } from "node:path";
355807
356468
  async function ensurePackageJson(ctx, yes, deps) {
355808
- const pkgPath = join41(deps.cwd, "package.json");
356469
+ const pkgPath = join43(deps.cwd, "package.json");
355809
356470
  if (!await deps.fs.pathExists(pkgPath)) {
355810
356471
  const confirmed2 = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
355811
356472
  if (!confirmed2.ok || !confirmed2.value) {
@@ -355931,9 +356592,9 @@ function makeDefaultInitDeps() {
355931
356592
  async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
355932
356593
  ctx.ui.gap();
355933
356594
  ctx.ui.intro(initMessages.title);
355934
- const configPath = join42(deps.cwd, "qawolf.config.ts");
355935
- const flowPath = join42(deps.cwd, "src", "flows", "example.flow.ts");
355936
- const gitignorePath = join42(deps.cwd, ".qawolf", ".gitignore");
356595
+ const configPath = join44(deps.cwd, "qawolf.config.ts");
356596
+ const flowPath = join44(deps.cwd, "src", "flows", "example.flow.ts");
356597
+ const gitignorePath = join44(deps.cwd, ".qawolf", ".gitignore");
355937
356598
  await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
355938
356599
  await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
355939
356600
  await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
@@ -355941,7 +356602,7 @@ async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
355941
356602
  ctx.ui.outro(initMessages.outro);
355942
356603
  }
355943
356604
  async function writeWithPrompt(ctx, filePath, content, yes, deps) {
355944
- const relPath = relative7(deps.cwd, filePath);
356605
+ const relPath = relative9(deps.cwd, filePath);
355945
356606
  if (await deps.fs.pathExists(filePath)) {
355946
356607
  const confirmed = await ctx.ui.confirm(initMessages.overwritePrompt(relPath), {
355947
356608
  yes,
@@ -355971,7 +356632,7 @@ Examples:
355971
356632
  import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
355972
356633
 
355973
356634
  // src/domains/install/android/avd.ts
355974
- import { join as join43 } from "node:path";
356635
+ import { join as join45 } from "node:path";
355975
356636
  async function installAvds(ctx, specs, deps) {
355976
356637
  const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
355977
356638
  const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
@@ -355981,7 +356642,7 @@ async function installAvds(ctx, specs, deps) {
355981
356642
  if (versionResult.exitCode !== 0) {
355982
356643
  throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
355983
356644
  }
355984
- const licenseFile = join43(deps.androidHome, "licenses", "android-sdk-license");
356645
+ const licenseFile = join45(deps.androidHome, "licenses", "android-sdk-license");
355985
356646
  if (deps.checkExists(licenseFile)) {
355986
356647
  ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
355987
356648
  } else {
@@ -355996,7 +356657,7 @@ async function installAvds(ctx, specs, deps) {
355996
356657
  for (const spec of specs) {
355997
356658
  if (installedImages.has(spec.systemImage))
355998
356659
  continue;
355999
- const systemImg = join43(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
356660
+ const systemImg = join45(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
356000
356661
  if (deps.checkExists(systemImg)) {
356001
356662
  ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
356002
356663
  installedImages.add(spec.systemImage);
@@ -356882,7 +357543,7 @@ function describeFailure(failureReason) {
356882
357543
  }
356883
357544
 
356884
357545
  // src/shell/interactiveRunner/collectRunFiles.ts
356885
- import { join as join45 } from "node:path";
357546
+ import { join as join47 } from "node:path";
356886
357547
 
356887
357548
  // src/core/interactiveRunner/getImports.ts
356888
357549
  function getImports(options) {
@@ -356921,7 +357582,7 @@ function isPathAlias(importPath, paths) {
356921
357582
  }
356922
357583
 
356923
357584
  // src/core/interactiveRunner/resolveImportPath.ts
356924
- import { dirname as dirname15, join as join44, normalize as normalize3 } from "node:path/posix";
357585
+ import { dirname as dirname15, join as join46, normalize as normalize4 } from "node:path/posix";
356925
357586
 
356926
357587
  // src/core/interactiveRunner/tsconfigPaths.ts
356927
357588
  function isRecord5(value) {
@@ -356974,7 +357635,7 @@ function candidatesForExplicitExtension(options) {
356974
357635
  }
356975
357636
  function resolveImportPath(options) {
356976
357637
  const aliasTarget = resolvePathAlias(options.importPath, options.tsconfigPaths);
356977
- const resolvedPath = aliasTarget === undefined ? normalize3(join44(dirname15(options.importingFilePath), options.importPath)) : normalize3(aliasTarget);
357638
+ const resolvedPath = aliasTarget === undefined ? normalize4(join46(dirname15(options.importingFilePath), options.importPath)) : normalize4(aliasTarget);
356978
357639
  const hasExplicitExtension = supportedExtensions.some((extension) => options.importPath.endsWith(extension));
356979
357640
  const candidates = hasExplicitExtension ? candidatesForExplicitExtension({
356980
357641
  importPath: options.importPath,
@@ -356998,7 +357659,7 @@ function resolveImportPath(options) {
356998
357659
  var tsconfigPath = "tsconfig.json";
356999
357660
  async function collectRunFiles(options) {
357000
357661
  const held = await listShippablePaths(options);
357001
- const readFile2 = (path9) => options.fs.readFile(join45(options.cwd, path9));
357662
+ const readFile2 = (path9) => options.fs.readFile(join47(options.cwd, path9));
357002
357663
  const tsconfigContent = held.has(tsconfigPath) ? await readFile2(tsconfigPath).catch(() => {
357003
357664
  return;
357004
357665
  }) : undefined;
@@ -357081,7 +357742,7 @@ function makeRunnerId() {
357081
357742
  }
357082
357743
 
357083
357744
  // src/shell/interactiveRunner/runFilesManifest.ts
357084
- import { join as join46 } from "node:path";
357745
+ import { join as join48 } from "node:path";
357085
357746
  var manifestFileName = "runner-files.json";
357086
357747
  var manifestSchema2 = exports_external.object({
357087
357748
  files: exports_external.array(exports_external.object({ contentHash: exports_external.string(), path: exports_external.string() })),
@@ -357089,8 +357750,8 @@ var manifestSchema2 = exports_external.object({
357089
357750
  version: exports_external.literal(1)
357090
357751
  });
357091
357752
  function makeRunFilesManifestStore(options) {
357092
- const directory = join46(options.cwd, qawolfDir);
357093
- const path9 = join46(directory, manifestFileName);
357753
+ const directory = join48(options.cwd, qawolfDir);
357754
+ const path9 = join48(directory, manifestFileName);
357094
357755
  let pendingWrites = 0;
357095
357756
  return {
357096
357757
  async read() {
@@ -357120,7 +357781,7 @@ function parseJson(text3) {
357120
357781
  }
357121
357782
 
357122
357783
  // src/shell/interactiveRunner/runnerStore.ts
357123
- import { join as join47 } from "node:path";
357784
+ import { join as join49 } from "node:path";
357124
357785
  var storeFileName = "runner.json";
357125
357786
  var runnersDirName = "runners";
357126
357787
  var storedRunnerSchema = exports_external.object({
@@ -357139,11 +357800,11 @@ function parseJson2(text3) {
357139
357800
  }
357140
357801
  var pendingWrites = 0;
357141
357802
  function makeRunnerStore(options) {
357142
- const directory = join47(options.cwd, qawolfDir);
357143
- const path9 = join47(directory, storeFileName);
357144
- const runnersDir = join47(directory, runnersDirName);
357803
+ const directory = join49(options.cwd, qawolfDir);
357804
+ const path9 = join49(directory, storeFileName);
357805
+ const runnersDir = join49(directory, runnersDirName);
357145
357806
  const nextPendingPath = (target) => `${target}.${process.pid}.${++pendingWrites}.tmp`;
357146
- const runnerPath = (runnerId) => join47(runnersDir, `${encodeURIComponent(runnerId)}.json`);
357807
+ const runnerPath = (runnerId) => join49(runnersDir, `${encodeURIComponent(runnerId)}.json`);
357147
357808
  const writeAtomically = async (target, contents2) => {
357148
357809
  const pendingPath = nextPendingPath(target);
357149
357810
  await options.fs.writeFile(pendingPath, `${JSON.stringify(contents2, undefined, 2)}
@@ -357180,7 +357841,7 @@ function makeRunnerStore(options) {
357180
357841
  readDefaultRunnerId,
357181
357842
  async readRunners() {
357182
357843
  const runners = await Promise.all((await readRunnerFileNames()).map(async (name) => {
357183
- const contents2 = await options.fs.readFile(join47(runnersDir, name)).catch(() => {
357844
+ const contents2 = await options.fs.readFile(join49(runnersDir, name)).catch(() => {
357184
357845
  return;
357185
357846
  });
357186
357847
  if (contents2 === undefined)
@@ -357199,7 +357860,7 @@ function makeRunnerStore(options) {
357199
357860
  await Promise.all(runnerIds.map((runnerId) => options.fs.rm(runnerPath(runnerId), { force: true })));
357200
357861
  const names = await readRunnerFileNames();
357201
357862
  await Promise.all(names.map(async (name) => {
357202
- const runnerFile = join47(runnersDir, name);
357863
+ const runnerFile = join49(runnersDir, name);
357203
357864
  const contents2 = await options.fs.readFile(runnerFile).catch(() => {
357204
357865
  return;
357205
357866
  });
@@ -357283,7 +357944,7 @@ function registerRunnerHighlightSelectorCommand(runner, signals) {
357283
357944
  }
357284
357945
 
357285
357946
  // src/domains/interactiveRunner/importPackage.ts
357286
- import { join as join48 } from "node:path";
357947
+ import { join as join50 } from "node:path";
357287
357948
 
357288
357949
  // src/core/interactiveRunner/npmDependencies.ts
357289
357950
  var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
@@ -357324,7 +357985,7 @@ function readNpmDependencies(packageJsonContent) {
357324
357985
  // src/domains/interactiveRunner/importPackage.ts
357325
357986
  var defaultPackageVersion = "latest";
357326
357987
  async function handleRunnerImportPackage(ctx, options, deps) {
357327
- const content = await deps.readFile(join48(deps.cwd, runPackageJsonPath)).catch(() => {
357988
+ const content = await deps.readFile(join50(deps.cwd, runPackageJsonPath)).catch(() => {
357328
357989
  return;
357329
357990
  });
357330
357991
  if (content === undefined) {
@@ -357662,10 +358323,10 @@ function registerRunnerInspectCommands(runner, signals) {
357662
358323
  }
357663
358324
 
357664
358325
  // src/core/interactiveRunner/runFiles.ts
357665
- import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve11, sep as sep5 } from "node:path";
358326
+ import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep5 } from "node:path";
357666
358327
  function toCollectedPath(cwd, path9) {
357667
358328
  const absolute = isAbsolute5(path9) ? path9 : resolve11(cwd, path9);
357668
- return relative8(cwd, absolute).split(sep5).join("/");
358329
+ return relative10(cwd, absolute).split(sep5).join("/");
357669
358330
  }
357670
358331
  var namedLargestFileCount = 3;
357671
358332
  function largestFiles(files) {
@@ -358106,7 +358767,7 @@ async function handleRunnerLaunch(ctx, options, deps) {
358106
358767
  }
358107
358768
 
358108
358769
  // src/domains/interactiveRunner/list.ts
358109
- var columns2 = [
358770
+ var columns = [
358110
358771
  { header: "id", value: (row) => row.id },
358111
358772
  { header: "family", value: (row) => row.runnerName ?? "" },
358112
358773
  { header: "default", value: (row) => row.isDefault ? "yes" : "" }
@@ -358164,12 +358825,12 @@ async function handleRunnerList(ctx, deps) {
358164
358825
  return;
358165
358826
  }
358166
358827
  if (ctx.ui.mode === "agent") {
358167
- ctx.ui.write(renderTable({ boldHeader: false, columns: columns2, rows: items }));
358828
+ ctx.ui.write(renderTable({ boldHeader: false, columns, rows: items }));
358168
358829
  return;
358169
358830
  }
358170
358831
  ctx.ui.gap();
358171
358832
  ctx.ui.intro(interactiveRunnerMessages.title);
358172
- ctx.ui.write(renderTable({ boldHeader: true, columns: columns2, rows: items }));
358833
+ ctx.ui.write(renderTable({ boldHeader: true, columns, rows: items }));
358173
358834
  ctx.ui.outro(interactiveRunnerMessages.runnerCount(items.length));
358174
358835
  }
358175
358836
 
@@ -359030,4 +359691,4 @@ createProgram({ signals }).parseAsync().catch(() => {
359030
359691
  process.exitCode = 1;
359031
359692
  }).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
359032
359693
 
359033
- //# debugId=3E751EC68D3A12B564756E2164756E21
359694
+ //# debugId=50C554904C1768A664756E2164756E21