@testrigor/testrigor-cli 1.0.0 → 2.0.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.
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const node_fetch_commonjs_1 = require("node-fetch-commonjs");
5
4
  const command_base_1 = require("../../command-base");
6
5
  const environments_1 = require("../../config/environments");
7
6
  const parser_1 = require("../../util/parser");
@@ -67,7 +66,7 @@ class ValidateSyntax extends command_base_1.CommandBase {
67
66
  if (explicitMutations) {
68
67
  body.explicitMutations = explicitMutations;
69
68
  }
70
- const response = await (0, node_fetch_commonjs_1.default)(`${this.TESTRIGOR_API2_URL}/apps/${testSuiteId}/validate-syntax`, {
69
+ const response = await fetch(`${this.TESTRIGOR_API2_URL}/apps/${testSuiteId}/validate-syntax`, {
71
70
  method: "POST",
72
71
  body: JSON.stringify(body),
73
72
  headers: {
@@ -82,7 +81,6 @@ class ValidateSyntax extends command_base_1.CommandBase {
82
81
  return (await response.json());
83
82
  }
84
83
  }
85
- exports.default = ValidateSyntax;
86
84
  ValidateSyntax.description = "Validate step syntax against a test suite's reusable rules, without running the steps";
87
85
  ValidateSyntax.examples = [
88
86
  'testrigor test-suite validate-syntax <test-suite-id> --steps "click \\"Login\\""',
@@ -115,3 +113,4 @@ ValidateSyntax.args = {
115
113
  required: false,
116
114
  }),
117
115
  };
116
+ exports.default = ValidateSyntax;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChromeExtensionFlag = void 0;
4
+ const tslib_1 = require("tslib");
4
5
  const core_1 = require("@oclif/core");
5
- const fs = require("fs");
6
- const path = require("path");
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const path = tslib_1.__importStar(require("path"));
7
8
  const utils_1 = require("../util/utils");
8
9
  let totalChromeExtensionSize = 0;
9
10
  exports.ChromeExtensionFlag = core_1.Flags.custom({
package/dist/types.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- import { ReadStream } from "fs";
3
1
  export type RetestResponse = {
4
2
  result: string;
5
3
  msg: string;
@@ -62,16 +60,6 @@ export type ChromeExtensionInput = {
62
60
  name: string;
63
61
  file: string;
64
62
  };
65
- /**
66
- * Represents a single chrome extension override with its binary file.
67
- */
68
- export type ChromeExtensionOverrideItem = {
69
- name: string;
70
- file: ReadStream;
71
- };
72
- export type ChromeExtensionOverridesRequest = {
73
- chromeExtensions: ChromeExtensionOverrideItem[];
74
- };
75
63
  export type StoredValueFileInput = {
76
64
  name: string;
77
65
  file: string;
@@ -1,27 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stopAppium = exports.startAppium = exports.getRunningAppiumPid = exports.isProcessRunning = exports.ensureDriver = exports.getDriverStatus = exports.checkAppiumInstalled = exports.PID_FILE = exports.PID_DIR = exports.REQUIRED_DRIVERS = exports.DEFAULT_BASE_PATH = exports.DEFAULT_PORT = void 0;
4
- const childProcess = require("child_process");
5
- const fs = require("fs");
6
- const util = require("util");
7
- const path = require("path");
8
- const os = require("os");
9
- const Fs = require("@supercharge/fs");
3
+ exports.PID_FILE = exports.PID_DIR = exports.REQUIRED_DRIVERS = exports.DEFAULT_BASE_PATH = exports.DEFAULT_PORT = void 0;
4
+ exports.checkAppiumInstalled = checkAppiumInstalled;
5
+ exports.getDriverStatus = getDriverStatus;
6
+ exports.ensureDriver = ensureDriver;
7
+ exports.isProcessRunning = isProcessRunning;
8
+ exports.getRunningAppiumPid = getRunningAppiumPid;
9
+ exports.startAppium = startAppium;
10
+ exports.stopAppium = stopAppium;
11
+ const tslib_1 = require("tslib");
12
+ const childProcess = tslib_1.__importStar(require("child_process"));
13
+ const fs = tslib_1.__importStar(require("fs"));
14
+ const util = tslib_1.__importStar(require("util"));
15
+ const path = tslib_1.__importStar(require("path"));
16
+ const os = tslib_1.__importStar(require("os"));
17
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
18
+ const utils_1 = require("./utils");
10
19
  const execFile = util.promisify(childProcess.execFile);
11
20
  const PROJECT_ROOT = path.resolve(__dirname, "..", "..");
12
- const APPIUM_HOME = path.join(PROJECT_ROOT, ".appium");
13
21
  const DEFAULT_PORT = 4723;
14
22
  exports.DEFAULT_PORT = DEFAULT_PORT;
15
23
  const DEFAULT_BASE_PATH = "/wd/hub";
16
24
  exports.DEFAULT_BASE_PATH = DEFAULT_BASE_PATH;
17
- const PID_DIR = path.join(os.homedir(), ".testrigor");
25
+ const PID_DIR = (0, utils_1.getHomeDir)();
18
26
  exports.PID_DIR = PID_DIR;
19
27
  const PID_FILE = path.join(PID_DIR, "appium.pid");
20
28
  exports.PID_FILE = PID_FILE;
21
29
  const LOG_FILE = path.join(os.tmpdir(), "testrigor-appium.log");
30
+ // Appium writes its drivers here (hundreds of MB). It must live under the
31
+ // user's home, not the install directory: a global install is often
32
+ // root-owned, and `npm update -g` would wipe the drivers on every upgrade.
33
+ const APPIUM_HOME = process.env.APPIUM_HOME || path.join((0, utils_1.getHomeDir)(), "appium-home");
22
34
  const REQUIRED_DRIVERS = [
23
35
  { name: "uiautomator2", version: "4.2.3" },
24
- { name: "xcuitest", version: "9.2.4" },
36
+ { name: "xcuitest", version: "10.43.1" },
25
37
  ];
26
38
  exports.REQUIRED_DRIVERS = REQUIRED_DRIVERS;
27
39
  function getAppiumBin() {
@@ -34,19 +46,36 @@ function getAppiumEnv() {
34
46
  CHROMELABS_URL: "https://googlechromelabs.github.io",
35
47
  };
36
48
  }
49
+ /**
50
+ * Appium refuses to start unless APPIUM_HOME already exists, so create it
51
+ * before any invocation and surface a permission problem as itself rather
52
+ * than as a generic "appium is broken".
53
+ */
54
+ async function ensureAppiumHome() {
55
+ try {
56
+ await fs_1.default.ensureDir(APPIUM_HOME);
57
+ }
58
+ catch (error) {
59
+ throw new Error(`Cannot create the mobile server directory at ${APPIUM_HOME}: ` +
60
+ `${error instanceof Error ? error.message : String(error)}\n` +
61
+ "Check that you own that directory, or point APPIUM_HOME at a writable one.");
62
+ }
63
+ }
37
64
  async function checkAppiumInstalled() {
38
65
  const appiumBin = getAppiumBin();
39
- if (!(await Fs.exists(appiumBin))) {
40
- throw new Error(`Appium binary not found at ${appiumBin}. Try reinstalling testrigor-cli.`);
66
+ if (!(await fs_1.default.exists(appiumBin))) {
67
+ throw new Error(`Appium binary not found at ${appiumBin}. ` +
68
+ "Reinstall the CLI with: npm install -g @testrigor/testrigor-cli");
41
69
  }
70
+ await ensureAppiumHome();
42
71
  try {
43
72
  await execFile(appiumBin, ["--version"], { env: getAppiumEnv() });
44
73
  }
45
- catch {
46
- throw new Error(`Appium binary at ${appiumBin} is not working. Try reinstalling testrigor-cli.`);
74
+ catch (error) {
75
+ const detail = error instanceof Error ? error.message : String(error);
76
+ throw new Error(`Appium binary at ${appiumBin} failed to run (APPIUM_HOME=${APPIUM_HOME}).\n${detail}`);
47
77
  }
48
78
  }
49
- exports.checkAppiumInstalled = checkAppiumInstalled;
50
79
  async function getDriverStatus(driverName, requiredVersion) {
51
80
  const appiumBin = getAppiumBin();
52
81
  try {
@@ -66,7 +95,6 @@ async function getDriverStatus(driverName, requiredVersion) {
66
95
  return { installed: false, upToDate: false };
67
96
  }
68
97
  }
69
- exports.getDriverStatus = getDriverStatus;
70
98
  async function ensureDriver(driverName, requiredVersion) {
71
99
  const appiumBin = getAppiumBin();
72
100
  const env = getAppiumEnv();
@@ -86,10 +114,9 @@ async function ensureDriver(driverName, requiredVersion) {
86
114
  }
87
115
  catch (error) {
88
116
  const raw = error instanceof Error ? error.message : String(error);
89
- return raw.split("\n")[0].replace(/^Command failed:.*?Error:\s*/, "").trim();
117
+ return raw.replace(/^Command failed:.*?Error:\s*/, "").trim();
90
118
  }
91
119
  }
92
- exports.ensureDriver = ensureDriver;
93
120
  function isProcessRunning(pid) {
94
121
  try {
95
122
  process.kill(pid, 0);
@@ -99,25 +126,52 @@ function isProcessRunning(pid) {
99
126
  return false;
100
127
  }
101
128
  }
102
- exports.isProcessRunning = isProcessRunning;
103
129
  function killSafe(pid) {
104
130
  if (isProcessRunning(pid)) {
105
131
  process.kill(pid);
106
132
  }
107
133
  }
108
134
  async function getRunningAppiumPid() {
109
- if (!(await Fs.exists(PID_FILE))) {
135
+ if (!(await fs_1.default.exists(PID_FILE))) {
110
136
  return null;
111
137
  }
112
- const existingPid = Number((await Fs.content(PID_FILE)).trim());
138
+ const existingPid = Number((await fs_1.default.content(PID_FILE)).trim());
113
139
  if (isProcessRunning(existingPid)) {
114
140
  return existingPid;
115
141
  }
116
- await Fs.removeFile(PID_FILE);
142
+ await fs_1.default.removeFile(PID_FILE);
117
143
  return null;
118
144
  }
119
- exports.getRunningAppiumPid = getRunningAppiumPid;
120
- async function healthCheck(port, retries = 15, interval = 2000) {
145
+ /**
146
+ * Last lines of the Appium log, so a server that dies on startup reports the
147
+ * reason instead of just a health-check timeout. The log is shared across
148
+ * invocations, so only bytes written after `fromOffset` are considered.
149
+ */
150
+ function readLogTail(fromOffset, lines = 20) {
151
+ let fd;
152
+ try {
153
+ fd = fs.openSync(LOG_FILE, "r");
154
+ const size = fs.fstatSync(fd).size;
155
+ if (size <= fromOffset)
156
+ return "";
157
+ const maxTailBytes = 64 * 1024;
158
+ const start = Math.max(fromOffset, size - maxTailBytes);
159
+ const buffer = Buffer.alloc(size - start);
160
+ fs.readSync(fd, buffer, 0, buffer.length, start);
161
+ const content = buffer.toString("utf8").trimEnd();
162
+ if (!content)
163
+ return "";
164
+ return content.split("\n").slice(-lines).join("\n");
165
+ }
166
+ catch {
167
+ return "";
168
+ }
169
+ finally {
170
+ if (fd !== undefined)
171
+ fs.closeSync(fd);
172
+ }
173
+ }
174
+ async function healthCheck(port, logOffset, retries = 15, interval = 2000) {
121
175
  const url = `http://127.0.0.1:${port}/wd/hub/status`;
122
176
  let lastError = "";
123
177
  for (let i = 0; i < retries; i++) {
@@ -133,7 +187,11 @@ async function healthCheck(port, retries = 15, interval = 2000) {
133
187
  }
134
188
  await new Promise((r) => setTimeout(r, interval));
135
189
  }
136
- throw new Error(`Mobile server not ready after ${retries} attempts: ${lastError}`);
190
+ const tail = readLogTail(logOffset);
191
+ throw new Error(`Mobile server not ready after ${retries} attempts: ${lastError}` +
192
+ (tail
193
+ ? `\nLast lines of ${LOG_FILE}:\n${tail}`
194
+ : `\nSee ${LOG_FILE} for details.`));
137
195
  }
138
196
  async function startAppium(port = DEFAULT_PORT, logLevel = "debug") {
139
197
  const existingPid = await getRunningAppiumPid();
@@ -141,7 +199,9 @@ async function startAppium(port = DEFAULT_PORT, logLevel = "debug") {
141
199
  return { pid: existingPid, alreadyRunning: true };
142
200
  }
143
201
  const appiumBin = getAppiumBin();
202
+ await ensureAppiumHome();
144
203
  const logStream = fs.openSync(LOG_FILE, "a");
204
+ const logOffset = fs.fstatSync(logStream).size;
145
205
  const appiumProcess = childProcess.spawn(appiumBin, [
146
206
  "--port",
147
207
  String(port),
@@ -161,29 +221,27 @@ async function startAppium(port = DEFAULT_PORT, logLevel = "debug") {
161
221
  throw new Error("Failed to start Appium server: no PID returned");
162
222
  }
163
223
  appiumProcess.unref();
164
- await Fs.ensureDir(PID_DIR);
165
- await Fs.writeFile(PID_FILE, String(pid));
224
+ await fs_1.default.ensureDir(PID_DIR);
225
+ await fs_1.default.writeFile(PID_FILE, String(pid));
166
226
  try {
167
- await healthCheck(port);
227
+ await healthCheck(port, logOffset);
168
228
  }
169
229
  catch (err) {
170
230
  killSafe(pid);
171
- await Fs.removeFile(PID_FILE);
231
+ await fs_1.default.removeFile(PID_FILE);
172
232
  throw err;
173
233
  }
174
234
  return { pid, alreadyRunning: false };
175
235
  }
176
- exports.startAppium = startAppium;
177
236
  async function stopAppium() {
178
- if (!(await Fs.exists(PID_FILE))) {
237
+ if (!(await fs_1.default.exists(PID_FILE))) {
179
238
  return null;
180
239
  }
181
- const pidContent = (await Fs.content(PID_FILE)).trim();
240
+ const pidContent = (await fs_1.default.content(PID_FILE)).trim();
182
241
  const pid = Number(pidContent);
183
242
  if (!isNaN(pid) && isProcessRunning(pid)) {
184
243
  process.kill(pid, "SIGTERM");
185
244
  }
186
- await Fs.removeFile(PID_FILE);
245
+ await fs_1.default.removeFile(PID_FILE);
187
246
  return pid;
188
247
  }
189
- exports.stopAppium = stopAppium;
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadClientCertificate = void 0;
4
- const fs = require("fs");
5
- const path = require("path");
6
- const node_fetch_commonjs_1 = require("node-fetch-commonjs");
3
+ exports.uploadClientCertificate = uploadClientCertificate;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
7
  const type_guards_1 = require("./type-guards");
8
8
  async function uploadClientCertificate(api2BaseUrl, testSuiteId, token, localFilePath, password) {
9
9
  const fileName = path.basename(localFilePath);
10
10
  const certBase64 = fs.readFileSync(localFilePath).toString("base64");
11
- const response = await (0, node_fetch_commonjs_1.default)(`${api2BaseUrl}/apps/${testSuiteId}/test_data/global_variables/client_certificate`, {
11
+ const response = await fetch(`${api2BaseUrl}/apps/${testSuiteId}/test_data/global_variables/client_certificate`, {
12
12
  method: "POST",
13
13
  body: JSON.stringify({ fileName, certBase64, password }),
14
14
  headers: {
@@ -29,4 +29,3 @@ async function uploadClientCertificate(api2BaseUrl, testSuiteId, token, localFil
29
29
  info: responseJson.data.info,
30
30
  };
31
31
  }
32
- exports.uploadClientCertificate = uploadClientCertificate;
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deleteConfigProperty = exports.updateConfigProperty = exports.readConfigProperty = exports.writeConfig = exports.readConfig = exports.getConfigPath = void 0;
3
+ exports.getConfigPath = getConfigPath;
4
+ exports.readConfig = readConfig;
5
+ exports.writeConfig = writeConfig;
6
+ exports.readConfigProperty = readConfigProperty;
7
+ exports.updateConfigProperty = updateConfigProperty;
8
+ exports.deleteConfigProperty = deleteConfigProperty;
9
+ const tslib_1 = require("tslib");
4
10
  const core_1 = require("@oclif/core");
5
- const Fs = require("@supercharge/fs");
6
- const path = require("path");
11
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
12
+ const path = tslib_1.__importStar(require("path"));
7
13
  const yaml_1 = require("yaml");
8
14
  const utils_1 = require("./utils");
9
15
  /**
@@ -14,17 +20,16 @@ function getConfigPath() {
14
20
  const configPath = path.join(homeDir, "testrigor.yml");
15
21
  return { homeDir, configPath };
16
22
  }
17
- exports.getConfigPath = getConfigPath;
18
23
  /**
19
24
  * Read testRigor configuration from ~/.testrigor/testrigor.yml
20
25
  */
21
26
  async function readConfig() {
22
27
  const { configPath } = getConfigPath();
23
- if (!(await Fs.pathExists(configPath))) {
28
+ if (!(await fs_1.default.pathExists(configPath))) {
24
29
  return {};
25
30
  }
26
31
  try {
27
- const content = await Fs.content(configPath);
32
+ const content = await fs_1.default.content(configPath);
28
33
  return (0, yaml_1.parse)(content.toString()) || {};
29
34
  }
30
35
  catch (error) {
@@ -34,26 +39,24 @@ async function readConfig() {
34
39
  return {};
35
40
  }
36
41
  }
37
- exports.readConfig = readConfig;
38
42
  /**
39
43
  * Write testRigor configuration to ~/.testrigor/testrigor.yml
40
44
  */
41
45
  async function writeConfig(config) {
42
46
  const { homeDir, configPath } = getConfigPath();
43
47
  // Ensure the config directory exists
44
- if (!(await Fs.pathExists(homeDir))) {
45
- await Fs.ensureDir(homeDir);
48
+ if (!(await fs_1.default.pathExists(homeDir))) {
49
+ await fs_1.default.ensureDir(homeDir);
46
50
  }
47
51
  // Avoid writing empty objects as "{}"
48
52
  const configContent = Object.keys(config).length > 0 ? (0, yaml_1.stringify)(config) : "";
49
53
  if (configContent.trim() === "") {
50
- await Fs.writeFile(configPath, "");
54
+ await fs_1.default.writeFile(configPath, "");
51
55
  }
52
56
  else {
53
- await Fs.writeFile(configPath, configContent);
57
+ await fs_1.default.writeFile(configPath, configContent);
54
58
  }
55
59
  }
56
- exports.writeConfig = writeConfig;
57
60
  /**
58
61
  * Read a specific property from testRigor configuration
59
62
  */
@@ -61,7 +64,6 @@ async function readConfigProperty(property) {
61
64
  const config = await readConfig();
62
65
  return config[property] || null;
63
66
  }
64
- exports.readConfigProperty = readConfigProperty;
65
67
  /**
66
68
  * Update a specific property in testRigor configuration
67
69
  */
@@ -75,7 +77,6 @@ async function updateConfigProperty(property, value) {
75
77
  }
76
78
  await writeConfig(config);
77
79
  }
78
- exports.updateConfigProperty = updateConfigProperty;
79
80
  /**
80
81
  * Delete a specific property from testRigor configuration
81
82
  */
@@ -84,4 +85,3 @@ async function deleteConfigProperty(property) {
84
85
  delete config[property];
85
86
  await writeConfig(config);
86
87
  }
87
- exports.deleteConfigProperty = deleteConfigProperty;
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSettings = exports.parseRules = exports.parseTestCases = void 0;
4
- const Fs = require("@supercharge/fs");
3
+ exports.parseTestCases = parseTestCases;
4
+ exports.parseRules = parseRules;
5
+ exports.parseSettings = parseSettings;
6
+ const tslib_1 = require("tslib");
7
+ const fs_1 = tslib_1.__importDefault(require("@supercharge/fs"));
5
8
  const glob_1 = require("glob");
6
9
  const yaml_1 = require("yaml");
7
10
  const utils_1 = require("./utils");
@@ -10,7 +13,7 @@ async function parseTestCases(path) {
10
13
  const files = await (0, glob_1.glob)(path, { withFileTypes: true });
11
14
  for (const file of files) {
12
15
  if (file.isFile()) {
13
- const content = await Fs.content(file.fullpath());
16
+ const content = await fs_1.default.content(file.fullpath());
14
17
  if ((0, utils_1.isYamlFile)(file.name)) {
15
18
  const json = (0, yaml_1.parse)(content.toString());
16
19
  const testCase = {
@@ -39,13 +42,12 @@ async function parseTestCases(path) {
39
42
  }
40
43
  return result;
41
44
  }
42
- exports.parseTestCases = parseTestCases;
43
45
  async function parseRules(path) {
44
46
  const result = [];
45
47
  const files = await (0, glob_1.glob)(path, { withFileTypes: true });
46
48
  for (const file of files) {
47
49
  if (file.isFile()) {
48
- const content = await Fs.content(file.fullpath());
50
+ const content = await fs_1.default.content(file.fullpath());
49
51
  if ((0, utils_1.isYamlFile)(file.name)) {
50
52
  const json = (0, yaml_1.parse)(content.toString());
51
53
  const ruleName = (0, utils_1.removeExtension)(file.name);
@@ -69,9 +71,8 @@ async function parseRules(path) {
69
71
  }
70
72
  return result;
71
73
  }
72
- exports.parseRules = parseRules;
73
74
  async function parseSettings(path) {
74
- const content = await Fs.content(path);
75
+ const content = await fs_1.default.content(path);
75
76
  if ((0, utils_1.isYamlFile)(path)) {
76
77
  return (0, yaml_1.parse)(content.toString());
77
78
  }
@@ -80,4 +81,3 @@ async function parseSettings(path) {
80
81
  }
81
82
  return {};
82
83
  }
83
- exports.parseSettings = parseSettings;
@@ -1,11 +1,5 @@
1
- import * as FormData from "form-data";
2
1
  import { ChromeExtensionInput } from "../types";
3
- export interface ChromeExtensionFormDataResult {
4
- form: FormData;
5
- cleanup: () => void;
6
- }
7
2
  /**
8
- * Converts ChromeExtensionInput[] -> ChromeExtensionOverridesRequest -> FormData
9
- * Returns both the FormData and a cleanup function to close streams.
3
+ * Builds the multipart body for the chrome extension overrides of a retest.
10
4
  */
11
- export declare function buildChromeExtensionFormData(chromeExtensions: ChromeExtensionInput[]): ChromeExtensionFormDataResult;
5
+ export declare function buildChromeExtensionFormData(chromeExtensions: ChromeExtensionInput[]): Promise<FormData>;
@@ -1,49 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildChromeExtensionFormData = void 0;
4
- const FormData = require("form-data");
5
- const Fs = require("@supercharge/fs");
6
- const path = require("path");
7
- function toChromeExtensionOverrideItem(input) {
3
+ exports.buildChromeExtensionFormData = buildChromeExtensionFormData;
4
+ const tslib_1 = require("tslib");
5
+ const fs_1 = require("fs");
6
+ const path = tslib_1.__importStar(require("path"));
7
+ async function readChromeExtension(input) {
8
8
  try {
9
- return {
10
- name: input.name,
11
- file: Fs.createReadStream(input.file),
12
- };
9
+ return await (0, fs_1.openAsBlob)(input.file);
13
10
  }
14
11
  catch (error) {
15
12
  throw new Error(`Failed to read chrome extension file "${input.file}": ${error instanceof Error ? error.message : String(error)}`);
16
13
  }
17
14
  }
18
- function toChromeExtensionOverridesRequest(extensionInputs) {
19
- const chromeExtensions = extensionInputs.map(toChromeExtensionOverrideItem);
20
- const streams = chromeExtensions.map((item) => item.file);
21
- return {
22
- request: { chromeExtensions },
23
- streams,
24
- };
25
- }
26
15
  /**
27
- * Converts ChromeExtensionInput[] -> ChromeExtensionOverridesRequest -> FormData
28
- * Returns both the FormData and a cleanup function to close streams.
16
+ * Builds the multipart body for the chrome extension overrides of a retest.
29
17
  */
30
- function buildChromeExtensionFormData(chromeExtensions) {
31
- const { request, streams } = toChromeExtensionOverridesRequest(chromeExtensions);
18
+ async function buildChromeExtensionFormData(chromeExtensions) {
19
+ const files = await Promise.all(chromeExtensions.map(readChromeExtension));
32
20
  const form = new FormData();
33
- request.chromeExtensions.forEach((ext, index) => {
21
+ chromeExtensions.forEach((ext, index) => {
34
22
  form.append(`chromeExtensions[${index}].name`, ext.name);
35
- const fileName = path.basename(chromeExtensions[index].file);
36
- form.append(`chromeExtensions[${index}].file`, ext.file, {
37
- filename: fileName,
38
- });
23
+ form.append(`chromeExtensions[${index}].file`, files[index], path.basename(ext.file));
39
24
  });
40
- const cleanup = () => {
41
- streams.forEach((stream) => {
42
- if (!stream.destroyed) {
43
- stream.destroy();
44
- }
45
- });
46
- };
47
- return { form, cleanup };
25
+ return form;
48
26
  }
49
- exports.buildChromeExtensionFormData = buildChromeExtensionFormData;
@@ -1 +1,3 @@
1
+ export declare function extractZip(dir: string, stream: NodeJS.ReadableStream): Promise<string>;
2
+ export declare function extractTar(dir: string, stream: NodeJS.ReadableStream): Promise<string>;
1
3
  export declare function download(dir: string): Promise<string>;