@wdio/browserstack-service 9.27.2 → 9.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cleanup.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/util.ts
2
2
  import { hostname as hostname2, platform as platform3, type as type2, version as version2, arch as arch3, tmpdir } from "node:os";
3
3
  import crypto from "node:crypto";
4
- import fs7 from "node:fs";
4
+ import fs8 from "node:fs";
5
5
  import zlib from "node:zlib";
6
6
  import { format, promisify as promisify2 } from "node:util";
7
- import path6 from "node:path";
7
+ import path7 from "node:path";
8
8
  import util3 from "node:util";
9
9
  import gitRepoInfo from "git-repo-info";
10
10
  import gitconfig from "gitconfiglocal";
@@ -53,12 +53,12 @@ var logPatcher_default = logPatcher;
53
53
 
54
54
  // src/instrumentation/performance/performance-tester.ts
55
55
  import { createObjectCsvWriter } from "csv-writer";
56
- import fs4 from "node:fs";
56
+ import fs5 from "node:fs";
57
57
  import fsPromise from "node:fs/promises";
58
58
  import { performance, PerformanceObserver } from "node:perf_hooks";
59
59
  import util2 from "node:util";
60
60
  import worker from "node:worker_threads";
61
- import path4 from "node:path";
61
+ import path5 from "node:path";
62
62
  import { arch as arch2, hostname, platform as platform2, type, version } from "node:os";
63
63
 
64
64
  // src/bstackLogger.ts
@@ -70,7 +70,7 @@ import logger from "@wdio/logger";
70
70
  // package.json
71
71
  var package_default = {
72
72
  name: "@wdio/browserstack-service",
73
- version: "9.27.1",
73
+ version: "9.27.2",
74
74
  description: "WebdriverIO service for better Browserstack integration",
75
75
  author: "Adam Bjerstedt <abjerstedt@gmail.com>",
76
76
  homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
@@ -266,10 +266,10 @@ var APIUtils = class {
266
266
  };
267
267
 
268
268
  // src/cli/cliUtils.ts
269
- import fs3 from "node:fs";
269
+ import fs4 from "node:fs";
270
270
  import fsp from "node:fs/promises";
271
271
  import { platform, arch, homedir } from "node:os";
272
- import path3 from "node:path";
272
+ import path4 from "node:path";
273
273
  import util, { promisify } from "node:util";
274
274
  import { exec } from "node:child_process";
275
275
  import { Readable } from "node:stream";
@@ -277,7 +277,20 @@ import yauzl from "yauzl";
277
277
  import { threadId } from "node:worker_threads";
278
278
 
279
279
  // src/fetchWrapper.ts
280
- import { fetch as undiciFetch, ProxyAgent } from "undici";
280
+ import { fetch as undiciFetch, ProxyAgent, Agent as Agent2 } from "undici";
281
+
282
+ // src/caCert.ts
283
+ import { setGlobalDispatcher, Agent } from "undici";
284
+ import tls from "node:tls";
285
+ import fs2 from "node:fs";
286
+ import os from "node:os";
287
+ import path2 from "node:path";
288
+ var mergedCa;
289
+ function getMergedCa() {
290
+ return mergedCa;
291
+ }
292
+
293
+ // src/fetchWrapper.ts
281
294
  var ResponseError = class extends Error {
282
295
  response;
283
296
  constructor(message, res) {
@@ -285,26 +298,56 @@ var ResponseError = class extends Error {
285
298
  this.response = res;
286
299
  }
287
300
  };
288
- async function fetchWrap(input, init) {
289
- const res = await _fetch(input, init);
301
+ var dispatcherCache = /* @__PURE__ */ new Map();
302
+ function getDispatcher(proxyUrl, connectTimeoutMs) {
303
+ const ca = getMergedCa();
304
+ const key = `${proxyUrl ?? "direct"}:${connectTimeoutMs ?? "default"}:${ca ? "ca" : "noca"}`;
305
+ let dispatcher = dispatcherCache.get(key);
306
+ if (!dispatcher) {
307
+ const connect = {};
308
+ if (connectTimeoutMs) {
309
+ connect.timeout = connectTimeoutMs;
310
+ }
311
+ if (proxyUrl) {
312
+ dispatcher = new ProxyAgent({ uri: proxyUrl, connect, ...ca ? { requestTls: { ca } } : {} });
313
+ } else {
314
+ if (ca) {
315
+ connect.ca = ca;
316
+ }
317
+ dispatcher = new Agent2({ connect });
318
+ }
319
+ dispatcherCache.set(key, dispatcher);
320
+ }
321
+ return dispatcher;
322
+ }
323
+ async function fetchWrap(input, init, options) {
324
+ const res = await _fetch(input, init, options);
290
325
  if (!res.ok) {
291
326
  throw new ResponseError(`Error response from server ${res.status}: ${await res.text()}`, res);
292
327
  }
293
328
  return res;
294
329
  }
295
- function _fetch(input, init) {
330
+ function _fetch(input, init, options) {
331
+ const connectTimeoutMs = options?.connectTimeoutMs;
296
332
  const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
297
333
  if (proxyUrl) {
298
334
  const noProxy = process.env.NO_PROXY && process.env.NO_PROXY.trim() ? process.env.NO_PROXY.trim().split(/[\s,;]+/) : [];
299
335
  const request = new Request(input);
300
336
  const url = new URL(request.url);
301
337
  if (!noProxy.some((str) => url.hostname.endsWith(str))) {
338
+ const dispatcher = getDispatcher(proxyUrl, connectTimeoutMs);
302
339
  return undiciFetch(
303
340
  request.url,
304
- { ...init, dispatcher: new ProxyAgent(proxyUrl) }
341
+ { ...init, dispatcher }
305
342
  );
306
343
  }
307
344
  }
345
+ if (connectTimeoutMs) {
346
+ return undiciFetch(
347
+ new Request(input).url,
348
+ { ...init, dispatcher: getDispatcher(void 0, connectTimeoutMs) }
349
+ );
350
+ }
308
351
  return fetch(input, init);
309
352
  }
310
353
 
@@ -456,20 +499,20 @@ var DISPATCHER_EVENTS = {
456
499
  };
457
500
 
458
501
  // src/cli/cliLogger.ts
459
- import path2 from "node:path";
460
- import fs2 from "node:fs";
502
+ import path3 from "node:path";
503
+ import fs3 from "node:fs";
461
504
  import chalk2 from "chalk";
462
505
  import logger2 from "@wdio/logger";
463
506
  var log2 = logger2("@wdio/browserstack-service/cli");
464
507
  var BStackLogger2 = class {
465
- static logFilePath = path2.join(process.cwd(), LOGS_FILE);
466
- static logFolderPath = path2.join(process.cwd(), "logs");
508
+ static logFilePath = path3.join(process.cwd(), LOGS_FILE);
509
+ static logFolderPath = path3.join(process.cwd(), "logs");
467
510
  static logFileStream;
468
511
  static logToFile(logMessage, logLevel) {
469
512
  try {
470
513
  if (!this.logFileStream) {
471
514
  this.ensureLogsFolder();
472
- this.logFileStream = fs2.createWriteStream(this.logFilePath, { flags: "a" });
515
+ this.logFileStream = fs3.createWriteStream(this.logFilePath, { flags: "a" });
473
516
  }
474
517
  if (this.logFileStream && this.logFileStream.writable) {
475
518
  this.logFileStream.write(this.formatLog(logMessage, logLevel));
@@ -513,13 +556,13 @@ var BStackLogger2 = class {
513
556
  this.logFileStream = null;
514
557
  }
515
558
  static clearLogFile() {
516
- if (fs2.existsSync(this.logFilePath)) {
517
- fs2.truncateSync(this.logFilePath);
559
+ if (fs3.existsSync(this.logFilePath)) {
560
+ fs3.truncateSync(this.logFilePath);
518
561
  }
519
562
  }
520
563
  static ensureLogsFolder() {
521
- if (!fs2.existsSync(this.logFolderPath)) {
522
- fs2.mkdirSync(this.logFolderPath);
564
+ if (!fs3.existsSync(this.logFolderPath)) {
565
+ fs3.mkdirSync(this.logFolderPath);
523
566
  }
524
567
  }
525
568
  };
@@ -574,6 +617,9 @@ var CLI_LOCK_POLL_MS = 1e3;
574
617
  var CLI_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
575
618
  var CLI_DOWNLOAD_TMP_PREFIX = "downloaded_file_";
576
619
  var CLI_DOWNLOAD_TMP_SUFFIX = ".zip";
620
+ var CLI_CONNECT_TIMEOUT_MS = Number(process.env.BROWSERSTACK_CLI_CONNECT_TIMEOUT_MS) || 60 * 1e3;
621
+ var CLI_FETCH_MAX_ATTEMPTS = 3;
622
+ var CLI_FETCH_RETRY_BASE_DELAY_MS = 500;
577
623
  var CLIUtils = class _CLIUtils {
578
624
  static automationFrameworkDetail = {};
579
625
  static testFrameworkDetail = {};
@@ -680,7 +726,7 @@ var CLIUtils = class _CLIUtils {
680
726
  BStackLogger2.debug(`Resolved binary path: ${finalBinaryPath}`);
681
727
  return finalBinaryPath;
682
728
  } catch (err) {
683
- BStackLogger2.debug(
729
+ BStackLogger2.warn(
684
730
  `Error in setting up cli path directory, Exception: ${util.format(err)}`
685
731
  );
686
732
  }
@@ -691,7 +737,7 @@ var CLIUtils = class _CLIUtils {
691
737
  BStackLogger2.debug(
692
738
  `Worker process detected, skipping CLI update. Using existing: ${existingCliPath}`
693
739
  );
694
- if (existingCliPath && fs3.existsSync(existingCliPath)) {
740
+ if (existingCliPath && fs4.existsSync(existingCliPath)) {
695
741
  return existingCliPath;
696
742
  }
697
743
  BStackLogger2.warn(
@@ -751,8 +797,8 @@ var CLIUtils = class _CLIUtils {
751
797
  if (isNullOrEmpty(writableDir)) {
752
798
  throw new Error("No writable directory available for the CLI");
753
799
  }
754
- const cliDirPath = path3.join(writableDir, "cli");
755
- if (!fs3.existsSync(cliDirPath)) {
800
+ const cliDirPath = path4.join(writableDir, "cli");
801
+ if (!fs4.existsSync(cliDirPath)) {
756
802
  createDir(cliDirPath);
757
803
  }
758
804
  return cliDirPath;
@@ -766,37 +812,37 @@ var CLIUtils = class _CLIUtils {
766
812
  static getWritableDir() {
767
813
  const writableDirOptions = [
768
814
  process.env.BROWSERSTACK_FILES_DIR,
769
- path3.join(homedir(), ".browserstack"),
770
- path3.join("tmp", ".browserstack")
815
+ path4.join(homedir(), ".browserstack"),
816
+ path4.join("tmp", ".browserstack")
771
817
  ];
772
- for (const path9 of writableDirOptions) {
773
- if (isNullOrEmpty(path9)) {
818
+ for (const path10 of writableDirOptions) {
819
+ if (isNullOrEmpty(path10)) {
774
820
  continue;
775
821
  }
776
822
  try {
777
- if (fs3.existsSync(path9)) {
778
- BStackLogger2.debug(`File ${path9} already exist`);
779
- if (!isWritable(path9)) {
780
- BStackLogger2.debug(`Giving write permission to ${path9}`);
781
- const success = setReadWriteAccess(path9);
823
+ if (fs4.existsSync(path10)) {
824
+ BStackLogger2.debug(`File ${path10} already exist`);
825
+ if (!isWritable(path10)) {
826
+ BStackLogger2.debug(`Giving write permission to ${path10}`);
827
+ const success = setReadWriteAccess(path10);
782
828
  if (!isTrue(success)) {
783
829
  BStackLogger2.warn(
784
- `Unable to provide write permission to ${path9}`
830
+ `Unable to provide write permission to ${path10}`
785
831
  );
786
832
  }
787
833
  }
788
834
  } else {
789
- BStackLogger2.debug(`File does not exist: ${path9}`);
790
- createDir(path9);
791
- BStackLogger2.debug(`Giving write permission to ${path9}`);
792
- const success = setReadWriteAccess(path9);
835
+ BStackLogger2.debug(`File does not exist: ${path10}`);
836
+ createDir(path10);
837
+ BStackLogger2.debug(`Giving write permission to ${path10}`);
838
+ const success = setReadWriteAccess(path10);
793
839
  if (!isTrue(success)) {
794
840
  BStackLogger2.warn(
795
- `Unable to provide write permission to ${path9}`
841
+ `Unable to provide write permission to ${path10}`
796
842
  );
797
843
  }
798
844
  }
799
- return path9;
845
+ return path10;
800
846
  } catch (err) {
801
847
  BStackLogger2.error(
802
848
  `Unable to get writable directory, exception ${util.format(err)}`
@@ -807,16 +853,16 @@ var CLIUtils = class _CLIUtils {
807
853
  }
808
854
  static getExistingCliPath(cliDir) {
809
855
  try {
810
- if (!fs3.existsSync(cliDir) || !fs3.statSync(cliDir).isDirectory()) {
856
+ if (!fs4.existsSync(cliDir) || !fs4.statSync(cliDir).isDirectory()) {
811
857
  return "";
812
858
  }
813
- const allBinaries = fs3.readdirSync(cliDir).map((file) => path3.join(cliDir, file)).filter(
814
- (filePath) => fs3.statSync(filePath).isFile() && path3.basename(filePath).startsWith("binary-")
859
+ const allBinaries = fs4.readdirSync(cliDir).map((file) => path4.join(cliDir, file)).filter(
860
+ (filePath) => fs4.statSync(filePath).isFile() && path4.basename(filePath).startsWith("binary-")
815
861
  );
816
862
  if (allBinaries.length > 0) {
817
863
  const latestBinary = allBinaries.map((filePath) => ({
818
864
  filePath,
819
- mtime: fs3.statSync(filePath).mtime
865
+ mtime: fs4.statSync(filePath).mtime
820
866
  })).reduce(
821
867
  (latest, current) => {
822
868
  if (!latest || !latest.mtime) {
@@ -844,12 +890,12 @@ var CLIUtils = class _CLIUtils {
844
890
  if (platform() === "darwin") {
845
891
  return false;
846
892
  }
847
- if (!fs3.existsSync(binaryPath)) {
893
+ if (!fs4.existsSync(binaryPath)) {
848
894
  return false;
849
895
  }
850
896
  try {
851
- const fd = fs3.openSync(binaryPath, "r+");
852
- fs3.closeSync(fd);
897
+ const fd = fs4.openSync(binaryPath, "r+");
898
+ fs4.closeSync(fd);
853
899
  return false;
854
900
  } catch (err) {
855
901
  if (BINARY_BUSY_ERROR_CODES.includes(err.code)) {
@@ -860,6 +906,35 @@ var CLIUtils = class _CLIUtils {
860
906
  return false;
861
907
  }
862
908
  }
909
+ /**
910
+ * fetch wrapper for CLI network calls that (a) raises undici's connect timeout
911
+ * above the non-overridable 10s default of global fetch and (b) retries a few
912
+ * times with linear backoff on transient connection failures. Both are needed
913
+ * for reliability in constrained CI/containerised networks (SDK-6152).
914
+ */
915
+ static fetchWithRetry = async (input, init, label) => {
916
+ let lastError;
917
+ for (let attempt = 1; attempt <= CLI_FETCH_MAX_ATTEMPTS; attempt++) {
918
+ try {
919
+ return await _fetch(input, init, { connectTimeoutMs: CLI_CONNECT_TIMEOUT_MS });
920
+ } catch (err) {
921
+ lastError = err;
922
+ const reason = (
923
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
924
+ err?.cause?.code || err?.code || err?.message
925
+ );
926
+ BStackLogger2.warn(
927
+ `${label} attempt ${attempt}/${CLI_FETCH_MAX_ATTEMPTS} failed: ${reason}`
928
+ );
929
+ if (attempt < CLI_FETCH_MAX_ATTEMPTS) {
930
+ await new Promise(
931
+ (resolve) => setTimeout(resolve, CLI_FETCH_RETRY_BASE_DELAY_MS * attempt)
932
+ );
933
+ }
934
+ }
935
+ }
936
+ throw lastError;
937
+ };
863
938
  static requestToUpdateCLI = async (queryParams, config) => {
864
939
  const params = new URLSearchParams(queryParams);
865
940
  const requestInit = {
@@ -868,9 +943,10 @@ var CLIUtils = class _CLIUtils {
868
943
  Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString("base64")}`
869
944
  }
870
945
  };
871
- const response = await _fetch(
946
+ const response = await this.fetchWithRetry(
872
947
  `${APIUtils.BROWSERSTACK_AUTOMATE_API_URL}/${UPDATED_CLI_ENDPOINT}?${params.toString()}`,
873
- requestInit
948
+ requestInit,
949
+ "update_cli request"
874
950
  );
875
951
  const jsonResponse = await response.json();
876
952
  BStackLogger2.debug(`response ${JSON.stringify(jsonResponse)}`);
@@ -895,11 +971,11 @@ var CLIUtils = class _CLIUtils {
895
971
  });
896
972
  }
897
973
  static downloadLatestBinary = async (binDownloadUrl, cliDir) => {
898
- const lockPath = path3.join(cliDir, "download.lock");
974
+ const lockPath = path4.join(cliDir, "download.lock");
899
975
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
900
976
  const parseLockFile = () => {
901
977
  try {
902
- const content = fs3.readFileSync(lockPath, "utf8").trim();
978
+ const content = fs4.readFileSync(lockPath, "utf8").trim();
903
979
  const [pidLine, timestampLine] = content.split("\n");
904
980
  const pid = Number.parseInt(pidLine, 10);
905
981
  const timestamp = Number.parseInt(timestampLine, 10);
@@ -923,20 +999,20 @@ var CLIUtils = class _CLIUtils {
923
999
  const start = Date.now();
924
1000
  while (true) {
925
1001
  try {
926
- const fd = fs3.openSync(lockPath, "wx");
1002
+ const fd = fs4.openSync(lockPath, "wx");
927
1003
  try {
928
- fs3.writeFileSync(fd, `${process.pid}
1004
+ fs4.writeFileSync(fd, `${process.pid}
929
1005
  ${Date.now()}
930
1006
  `);
931
1007
  } catch {
932
1008
  }
933
1009
  return () => {
934
1010
  try {
935
- fs3.closeSync(fd);
1011
+ fs4.closeSync(fd);
936
1012
  } catch {
937
1013
  }
938
1014
  try {
939
- fs3.unlinkSync(lockPath);
1015
+ fs4.unlinkSync(lockPath);
940
1016
  } catch {
941
1017
  }
942
1018
  };
@@ -952,14 +1028,14 @@ ${Date.now()}
952
1028
  `Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.`
953
1029
  );
954
1030
  try {
955
- fs3.unlinkSync(lockPath);
1031
+ fs4.unlinkSync(lockPath);
956
1032
  } catch {
957
1033
  }
958
1034
  continue;
959
1035
  }
960
1036
  }
961
1037
  const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
962
- if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) {
1038
+ if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
963
1039
  BStackLogger2.debug(
964
1040
  `Binary appeared while waiting for lock: ${existingBinary}`
965
1041
  );
@@ -981,16 +1057,16 @@ ${Date.now()}
981
1057
  try {
982
1058
  const now = Date.now();
983
1059
  const tmpExtractRe = /\.tmp\.\d+$/;
984
- for (const entry of fs3.readdirSync(cliDir)) {
1060
+ for (const entry of fs4.readdirSync(cliDir)) {
985
1061
  const isDownloadZip = entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) && entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX);
986
1062
  const isExtractTmp = tmpExtractRe.test(entry);
987
1063
  if (!isDownloadZip && !isExtractTmp) {
988
1064
  continue;
989
1065
  }
990
- const filePath = path3.join(cliDir, entry);
1066
+ const filePath = path4.join(cliDir, entry);
991
1067
  let stats;
992
1068
  try {
993
- stats = fs3.statSync(filePath);
1069
+ stats = fs4.statSync(filePath);
994
1070
  } catch {
995
1071
  continue;
996
1072
  }
@@ -998,7 +1074,7 @@ ${Date.now()}
998
1074
  continue;
999
1075
  }
1000
1076
  try {
1001
- fs3.unlinkSync(filePath);
1077
+ fs4.unlinkSync(filePath);
1002
1078
  } catch (err) {
1003
1079
  BStackLogger2.debug(
1004
1080
  `Failed to delete temp CLI file ${filePath}: ${util.format(err)}`
@@ -1038,7 +1114,7 @@ ${Date.now()}
1038
1114
  }
1039
1115
  releaseLock = lockResult;
1040
1116
  const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
1041
- if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) {
1117
+ if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
1042
1118
  BStackLogger2.debug(
1043
1119
  `Binary already exists after acquiring lock: ${existingBinary}`
1044
1120
  );
@@ -1047,11 +1123,11 @@ ${Date.now()}
1047
1123
  return existingBinary;
1048
1124
  }
1049
1125
  cleanupTemporaryDownloads();
1050
- const zipFilePath = path3.join(
1126
+ const zipFilePath = path4.join(
1051
1127
  cliDir,
1052
1128
  `${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}`
1053
1129
  );
1054
- const downloadedFileStream = fs3.createWriteStream(zipFilePath);
1130
+ const downloadedFileStream = fs4.createWriteStream(zipFilePath);
1055
1131
  return new Promise((resolve, reject) => {
1056
1132
  const processDownload = async () => {
1057
1133
  const abortController = new AbortController();
@@ -1061,9 +1137,11 @@ ${Date.now()}
1061
1137
  );
1062
1138
  let response;
1063
1139
  try {
1064
- response = await _fetch(binDownloadUrl, {
1065
- signal: abortController.signal
1066
- });
1140
+ response = await _fetch(
1141
+ binDownloadUrl,
1142
+ { signal: abortController.signal },
1143
+ { connectTimeoutMs: CLI_CONNECT_TIMEOUT_MS }
1144
+ );
1067
1145
  } finally {
1068
1146
  clearTimeout(timeout);
1069
1147
  }
@@ -1133,17 +1211,17 @@ ${Date.now()}
1133
1211
  zipfile.readEntry();
1134
1212
  return;
1135
1213
  }
1136
- const candidatePath = path3.join(cliDir, entry.fileName);
1137
- const resolvedCandidate = path3.resolve(candidatePath);
1138
- const resolvedDir = path3.resolve(cliDir) + path3.sep;
1214
+ const candidatePath = path4.join(cliDir, entry.fileName);
1215
+ const resolvedCandidate = path4.resolve(candidatePath);
1216
+ const resolvedDir = path4.resolve(cliDir) + path4.sep;
1139
1217
  if (!resolvedCandidate.startsWith(resolvedDir)) {
1140
1218
  zipfile.close();
1141
1219
  reject(new Error(`Zip-slip detected: entry "${entry.fileName}" resolves outside ${cliDir}`));
1142
1220
  return;
1143
1221
  }
1144
- const isBinaryEntry = path3.basename(entry.fileName).startsWith("binary-");
1222
+ const isBinaryEntry = path4.basename(entry.fileName).startsWith("binary-");
1145
1223
  if (!isBinaryEntry) {
1146
- const directStream = fs3.createWriteStream(candidatePath);
1224
+ const directStream = fs4.createWriteStream(candidatePath);
1147
1225
  directStream.on("error", (writeErr) => {
1148
1226
  zipfile.close();
1149
1227
  reject(writeErr);
@@ -1165,8 +1243,8 @@ ${Date.now()}
1165
1243
  return;
1166
1244
  }
1167
1245
  const finalPath = candidatePath;
1168
- const tempPath = path3.join(cliDir, `${entry.fileName}.tmp.${process.pid}`);
1169
- const writeStream = fs3.createWriteStream(tempPath);
1246
+ const tempPath = path4.join(cliDir, `${entry.fileName}.tmp.${process.pid}`);
1247
+ const writeStream = fs4.createWriteStream(tempPath);
1170
1248
  let writeStreamErrored = false;
1171
1249
  writeStream.on("error", (writeErr) => {
1172
1250
  writeStreamErrored = true;
@@ -1347,11 +1425,11 @@ var PerformanceTester = class _PerformanceTester {
1347
1425
  static browser;
1348
1426
  static scenarioThatRan;
1349
1427
  static jsonReportDirName = "performance-report";
1350
- static jsonReportDirPath = path4.join(process.cwd(), "logs", this.jsonReportDirName);
1428
+ static jsonReportDirPath = path5.join(process.cwd(), "logs", this.jsonReportDirName);
1351
1429
  static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
1352
1430
  static startMonitoring(csvName = "performance-report.csv") {
1353
- if (!fs4.existsSync(this.jsonReportDirPath)) {
1354
- fs4.mkdirSync(this.jsonReportDirPath, { recursive: true });
1431
+ if (!fs5.existsSync(this.jsonReportDirPath)) {
1432
+ fs5.mkdirSync(this.jsonReportDirPath, { recursive: true });
1355
1433
  }
1356
1434
  this._observer = new PerformanceObserver((list) => {
1357
1435
  list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
@@ -1425,10 +1503,10 @@ var PerformanceTester = class _PerformanceTester {
1425
1503
  this.started = false;
1426
1504
  this.generateCSV(this._events);
1427
1505
  const content = this.generateReport(this._events);
1428
- const dir = path4.join(process.cwd(), filename);
1506
+ const dir = path5.join(process.cwd(), filename);
1429
1507
  try {
1430
1508
  await fsPromise.writeFile(dir, content);
1431
- BStackLogger.info(`Performance report is at ${path4}`);
1509
+ BStackLogger.info(`Performance report is at ${path5}`);
1432
1510
  } catch (err) {
1433
1511
  BStackLogger.error(`Error in writing html ${util2.format(err)}`);
1434
1512
  }
@@ -1599,7 +1677,7 @@ var PerformanceTester = class _PerformanceTester {
1599
1677
  this.start(EVENTS.SDK_KEY_METRICS_PREPARATION);
1600
1678
  let measures = [];
1601
1679
  if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
1602
- const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path4.resolve(this.jsonReportDirPath, file));
1680
+ const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path5.resolve(this.jsonReportDirPath, file));
1603
1681
  measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, "utf-8")))).map((el) => `[${el.slice(0, -1)}]`).map((el) => JSON.parse(el)).flat();
1604
1682
  }
1605
1683
  BStackLogger.debug(`[Performance Upload] Total events from files: ${measures.length}`);
@@ -1676,7 +1754,7 @@ var PerformanceTester = class _PerformanceTester {
1676
1754
  if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
1677
1755
  const files = await fsPromise.readdir(this.jsonReportDirPath);
1678
1756
  for (const file of files) {
1679
- await fsPromise.unlink(path4.join(this.jsonReportDirPath, file));
1757
+ await fsPromise.unlink(path5.join(this.jsonReportDirPath, file));
1680
1758
  }
1681
1759
  BStackLogger.debug(`[Performance Upload] Cleaned up ${files.length} temporary report files`);
1682
1760
  }
@@ -1732,7 +1810,7 @@ var getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
1732
1810
  };
1733
1811
 
1734
1812
  // src/testorchestration/testorcherstrationutils.ts
1735
- import fs5 from "node:fs";
1813
+ import fs6 from "node:fs";
1736
1814
  var RUN_SMART_SELECTION = "runSmartSelection";
1737
1815
  var ALLOWED_ORCHESTRATION_KEYS = [
1738
1816
  RUN_SMART_SELECTION
@@ -1962,13 +2040,13 @@ var OrchestrationUtils = class _OrchestrationUtils {
1962
2040
  * @returns Formatted list of repository configurations
1963
2041
  */
1964
2042
  _loadSourceFromFile(filePath) {
1965
- if (!fs5.existsSync(filePath)) {
2043
+ if (!fs6.existsSync(filePath)) {
1966
2044
  BStackLogger.error(`Source file '${filePath}' does not exist.`);
1967
2045
  return [];
1968
2046
  }
1969
2047
  let data = null;
1970
2048
  try {
1971
- const fileContent = fs5.readFileSync(filePath, "utf8");
2049
+ const fileContent = fs6.readFileSync(filePath, "utf8");
1972
2050
  data = JSON.parse(fileContent);
1973
2051
  } catch (error) {
1974
2052
  const message = error instanceof Error ? error.message : String(error);
@@ -2553,9 +2631,9 @@ var usageStats_default = UsageStats;
2553
2631
  import { create } from "tar";
2554
2632
 
2555
2633
  // src/scripts/accessibility-scripts.ts
2556
- import path5 from "node:path";
2557
- import fs6 from "node:fs";
2558
- import os from "node:os";
2634
+ import path6 from "node:path";
2635
+ import fs7 from "node:fs";
2636
+ import os2 from "node:os";
2559
2637
  var AccessibilityScripts = class _AccessibilityScripts {
2560
2638
  static instance = null;
2561
2639
  performScan = null;
@@ -2569,7 +2647,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
2569
2647
  // don't allow to create instances from it other than through `checkAndGetInstance`
2570
2648
  constructor() {
2571
2649
  this.browserstackFolderPath = this.getWritableDir();
2572
- this.commandsPath = path5.join(this.browserstackFolderPath, "commands.json");
2650
+ this.commandsPath = path6.join(this.browserstackFolderPath, "commands.json");
2573
2651
  }
2574
2652
  static checkAndGetInstance() {
2575
2653
  if (!_AccessibilityScripts.instance) {
@@ -2581,17 +2659,17 @@ var AccessibilityScripts = class _AccessibilityScripts {
2581
2659
  /* eslint-disable @typescript-eslint/no-unused-vars */
2582
2660
  getWritableDir() {
2583
2661
  const orderedPaths = [
2584
- path5.join(os.homedir(), ".browserstack"),
2662
+ path6.join(os2.homedir(), ".browserstack"),
2585
2663
  process.cwd(),
2586
- os.tmpdir()
2664
+ os2.tmpdir()
2587
2665
  ];
2588
2666
  for (const orderedPath of orderedPaths) {
2589
2667
  try {
2590
- if (fs6.existsSync(orderedPath)) {
2591
- fs6.accessSync(orderedPath);
2668
+ if (fs7.existsSync(orderedPath)) {
2669
+ fs7.accessSync(orderedPath);
2592
2670
  return orderedPath;
2593
2671
  }
2594
- fs6.mkdirSync(orderedPath, { recursive: true });
2672
+ fs7.mkdirSync(orderedPath, { recursive: true });
2595
2673
  return orderedPath;
2596
2674
  } catch (error) {
2597
2675
  }
@@ -2600,8 +2678,8 @@ var AccessibilityScripts = class _AccessibilityScripts {
2600
2678
  }
2601
2679
  readFromExistingFile() {
2602
2680
  try {
2603
- if (fs6.existsSync(this.commandsPath)) {
2604
- const data = fs6.readFileSync(this.commandsPath, "utf8");
2681
+ if (fs7.existsSync(this.commandsPath)) {
2682
+ const data = fs7.readFileSync(this.commandsPath, "utf8");
2605
2683
  if (data) {
2606
2684
  this.update(JSON.parse(data));
2607
2685
  }
@@ -2624,10 +2702,10 @@ var AccessibilityScripts = class _AccessibilityScripts {
2624
2702
  }
2625
2703
  }
2626
2704
  store() {
2627
- if (!fs6.existsSync(this.browserstackFolderPath)) {
2628
- fs6.mkdirSync(this.browserstackFolderPath);
2705
+ if (!fs7.existsSync(this.browserstackFolderPath)) {
2706
+ fs7.mkdirSync(this.browserstackFolderPath);
2629
2707
  }
2630
- fs6.writeFileSync(this.commandsPath, JSON.stringify({
2708
+ fs7.writeFileSync(this.commandsPath, JSON.stringify({
2631
2709
  commands: this.commandsToWrap,
2632
2710
  scripts: {
2633
2711
  scan: this.performScan,
@@ -3326,7 +3404,7 @@ function getObservabilityBuild(options, bstackBuildName) {
3326
3404
  if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
3327
3405
  return options.testObservabilityOptions.buildName;
3328
3406
  }
3329
- return bstackBuildName || path6.basename(path6.resolve(process.cwd()));
3407
+ return bstackBuildName || path7.basename(path7.resolve(process.cwd()));
3330
3408
  }
3331
3409
  function getObservabilityBuildTags(options, bstackBuildTag) {
3332
3410
  if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
@@ -3420,6 +3498,45 @@ var getPlatformVersion = o11yErrorHandler(function getPlatformVersion2(caps, use
3420
3498
  }
3421
3499
  return void 0;
3422
3500
  });
3501
+ var getResolvedDeviceName = o11yErrorHandler(function getResolvedDeviceName2(driverCaps, requestedCaps) {
3502
+ const flattenMultiremote = (caps) => {
3503
+ if (!caps) {
3504
+ return [];
3505
+ }
3506
+ const obj = caps;
3507
+ if (obj["deviceModel"] || obj["appium:deviceModel"] || obj["deviceName"] || obj["bstack:options"]) {
3508
+ return [caps];
3509
+ }
3510
+ return Object.values(obj).filter((v) => v !== null && typeof v === "object" && "capabilities" in v).map((v) => v.capabilities).filter(Boolean);
3511
+ };
3512
+ const sources = [
3513
+ ...flattenMultiremote(driverCaps),
3514
+ ...flattenMultiremote(requestedCaps)
3515
+ ];
3516
+ if (!sources.length) {
3517
+ return void 0;
3518
+ }
3519
+ const pickString = (obj, key) => {
3520
+ const v = obj?.[key];
3521
+ return typeof v === "string" && v.length > 0 ? v : void 0;
3522
+ };
3523
+ const paths = [
3524
+ (c) => pickString(c, "deviceModel"),
3525
+ (c) => pickString(c, "appium:deviceModel"),
3526
+ (c) => pickString(c["bstack:options"], "deviceName"),
3527
+ (c) => pickString(c, "appium:deviceName"),
3528
+ (c) => pickString(c, "deviceName")
3529
+ ];
3530
+ for (const path10 of paths) {
3531
+ for (const src of sources) {
3532
+ const v = path10(src);
3533
+ if (v) {
3534
+ return v;
3535
+ }
3536
+ }
3537
+ }
3538
+ return void 0;
3539
+ });
3423
3540
  var isObjectEmpty = (objectName) => {
3424
3541
  return objectName && Object.keys(objectName).length === 0 && objectName.constructor === Object;
3425
3542
  };
@@ -3581,28 +3698,28 @@ function nestedKeyValue(hash, keys) {
3581
3698
  return keys.reduce((hash2, key) => isHash(hash2) ? hash2[key] : void 0, hash);
3582
3699
  }
3583
3700
  function removeDir(dir) {
3584
- const list = fs7.readdirSync(dir);
3701
+ const list = fs8.readdirSync(dir);
3585
3702
  for (let i = 0; i < list.length; i++) {
3586
- const filename = path6.join(dir, list[i]);
3587
- const stat = fs7.statSync(filename);
3703
+ const filename = path7.join(dir, list[i]);
3704
+ const stat = fs8.statSync(filename);
3588
3705
  if (filename === "." || filename === "..") {
3589
3706
  } else if (stat.isDirectory()) {
3590
3707
  removeDir(filename);
3591
3708
  } else {
3592
- fs7.unlinkSync(filename);
3709
+ fs8.unlinkSync(filename);
3593
3710
  }
3594
3711
  }
3595
- fs7.rmdirSync(dir);
3712
+ fs8.rmdirSync(dir);
3596
3713
  }
3597
3714
  function createDir(dir) {
3598
- if (fs7.existsSync(dir)) {
3715
+ if (fs8.existsSync(dir)) {
3599
3716
  removeDir(dir);
3600
3717
  }
3601
- fs7.mkdirSync(dir, { recursive: true });
3718
+ fs8.mkdirSync(dir, { recursive: true });
3602
3719
  }
3603
3720
  function isWritable(dirPath) {
3604
3721
  try {
3605
- fs7.accessSync(dirPath, fs7.constants.W_OK);
3722
+ fs8.accessSync(dirPath, fs8.constants.W_OK);
3606
3723
  return true;
3607
3724
  } catch {
3608
3725
  return false;
@@ -3610,7 +3727,7 @@ function isWritable(dirPath) {
3610
3727
  }
3611
3728
  function setReadWriteAccess(dirPath) {
3612
3729
  try {
3613
- fs7.chmodSync(dirPath, 438);
3730
+ fs8.chmodSync(dirPath, 438);
3614
3731
  BStackLogger.debug(`Directory ${dirPath} is now read/write accessible.`);
3615
3732
  } catch (err) {
3616
3733
  BStackLogger.error(`Failed to set directory access: ${err.stack}`);
@@ -3618,19 +3735,19 @@ function setReadWriteAccess(dirPath) {
3618
3735
  }
3619
3736
 
3620
3737
  // src/cleanup.ts
3621
- import fs10 from "node:fs";
3738
+ import fs11 from "node:fs";
3622
3739
  import util5 from "node:util";
3623
3740
 
3624
3741
  // src/instrumentation/funnelInstrumentation.ts
3625
- import os2 from "node:os";
3742
+ import os3 from "node:os";
3626
3743
  import util4, { format as format2 } from "node:util";
3627
- import path8 from "node:path";
3628
- import fs9 from "node:fs";
3744
+ import path9 from "node:path";
3745
+ import fs10 from "node:fs";
3629
3746
 
3630
3747
  // src/data-store.ts
3631
- import path7 from "node:path";
3632
- import fs8 from "node:fs";
3633
- var workersDataDirPath = path7.join(process.cwd(), "logs", "worker_data");
3748
+ import path8 from "node:path";
3749
+ import fs9 from "node:fs";
3750
+ var workersDataDirPath = path8.join(process.cwd(), "logs", "worker_data");
3634
3751
 
3635
3752
  // src/instrumentation/funnelInstrumentation.ts
3636
3753
  function redactCredentialsFromFunnelData(data) {
@@ -3736,7 +3853,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
3736
3853
  if (!filePath) {
3737
3854
  return null;
3738
3855
  }
3739
- const content = fs10.readFileSync(filePath, "utf8");
3856
+ const content = fs11.readFileSync(filePath, "utf8");
3740
3857
  const data = JSON.parse(content);
3741
3858
  this.removeFunnelDataFile(filePath);
3742
3859
  return data;
@@ -3745,7 +3862,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
3745
3862
  if (!filePath) {
3746
3863
  return;
3747
3864
  }
3748
- fs10.rmSync(filePath, { force: true });
3865
+ fs11.rmSync(filePath, { force: true });
3749
3866
  }
3750
3867
  };
3751
3868
  void BStackCleanup.startCleanup();