@deeeed/metamask-harness 0.26.4 → 0.27.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.
@@ -131,7 +131,31 @@ async function handleDoctor({ options }) {
131
131
  }
132
132
  return status === "pass" ? 0 : 1;
133
133
  }
134
- const result = createDoctorReport(adapter, target, manifestValidation, actionManifestPath);
134
+ const mobilePlatform = adapter === "mobile" && (platformOption === "ios" || platformOption === "android") ? platformOption : void 0;
135
+ const result = createDoctorReport(
136
+ adapter,
137
+ target,
138
+ manifestValidation,
139
+ actionManifestPath,
140
+ mobilePlatform
141
+ );
142
+ const missingRequiredTool = result.checks.find(
143
+ (check) => check.required && check.status === "fail" && check.id.startsWith("device-tool-") && check.userAction
144
+ );
145
+ if (missingRequiredTool) {
146
+ const error = {
147
+ code: "DOCTOR_CHECKS_FAILED",
148
+ message: missingRequiredTool.message,
149
+ userAction: missingRequiredTool.userAction
150
+ };
151
+ if (json) {
152
+ console.log(JSON.stringify({ ...result, ready: false, error }, null, 2));
153
+ } else {
154
+ console.error(`\u2717 doctor: ${error.message}
155
+ Next: ${error.userAction}`);
156
+ }
157
+ return EXIT.runtime;
158
+ }
135
159
  let runtime;
136
160
  let runtimeProbeError;
137
161
  try {
@@ -178,7 +202,9 @@ async function handleDoctor({ options }) {
178
202
  printReady
179
203
  );
180
204
  }
181
- const doctorUserAction = `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
205
+ const doctorUserAction = doctorResult.checks.find(
206
+ (check) => check.required && check.status === "fail" && check.userAction
207
+ )?.userAction ?? `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`;
182
208
  const doctorError = doctorResult.status === "fail" ? { code: "DOCTOR_CHECKS_FAILED", message: "one or more required doctor checks failed", userAction: doctorUserAction } : void 0;
183
209
  const next = doctorError?.userAction ?? (runtime?.decision === "ready" ? void 0 : runtime?.nextAction);
184
210
  if (json) console.log(JSON.stringify({ ...doctorResult, ready: runtime?.decision === "ready", runtime, orphanMetros, capture, devices, additionalReachableDevices, ...next ? { next } : {}, ...doctorError ? { error: doctorError } : {} }, null, 2));
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { resolveMobileToolPath } from "../../library/actions/mobile/platform/tool-paths.mjs";
2
3
  import { listConnectedDevices } from "../devices.js";
3
4
  import { renderDeviceList, scopedDevices } from "./device-target.js";
4
5
  import { probeMobileLiveState } from "./status-probe.js";
@@ -116,8 +117,10 @@ function selectedAndroidPortHints(devices, liveMap) {
116
117
  }).filter((hint) => hint.reversePorts.length > 0);
117
118
  }
118
119
  function androidReversePorts(serial) {
120
+ const adbPath = resolveMobileToolPath("adb");
121
+ if (!adbPath) return [];
119
122
  try {
120
- const out = execFileSync("adb", ["-s", serial, "reverse", "--list"], {
123
+ const out = execFileSync(adbPath, ["-s", serial, "reverse", "--list"], {
121
124
  encoding: "utf8",
122
125
  stdio: ["ignore", "pipe", "ignore"],
123
126
  timeout: 5e3
package/dist/devices.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
2
3
  function listConnectedDevices(platform) {
3
4
  const devices = [];
4
5
  if (!platform || platform === "android") devices.push(...listAndroidDevices());
@@ -6,9 +7,11 @@ function listConnectedDevices(platform) {
6
7
  return devices;
7
8
  }
8
9
  function listAndroidDevices() {
10
+ const adbPath = resolveMobileToolPath("adb");
11
+ if (!adbPath) return [];
9
12
  let output;
10
13
  try {
11
- output = execFileSync("adb", ["devices", "-l"], {
14
+ output = execFileSync(adbPath, ["devices", "-l"], {
12
15
  encoding: "utf8",
13
16
  timeout: 5e3,
14
17
  stdio: ["ignore", "pipe", "ignore"]
package/dist/doctor.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { color } from "./cli-color.js";
4
+ import {
5
+ mobileToolRecovery,
6
+ resolveMobileToolPath
7
+ } from "../library/actions/mobile/platform/tool-paths.mjs";
4
8
  import { mobilePerpsEnvironment } from "./adapters/mobile/perps-env.js";
5
9
  import { readRuntimeContextField, resolveRuntimeContextPath } from "./harness.js";
6
10
  import { manifestPath, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "./paths.js";
@@ -128,7 +132,7 @@ function renderRuntimeContext(runtimeContext) {
128
132
  }
129
133
  return lines.join("\n");
130
134
  }
131
- function createDoctorReport(adapter, target, manifestValidation, actionManifestPath = manifestPath(adapter)) {
135
+ function createDoctorReport(adapter, target, manifestValidation, actionManifestPath = manifestPath(adapter), mobilePlatform) {
132
136
  const mode = compatibilityMode(adapter, target);
133
137
  const manifestErrors = Number(manifestValidation.summary?.errors ?? 0);
134
138
  const checks = [
@@ -143,7 +147,8 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
143
147
  status: mode === "unsupported/no bridge" ? "fail" : "pass",
144
148
  required: false,
145
149
  message: mode === "unsupported/no bridge" ? `No ${adapter} bridge is available for this checkout.` : `${adapter} compatibility mode: ${mode}.`
146
- }
150
+ },
151
+ ...adapter === "mobile" ? mobileToolDoctorChecks(mobilePlatform) : []
147
152
  ];
148
153
  const requiredChecks = requiredDoctorCheckSummary(checks);
149
154
  return {
@@ -164,6 +169,23 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
164
169
  manifestValidation: manifestValidation.summary
165
170
  };
166
171
  }
172
+ function mobileToolDoctorChecks(platform) {
173
+ const specs = platform === "android" ? [{ tool: "adb", platform: "Android" }] : platform === "ios" ? [{ tool: "idb", platform: "iOS" }] : [
174
+ { tool: "adb", platform: "Android" },
175
+ { tool: "idb", platform: "iOS" }
176
+ ];
177
+ return specs.map(({ tool, platform: platformName }) => {
178
+ const resolved = resolveMobileToolPath(tool);
179
+ return {
180
+ id: `device-tool-${tool}`,
181
+ status: resolved ? "pass" : "fail",
182
+ required: platform !== void 0,
183
+ message: resolved ? `${platformName} device tool ${tool} is ready.` : `${platformName} device tool ${tool} is unavailable.`,
184
+ ...resolved ? { detail: resolved } : {},
185
+ ...!resolved ? { userAction: mobileToolRecovery(tool) } : {}
186
+ };
187
+ });
188
+ }
167
189
  function runnerInstallKind(runnerRoot, invokedPath, executablePath) {
168
190
  const normalizedRoot = path.normalize(runnerRoot);
169
191
  const nodeModulesSegment = `${path.sep}node_modules${path.sep}`;
@@ -230,6 +252,7 @@ export {
230
252
  createDoctorReport,
231
253
  fixtureFileSummary,
232
254
  fixtureSummary,
255
+ mobileToolDoctorChecks,
233
256
  renderRuntimeContext,
234
257
  repoShape,
235
258
  requiredDoctorCheckSummary,
@@ -6,6 +6,7 @@ import {
6
6
  resolveExtensionBrowserPid
7
7
  } from "./recording-target.js";
8
8
  const activeRecordingsByPid = /* @__PURE__ */ new Map();
9
+ const ACTIVE_EXTENSION_RECORDING_PID = "METAMASK_RECIPE_EXTENSION_ACTIVE_RECORDING_PID";
9
10
  async function startRecipeRecording(adapter, projectRoot, artifactsDir, options) {
10
11
  if (!options.record) return void 0;
11
12
  if (process.platform !== "darwin") {
@@ -31,17 +32,27 @@ async function startRecipeRecording(adapter, projectRoot, artifactsDir, options)
31
32
  }
32
33
  const recordArgs = ["record", "--framed", "--pid", String(pid)];
33
34
  const relativePath = "videos/full-run.mp4";
34
- const stage = preparePrivateRecordingStage(artifactsDir, relativePath);
35
- const child = spawn(captureHelperPath(), [...recordArgs, "--output", stage.stagedOutputPath], {
36
- cwd: projectRoot,
37
- env: process.env,
38
- stdio: ["pipe", "pipe", "pipe"]
39
- });
35
+ const outputPath = path.join(artifactsDir, relativePath);
36
+ const { stagingDir, stagedPath } = preparePrivateRecordingDestination(
37
+ artifactsDir,
38
+ outputPath
39
+ );
40
+ let child;
41
+ try {
42
+ child = spawn(captureHelperPath(), [...recordArgs, "--output", stagedPath], {
43
+ cwd: projectRoot,
44
+ env: process.env,
45
+ stdio: ["pipe", "pipe", "pipe"]
46
+ });
47
+ } catch (error) {
48
+ cleanupRecordingStaging(stagingDir);
49
+ throw error;
50
+ }
40
51
  const recording = {
41
52
  child,
42
- outputPath: stage.outputPath,
43
- stagedOutputPath: stage.stagedOutputPath,
44
- stagingDir: stage.stagingDir,
53
+ outputPath,
54
+ stagingDir,
55
+ stagedPath,
45
56
  relativePath,
46
57
  pid,
47
58
  stdout: "",
@@ -50,6 +61,7 @@ async function startRecipeRecording(adapter, projectRoot, artifactsDir, options)
50
61
  exitCode: null,
51
62
  frameReady: false,
52
63
  finalized: false,
64
+ previousActiveRecordingPid: process.env[ACTIVE_EXTENSION_RECORDING_PID],
53
65
  stderrBuffer: "",
54
66
  pendingSnapshots: /* @__PURE__ */ new Map()
55
67
  };
@@ -69,11 +81,12 @@ async function startRecipeRecording(adapter, projectRoot, artifactsDir, options)
69
81
  recording.exited = true;
70
82
  recording.exitCode = exitCode;
71
83
  activeRecordingsByPid.delete(recording.pid);
84
+ restoreActiveRecordingEnvironment(recording);
72
85
  rejectPendingSnapshots(recording, new Error(`capture-helper recording exited before snapshot completed (code=${exitCode ?? "unknown"})`));
73
86
  });
74
87
  await waitForRecordingReady(recording, 15e3);
75
88
  if (recording.exited) {
76
- cleanupRecordingStage(recording);
89
+ cleanupRecordingStaging(recording.stagingDir);
77
90
  throw new Error(
78
91
  `capture-helper exited before recording its first frame (code=${recording.exitCode ?? "unknown"}): ${recording.stderr || recording.stdout}`
79
92
  );
@@ -82,14 +95,15 @@ async function startRecipeRecording(adapter, projectRoot, artifactsDir, options)
82
95
  try {
83
96
  await stopRecordingProcess(recording);
84
97
  } finally {
85
- cleanupRecordingStage(recording);
98
+ cleanupRecordingStaging(recording.stagingDir);
86
99
  }
87
100
  throw new Error(
88
101
  `capture-helper did not record a frame within 15000ms: ${recording.stderr || recording.stdout || "no recorder output"}`
89
102
  );
90
103
  }
91
104
  activeRecordingsByPid.set(pid, recording);
92
- console.error(`INFO: recording recipe video with capture-helper pid=${pid} output=${recording.outputPath}`);
105
+ process.env[ACTIVE_EXTENSION_RECORDING_PID] = String(pid);
106
+ console.error(`INFO: recording recipe video with capture-helper pid=${pid} output=${outputPath}`);
93
107
  return recording;
94
108
  }
95
109
  async function captureActiveRecipeRecordingSnapshot(pid, outputPath, timeoutMs = 3e4) {
@@ -117,29 +131,22 @@ async function stopRecipeRecording(recording, result) {
117
131
  recording.finalized = true;
118
132
  try {
119
133
  await stopRecordingProcess(recording);
134
+ const validation = validateRecordingArtifact(recording);
135
+ if (validation.ok === false) {
136
+ throw new Error(
137
+ `capture-helper recording did not produce a usable video artifact: ${validation.reason}`
138
+ );
139
+ }
140
+ publishRecordingArtifact(recording);
141
+ if (result) addRecordingArtifactToManifest(result, recording);
120
142
  } catch (error) {
121
- cleanupRecordingStage(recording);
122
143
  if (result) removeRecordingArtifactFromManifest(result, recording.relativePath);
123
144
  throw error;
145
+ } finally {
146
+ activeRecordingsByPid.delete(recording.pid);
147
+ restoreActiveRecordingEnvironment(recording);
148
+ cleanupRecordingStaging(recording.stagingDir);
124
149
  }
125
- const validation = validateRecordingArtifact(recording);
126
- if (validation.ok === false) {
127
- cleanupRecordingStage(recording);
128
- if (result) removeRecordingArtifactFromManifest(result, recording.relativePath);
129
- throw new Error(
130
- `capture-helper recording did not produce a usable video artifact: ${validation.reason}`
131
- );
132
- }
133
- try {
134
- publishPrivateRecording(recording);
135
- } catch (error) {
136
- cleanupRecordingStage(recording);
137
- if (result) removeRecordingArtifactFromManifest(result, recording.relativePath);
138
- throw new Error(
139
- `capture-helper recording could not safely publish ${recording.outputPath}: ${error instanceof Error ? error.message : String(error)}`
140
- );
141
- }
142
- if (result) addRecordingArtifactToManifest(result, recording);
143
150
  }
144
151
  async function stopRecordingProcess(recording) {
145
152
  if (!recording.exited && !recording.child.stdin.destroyed) {
@@ -227,117 +234,146 @@ function rejectPendingSnapshots(recording, error) {
227
234
  recording.pendingSnapshots.clear();
228
235
  }
229
236
  function validateRecordingArtifact(recording) {
230
- if (!fs.existsSync(recording.stagedOutputPath)) {
231
- return { ok: false, reason: `missing output ${recording.stagedOutputPath}` };
237
+ if (!fs.existsSync(recording.stagedPath)) {
238
+ return { ok: false, reason: `missing output ${recording.stagedPath}` };
232
239
  }
233
- const info = fs.lstatSync(recording.stagedOutputPath);
234
- if (info.isSymbolicLink() || !info.isFile()) {
235
- return { ok: false, reason: `output is not a safe regular file ${recording.stagedOutputPath}` };
240
+ const stagedStat = fs.lstatSync(recording.stagedPath);
241
+ if (stagedStat.isSymbolicLink() || !stagedStat.isFile()) {
242
+ return { ok: false, reason: `unsafe non-regular output ${recording.stagedPath}` };
236
243
  }
237
- if (info.size === 0) {
238
- return { ok: false, reason: `empty output ${recording.stagedOutputPath}` };
244
+ const size = stagedStat.size;
245
+ if (size === 0) {
246
+ return { ok: false, reason: `empty output ${recording.stagedPath}` };
239
247
  }
240
248
  const recorderOutput = `${recording.stdout}
241
249
  ${recording.stderr}`;
242
250
  if (!recorderOutput.includes("record_complete")) {
243
251
  return {
244
252
  ok: false,
245
- reason: `capture-helper did not report record_complete for ${recording.stagedOutputPath}: ${recorderOutput.trim() || "no recorder output"}`
253
+ reason: `capture-helper did not report record_complete for ${recording.stagedPath}: ${recorderOutput.trim() || "no recorder output"}`
246
254
  };
247
255
  }
248
256
  const ffprobe = spawnSync(
249
257
  "ffprobe",
250
- ["-hide_banner", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", recording.stagedOutputPath],
258
+ ["-hide_banner", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", recording.stagedPath],
251
259
  { encoding: "utf8" }
252
260
  );
253
261
  if (ffprobe.error && ffprobe.error.code === "ENOENT") {
254
262
  return { ok: true };
255
263
  }
256
264
  if (ffprobe.error) {
257
- return { ok: false, reason: `ffprobe failed for ${recording.stagedOutputPath}: ${ffprobe.error.message}` };
265
+ return { ok: false, reason: `ffprobe failed for ${recording.stagedPath}: ${ffprobe.error.message}` };
258
266
  }
259
267
  if (ffprobe.status !== 0) {
260
268
  return {
261
269
  ok: false,
262
- reason: `invalid MP4 ${recording.stagedOutputPath}: ${ffprobe.stderr.trim() || ffprobe.stdout.trim() || `ffprobe exited ${ffprobe.status}`}`
270
+ reason: `invalid MP4 ${recording.stagedPath}: ${ffprobe.stderr.trim() || ffprobe.stdout.trim() || `ffprobe exited ${ffprobe.status}`}`
263
271
  };
264
272
  }
265
273
  const durationSeconds = Number(ffprobe.stdout.trim());
266
274
  if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
267
- return { ok: false, reason: `MP4 has no positive duration: ${recording.outputPath}` };
275
+ return { ok: false, reason: `MP4 has no positive duration: ${recording.stagedPath}` };
268
276
  }
269
277
  return { ok: true };
270
278
  }
271
- function preparePrivateRecordingStage(artifactsDir, relativePath) {
272
- const artifactsRoot = path.resolve(artifactsDir);
273
- const outputPath = path.resolve(artifactsRoot, relativePath);
274
- if (!outputPath.startsWith(`${artifactsRoot}${path.sep}`)) {
275
- throw new Error(`Refusing recording artifact outside artifacts dir: ${relativePath}`);
276
- }
277
- fs.mkdirSync(artifactsRoot, { recursive: true });
278
- ensureRecordingParent(artifactsRoot, path.dirname(outputPath));
279
- refuseUnsafeRecordingDestination(outputPath);
280
- const stagingDir = fs.mkdtempSync(path.join(artifactsRoot, ".extension-recording-"));
279
+ function preparePrivateRecordingDestination(artifactsDir, outputPath) {
280
+ const outputDir = path.dirname(outputPath);
281
+ ensureRecordingDirectory(path.resolve(artifactsDir), outputDir);
282
+ refuseRecordingDestinationSymlink(outputPath);
283
+ const stagingDir = fs.mkdtempSync(path.join(outputDir, ".mm-harness-recording-"));
281
284
  fs.chmodSync(stagingDir, 448);
282
285
  return {
283
- outputPath,
284
- stagedOutputPath: path.join(stagingDir, "full-run.mp4"),
285
- stagingDir
286
+ stagingDir,
287
+ stagedPath: path.join(stagingDir, "full-run.mp4")
286
288
  };
287
289
  }
288
- function ensureRecordingParent(artifactsRoot, parent) {
289
- const relative = path.relative(artifactsRoot, parent);
290
+ function ensureRecordingDirectory(artifactsRoot, outputDir) {
291
+ fs.mkdirSync(artifactsRoot, { recursive: true });
292
+ requireRecordingDirectory(artifactsRoot);
293
+ const relative = path.relative(artifactsRoot, outputDir);
290
294
  let current = artifactsRoot;
291
295
  for (const segment of relative.split(path.sep).filter(Boolean)) {
292
296
  current = path.join(current, segment);
293
- const info = lstatIfPresent(current);
294
- if (!info) {
295
- fs.mkdirSync(current, { mode: 448 });
296
- continue;
297
- }
298
- if (info.isSymbolicLink() || !info.isDirectory()) {
299
- throw new Error(`Refusing recording artifact parent that is not a real directory: ${current}`);
297
+ try {
298
+ fs.mkdirSync(current);
299
+ } catch (error) {
300
+ if (error.code !== "EEXIST") throw error;
300
301
  }
302
+ requireRecordingDirectory(current);
301
303
  }
302
304
  }
303
- function refuseUnsafeRecordingDestination(outputPath) {
304
- const info = lstatIfPresent(outputPath);
305
- if (!info) return;
306
- if (info.isSymbolicLink()) {
307
- throw new Error(`Refusing recording artifact destination symlink: ${outputPath}`);
308
- }
309
- if (!info.isFile()) {
310
- throw new Error(`Refusing recording artifact destination that is not a regular file: ${outputPath}`);
305
+ function requireRecordingDirectory(directory) {
306
+ const entry = fs.lstatSync(directory);
307
+ if (entry.isDirectory() && !entry.isSymbolicLink()) return;
308
+ throw new Error(
309
+ `Refusing unsafe recording artifact directory: ${directory}
310
+ Next: replace ${JSON.stringify(directory)} with a regular directory and retry the recipe.`
311
+ );
312
+ }
313
+ function publishRecordingArtifact(recording) {
314
+ const noFollow = fs.constants.O_NOFOLLOW ?? 0;
315
+ let descriptor;
316
+ try {
317
+ descriptor = fs.openSync(recording.stagedPath, fs.constants.O_RDONLY | noFollow);
318
+ const stagedStat = fs.fstatSync(descriptor);
319
+ if (!stagedStat.isFile()) {
320
+ throw unsafeRecordingArtifactError(recording.stagedPath);
321
+ }
322
+ fs.fchmodSync(descriptor, 384);
323
+ } catch (error) {
324
+ if (error instanceof Error && error.message.includes("Next:")) throw error;
325
+ throw unsafeRecordingArtifactError(
326
+ recording.stagedPath,
327
+ error instanceof Error ? error.message : String(error)
328
+ );
329
+ } finally {
330
+ if (descriptor !== void 0) fs.closeSync(descriptor);
311
331
  }
332
+ refuseRecordingDestinationSymlink(recording.outputPath);
333
+ fs.renameSync(recording.stagedPath, recording.outputPath);
312
334
  }
313
- function lstatIfPresent(filePath) {
335
+ function refuseRecordingDestinationSymlink(outputPath) {
314
336
  try {
315
- return fs.lstatSync(filePath);
337
+ const destination = fs.lstatSync(outputPath);
338
+ if (destination.isSymbolicLink()) {
339
+ throw new Error(
340
+ `Refusing recording artifact destination symlink: ${outputPath}
341
+ Next: rm -- ${JSON.stringify(outputPath)} and retry the recipe.`
342
+ );
343
+ }
344
+ if (!destination.isFile()) {
345
+ throw new Error(
346
+ `Refusing non-file recording artifact destination: ${outputPath}
347
+ Next: remove ${JSON.stringify(outputPath)} and retry the recipe.`
348
+ );
349
+ }
316
350
  } catch (error) {
317
- if (error.code === "ENOENT") return void 0;
351
+ if (error.code === "ENOENT") return;
318
352
  throw error;
319
353
  }
320
354
  }
321
- function publishPrivateRecording(recording) {
322
- const descriptor = fs.openSync(
323
- recording.stagedOutputPath,
324
- fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0)
355
+ function unsafeRecordingArtifactError(stagedPath, detail) {
356
+ return new Error(
357
+ `Capture-helper recording output is not a safe regular file: ${stagedPath}${detail ? ` (${detail})` : ""}.
358
+ Next: capture-helper doctor --json`
325
359
  );
360
+ }
361
+ function cleanupRecordingStaging(stagingDir) {
326
362
  try {
327
- const info = fs.fstatSync(descriptor);
328
- if (!info.isFile()) {
329
- throw new Error(`Captured recording is not a safe regular file: ${recording.stagedOutputPath}`);
330
- }
331
- fs.fchmodSync(descriptor, 384);
332
- refuseUnsafeRecordingDestination(recording.outputPath);
333
- fs.renameSync(recording.stagedOutputPath, recording.outputPath);
334
- } finally {
335
- fs.closeSync(descriptor);
363
+ fs.rmSync(stagingDir, { recursive: true, force: true });
364
+ } catch (error) {
365
+ console.error(
366
+ `WARN: could not remove capture-helper staging directory ${stagingDir}: ${error instanceof Error ? error.message : String(error)}`
367
+ );
336
368
  }
337
- cleanupRecordingStage(recording);
338
369
  }
339
- function cleanupRecordingStage(recording) {
340
- fs.rmSync(recording.stagingDir, { recursive: true, force: true });
370
+ function restoreActiveRecordingEnvironment(recording) {
371
+ if (process.env[ACTIVE_EXTENSION_RECORDING_PID] !== String(recording.pid)) return;
372
+ if (recording.previousActiveRecordingPid === void 0) {
373
+ delete process.env[ACTIVE_EXTENSION_RECORDING_PID];
374
+ return;
375
+ }
376
+ process.env[ACTIVE_EXTENSION_RECORDING_PID] = recording.previousActiveRecordingPid;
341
377
  }
342
378
  function addRecordingArtifactToManifest(result, recording) {
343
379
  const manifestPath = result.artifactManifestPath;