@efffrida/vitest-pool 0.0.13 → 0.0.15

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/src/index.ts CHANGED
@@ -1,18 +1,18 @@
1
- import type * as Option from "effect/Option";
1
+ import type * as Context from "effect/Context";
2
2
 
3
+ import * as Array from "effect/Array";
3
4
  import * as Cause from "effect/Cause";
4
5
  import * as Deferred from "effect/Deferred";
5
6
  import * as Duration from "effect/Duration";
6
7
  import * as Effect from "effect/Effect";
7
8
  import * as Exit from "effect/Exit";
8
9
  import * as FileSystem from "effect/FileSystem";
10
+ import * as Function from "effect/Function";
9
11
  import * as Layer from "effect/Layer";
10
- import * as ManagedRuntime from "effect/ManagedRuntime";
11
12
  import * as Match from "effect/Match";
12
13
  import * as Path from "effect/Path";
13
14
  import * as Schema from "effect/Schema";
14
15
  import * as Scope from "effect/Scope";
15
- import * as Sink from "effect/Sink";
16
16
  import * as Stream from "effect/Stream";
17
17
  import * as ChildProcess from "effect/unstable/process/ChildProcess";
18
18
 
@@ -22,7 +22,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
22
22
  import * as FridaDevice from "@efffrida/frida-tools/FridaDevice";
23
23
  import * as FridaScript from "@efffrida/frida-tools/FridaScript";
24
24
  import * as FridaSession from "@efffrida/frida-tools/FridaSession";
25
- import * as Esbuild from "esbuild";
26
25
  import * as Flatted from "flatted";
27
26
  import * as Frida from "frida";
28
27
 
@@ -80,35 +79,17 @@ const ConfigSchema = Schema.Struct({
80
79
  * @category Tests
81
80
  */
82
81
  export class FridaPoolWorker implements VitestNode.PoolWorker {
83
- readonly agentTemplatePath = new URL("../frida/agent.ts", import.meta.url);
84
- readonly name = "frida-pool";
82
+ public readonly name = "frida-pool";
83
+ private static initQueue: Promise<void> = Promise.resolve();
85
84
 
86
- private readonly customOptions: Schema.Schema.Type<typeof ConfigSchema>;
85
+ private readonly scope: Scope.Closeable;
86
+ private readonly scriptContext: Promise<Context.Context<FridaScript.FridaScript>>;
87
+ private readonly cancelables: Map<(arg: any) => void, (interrupter?: number) => void> = new Map();
87
88
 
88
- private readonly cancelables: Map<(arg: any) => void, (interrupter?: number) => void>;
89
-
90
- private modifiedAgentScope: Scope.Closeable;
91
- private managedRuntime: ManagedRuntime.ManagedRuntime<FridaScript.FridaScript, unknown> | undefined;
92
- private sends: Array<Promise<Exit.Exit<unknown, unknown>>> = [];
93
- private compiledAgentUrlPromise: Promise<URL>;
94
- private messageCallback: ((arg: any) => void) | undefined;
89
+ private sends: Array<Promise<unknown>> = [];
95
90
 
96
91
  constructor(poolOptions: VitestNode.PoolOptions, customOptions: Schema.Schema.Type<typeof ConfigSchema>) {
97
- this.customOptions = customOptions;
98
- this.cancelables = new Map();
99
- this.managedRuntime = undefined;
100
- this.modifiedAgentScope = Effect.runSync(Scope.make());
101
- this.compiledAgentUrlPromise = compileTestFiles(this.agentTemplatePath, poolOptions).pipe(
102
- Scope.provide(this.modifiedAgentScope),
103
- Effect.provide(NodeServices.layer),
104
- Effect.runPromise
105
- );
106
- }
107
-
108
- async start(): Promise<void> {
109
- const tempAgentUrl = await this.compiledAgentUrlPromise;
110
-
111
- const FridaRuntime = Match.value(this.customOptions.runtime).pipe(
92
+ const FridaRuntime = Match.value(customOptions.runtime).pipe(
112
93
  Match.when(undefined, () => undefined),
113
94
  Match.when("v8", () => Frida.ScriptRuntime.V8),
114
95
  Match.when("qjs", () => Frida.ScriptRuntime.QJS),
@@ -116,7 +97,7 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
116
97
  Match.exhaustive
117
98
  );
118
99
 
119
- const FridaPlatform = Match.value(this.customOptions.platform).pipe(
100
+ const FridaPlatform = Match.value(customOptions.platform).pipe(
120
101
  Match.when(undefined, () => undefined),
121
102
  Match.when("gum", () => Frida.JsPlatform.Gum),
122
103
  Match.when("browser", () => Frida.JsPlatform.Browser),
@@ -124,19 +105,19 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
124
105
  Match.exhaustive
125
106
  );
126
107
 
127
- const DeviceLive = Match.value(this.customOptions.device).pipe(
108
+ const DeviceLive = Match.value(customOptions.device).pipe(
128
109
  Match.when({ connection: "local" }, () => FridaDevice.layerLocalDevice),
129
110
  Match.when({ connection: "usb" }, ({ timeout }) =>
130
- FridaDevice.layerUsbDevice({
131
- timeout: timeout ? Duration.toMillis(timeout) : undefined,
132
- } as Frida.GetDeviceOptions)
111
+ FridaDevice.layerUsbDevice(timeout !== undefined ? { timeout: Duration.toMillis(timeout) } : {})
133
112
  ),
134
113
  Match.when({ connection: "remote" }, ({ address, keepaliveInterval, origin, token }) =>
135
114
  FridaDevice.layerRemoteDevice(address, {
136
- token,
137
- origin,
138
- keepaliveInterval: keepaliveInterval ? Duration.toSeconds(keepaliveInterval) : undefined,
139
- } as Frida.RemoteDeviceOptions)
115
+ ...(token !== undefined ? { token } : {}),
116
+ ...(origin !== undefined ? { origin } : {}),
117
+ ...(keepaliveInterval !== undefined
118
+ ? { keepaliveInterval: Duration.toMillis(keepaliveInterval) }
119
+ : {}),
120
+ })
140
121
  ),
141
122
  Match.when(
142
123
  { connection: "android-emulator" },
@@ -151,11 +132,22 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
151
132
  Match.exhaustive
152
133
  );
153
134
 
154
- const SessionLive = Match.value(this.customOptions.attach).pipe(
135
+ const SessionLive = Match.value(customOptions.attach).pipe(
155
136
  Match.when({ pid: Match.number }, ({ pid }) => FridaSession.layer(pid)),
156
- Match.when({ attachFrontmost: true }, ({ frontmostScope }) =>
157
- FridaSession.layerFrontmost({ scope: frontmostScope } as Frida.FrontmostQueryOptions)
158
- ),
137
+ Match.when({ attachFrontmost: true }, ({ frontmostScope }) => {
138
+ return FridaSession.layerFrontmost(
139
+ frontmostScope !== undefined
140
+ ? {
141
+ scope: Match.value(frontmostScope).pipe(
142
+ Match.when("minimal", () => Frida.Scope.Minimal),
143
+ Match.when("metadata", () => Frida.Scope.Metadata),
144
+ Match.when("full", () => Frida.Scope.Full),
145
+ Match.exhaustive
146
+ ),
147
+ }
148
+ : {}
149
+ );
150
+ }),
159
151
  Match.when({ preSpawn: true }, ({ spawn }) =>
160
152
  Layer.unwrap(
161
153
  Effect.gen(function* () {
@@ -168,98 +160,156 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
168
160
  Match.orElse(({ spawn }) => FridaSession.layer(spawn))
169
161
  );
170
162
 
171
- const FridaLive = Layer.provide(SessionLive, DeviceLive).pipe(Layer.provide(NodeServices.layer));
172
- const ScriptLive = FridaScript.layer(tempAgentUrl, {
173
- externals: ["jsdom", "happy-dom", "@edge-runtime/vm"],
174
- ...(FridaRuntime !== undefined ? { runtime: FridaRuntime } : {}),
175
- ...(FridaPlatform !== undefined ? { platform: FridaPlatform } : {}),
176
- }).pipe(Layer.provide(FridaLive));
163
+ const FridaLive = Layer.provide(SessionLive, DeviceLive);
164
+ const ScriptLive = Effect.gen(function* () {
165
+ const path = yield* Path.Path;
166
+ const fs = yield* FileSystem.FileSystem;
167
+
168
+ const tempDir = path.join(poolOptions.project.config.root, "temp");
169
+ yield* fs.makeDirectory(tempDir, { recursive: true });
170
+
171
+ const agentUrl = yield* path.fromFileUrl(new URL("../frida/agent.ts", import.meta.url));
172
+ const baseAgent = yield* fs.readFileString(agentUrl);
173
+ const tempFile = yield* fs.makeTempFileScoped({
174
+ directory: tempDir,
175
+ prefix: ".vitest-frida-pool-agent-",
176
+ suffix: ".ts",
177
+ });
178
+
179
+ const setupFiles = poolOptions.project.config.setupFiles;
180
+ const testFiles = yield* Effect.map(
181
+ Effect.promise(() => poolOptions.project.globTestFiles()),
182
+ ({ testFiles }) => testFiles
183
+ );
184
+ const globalSetupFiles = Array.isArray(poolOptions.project.config.globalSetup)
185
+ ? poolOptions.project.config.globalSetup
186
+ : Array.make(poolOptions.project.config.globalSetup);
187
+
188
+ const marker = "// @efffrida/vitest-pool/agent/file-map";
189
+ const allFiles = Array.flatten([setupFiles, testFiles, globalSetupFiles]);
190
+ const newContent = Function.pipe(
191
+ allFiles,
192
+ Array.map(
193
+ (file) => `
194
+ if (_file === "${file}") {
195
+ // @ts-ignore
196
+ return await import("${file}")
197
+ }`
198
+ ),
199
+ Array.join("\n")
200
+ );
201
+
202
+ yield* fs.writeFileString(tempFile, baseAgent.replace(marker, newContent));
203
+ return yield* path.toFileUrl(tempFile);
204
+ }).pipe(
205
+ Effect.map(
206
+ FridaScript.layer({
207
+ ...(FridaRuntime !== undefined ? { runtime: FridaRuntime } : {}),
208
+ ...(FridaPlatform !== undefined ? { platform: FridaPlatform } : {}),
209
+ })
210
+ ),
211
+ Layer.unwrap,
212
+ Layer.provide(FridaLive),
213
+ Layer.provide(NodeServices.layer),
214
+ Layer.satisfiesSuccessType<FridaScript.FridaScript>()
215
+ );
177
216
 
178
- this.managedRuntime = ManagedRuntime.make(ScriptLive);
217
+ this.scope = Scope.makeUnsafe();
218
+ const prev = FridaPoolWorker.initQueue;
219
+ const runInit = () => ScriptLive.pipe(Layer.buildWithScope(this.scope), Effect.runPromise);
220
+ this.scriptContext = prev.then(runInit, runInit);
221
+ FridaPoolWorker.initQueue = this.scriptContext.then(
222
+ () => undefined,
223
+ () => undefined
224
+ );
225
+ }
179
226
 
180
- const exit = await this.managedRuntime.runPromiseExit(Effect.void);
181
- if (Exit.isSuccess(exit)) return;
182
- const prettyError = Cause.prettyErrors(exit.cause);
183
- throw prettyError[0];
227
+ async start(): Promise<void> {
228
+ await this.scriptContext;
184
229
  }
185
230
 
186
231
  async stop(): Promise<void> {
232
+ await Promise.allSettled(this.sends);
187
233
  for (const cancelable of this.cancelables.values()) cancelable();
234
+ await Scope.close(this.scope, Exit.void).pipe(Effect.runPromise);
188
235
  this.cancelables.clear();
189
- await Promise.allSettled(this.sends);
190
- await this.managedRuntime!.dispose();
191
- await Effect.runPromise(Scope.close(this.modifiedAgentScope, Exit.void));
192
236
  }
193
237
 
194
238
  async send(message: VitestNode.WorkerRequest): Promise<void> {
195
- const sendPromise = this.managedRuntime!.runPromiseExit(
196
- Effect.flatMap(FridaScript.FridaScript, (fridaScript) => fridaScript.callExport("onMessage")(message))
197
- );
198
-
199
- this.sends.push(sendPromise);
200
- const exit = await sendPromise;
201
- this.sends = this.sends.filter((p) => p !== sendPromise);
202
- if (Exit.isSuccess(exit)) {
203
- if (exit.value !== null && exit.value !== undefined) {
204
- this.messageCallback?.(exit.value);
205
- }
206
- return;
239
+ const context = await this.scriptContext;
240
+ let sendPromise: Promise<unknown> = undefined!;
241
+
242
+ try {
243
+ sendPromise = Effect.flatMap(FridaScript.FridaScript, (fridaScript) =>
244
+ fridaScript.callExport("onMessage")(message)
245
+ ).pipe(Effect.runPromiseWith(context));
246
+ this.sends.push(sendPromise);
247
+ await sendPromise;
248
+ } finally {
249
+ this.sends = this.sends.filter((p) => p !== sendPromise);
207
250
  }
208
- const prettyError = Cause.prettyErrors(exit.cause);
209
- throw prettyError[0];
210
251
  }
211
252
 
212
253
  on(event: string, callback: (arg: any) => void): void {
213
- let cancelable!: (interrupter?: number) => void;
214
-
215
254
  switch (event) {
216
255
  case "message": {
217
- this.messageCallback = callback;
218
- const sink = Sink.forEach<
219
- {
220
- message: unknown;
221
- data: Option.Option<Buffer<ArrayBufferLike>>;
222
- },
223
- void,
224
- never,
225
- never
226
- >((input) =>
227
- Effect.sync(() => {
228
- callback(input.message);
229
- })
230
- );
231
- cancelable = this.managedRuntime!.runCallback(
232
- Effect.flatMap(FridaScript.FridaScript, (fridaScript) => Stream.run(fridaScript.stream, sink))
233
- );
256
+ this.scriptContext.then((ctx) => {
257
+ this.cancelables.set(
258
+ callback,
259
+ Effect.runCallbackWith(ctx)(
260
+ Effect.flatMap(FridaScript.FridaScript, (fridaScript) =>
261
+ Stream.runForEach(fridaScript.stream, (input) =>
262
+ Effect.sync(() => callback(input.message))
263
+ )
264
+ )
265
+ )
266
+ );
267
+ });
268
+
234
269
  break;
235
270
  }
236
271
 
237
272
  case "error":
238
- cancelable = this.managedRuntime!.runCallback(
239
- Effect.flatMap(FridaScript.FridaScript, (fridaScript) => Deferred.await(fridaScript.scriptError)),
240
- {
241
- onExit: (exit) =>
242
- Exit.isSuccess(exit)
243
- ? callback(exit.value)
244
- : !Cause.hasInterruptsOnly(exit.cause)
245
- ? callback(exit.cause)
246
- : {},
247
- }
248
- );
273
+ this.scriptContext.then((ctx) => {
274
+ this.cancelables.set(
275
+ callback,
276
+ Effect.runCallbackWith(ctx)(
277
+ Effect.flatMap(FridaScript.FridaScript, (fridaScript) =>
278
+ Deferred.await(fridaScript.scriptError)
279
+ ),
280
+ {
281
+ onExit: (exit) => {
282
+ if (Exit.isSuccess(exit)) {
283
+ callback(exit.value);
284
+ } else if (!Cause.hasInterruptsOnly(exit.cause)) {
285
+ callback(exit.cause);
286
+ }
287
+ },
288
+ }
289
+ )
290
+ );
291
+ });
292
+
249
293
  break;
250
294
 
251
295
  case "exit":
252
- cancelable = this.managedRuntime!.runCallback(
253
- Effect.flatMap(FridaScript.FridaScript, (fridaScript) => Deferred.await(fridaScript.destroyed)),
254
- { onExit: () => callback(void 0) }
255
- );
296
+ this.scriptContext.then((ctx) => {
297
+ this.cancelables.set(
298
+ callback,
299
+ Effect.runCallbackWith(ctx)(
300
+ Effect.flatMap(FridaScript.FridaScript, (fridaScript) =>
301
+ Deferred.await(fridaScript.destroyed)
302
+ ),
303
+ { onExit: () => callback(void 0) }
304
+ )
305
+ );
306
+ });
307
+
256
308
  break;
257
309
 
258
310
  default:
259
311
  throw new Error(`Event ${event} not supported in FridaPoolWorker`);
260
312
  }
261
-
262
- this.cancelables.set(callback, cancelable);
263
313
  }
264
314
 
265
315
  off(_event: string, callback: (arg: any) => void): void {
@@ -268,9 +318,6 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
268
318
  this.cancelables.delete(callback);
269
319
  cancelable();
270
320
  }
271
- if (this.messageCallback === callback) {
272
- this.messageCallback = undefined;
273
- }
274
321
  }
275
322
 
276
323
  deserialize(data: unknown) {
@@ -290,206 +337,9 @@ export class FridaPoolWorker implements VitestNode.PoolWorker {
290
337
  export const createFridaPool = (
291
338
  customOptions: Schema.Codec.Encoded<typeof ConfigSchema>
292
339
  ): VitestNode.PoolRunnerInitializer => {
293
- const decoded = Schema.decodeUnknownSync(ConfigSchema)(customOptions);
294
340
  return {
295
341
  name: "frida-pool",
296
- createPoolWorker: (options: VitestNode.PoolOptions) => new FridaPoolWorker(options, decoded),
342
+ createPoolWorker: (options: VitestNode.PoolOptions) =>
343
+ new FridaPoolWorker(options, Schema.decodeUnknownSync(ConfigSchema)(customOptions)),
297
344
  };
298
345
  };
299
-
300
- /** @internal */
301
- const vitestGlobalsPlugin: Esbuild.Plugin = {
302
- name: "vitest-globals",
303
- setup(build) {
304
- // Handle vitest and @vitest/* imports
305
- build.onResolve({ filter: /^(vitest|@vitest\/.*)$/ }, (args) => ({
306
- path: args.path,
307
- namespace: "vitest-globals",
308
- }));
309
-
310
- build.onLoad({ filter: /.*/, namespace: "vitest-globals" }, (args) => {
311
- if (args.path === "vitest") {
312
- return {
313
- loader: "js",
314
- contents: "export * from 'VITEST_GLOBAL';\nexport { default } from 'VITEST_GLOBAL';",
315
- };
316
- }
317
- if (args.path === "@vitest/runner") {
318
- return {
319
- loader: "js",
320
- contents: "export * from 'VITEST_RUNNER_GLOBAL';\nexport { default } from 'VITEST_RUNNER_GLOBAL';",
321
- };
322
- }
323
- return {
324
- loader: "js",
325
- contents: "export * from 'VITEST_GLOBAL';\nexport { default } from 'VITEST_GLOBAL';",
326
- };
327
- });
328
-
329
- build.onResolve({ filter: /^VITEST_(GLOBAL|RUNNER_GLOBAL)$/ }, (args) => ({
330
- path: args.path,
331
- namespace: "vitest-synthetic",
332
- }));
333
-
334
- build.onLoad({ filter: /.*/, namespace: "vitest-synthetic" }, (args) => {
335
- const globalName = args.path === "VITEST_GLOBAL" ? "__vitest" : "__vitest_runner";
336
- return {
337
- loader: "js",
338
- contents: `
339
- const g = globalThis.${globalName};
340
- export default g;
341
- export const {
342
- describe, it, test, expect, vi, beforeAll, afterAll,
343
- beforeEach, afterEach, suite, bench, assert
344
- } = g;
345
- `,
346
- };
347
- });
348
-
349
- // Handle Node.js built-in modules - redirect to globals set up by the agent
350
- // The agent imports these from frida-compile's shims and exposes them as globals
351
- const nodeModuleMap: Record<string, string> = {
352
- "node:assert": "__node_assert",
353
- "node:buffer": "__node_buffer",
354
- "node:crypto": "__node_crypto",
355
- "node:diagnostics_channel": "__node_diagnosticsChannel",
356
- "node:events": "__node_events",
357
- "node:fs": "__node_fs",
358
- "node:net": "__node_net",
359
- "node:os": "__node_os",
360
- "node:path": "__node_path",
361
- "node:process": "__node_process",
362
- "node:stream": "__node_stream",
363
- "node:timers": "__node_timers",
364
- "node:tty": "__node_tty",
365
- "node:url": "__node_url",
366
- "node:util": "__node_util",
367
- "node:vm": "__node_vm",
368
- assert: "__node_assert",
369
- buffer: "__node_buffer",
370
- crypto: "__node_crypto",
371
- diagnostics_channel: "__node_diagnosticsChannel",
372
- events: "__node_events",
373
- fs: "__node_fs",
374
- net: "__node_net",
375
- os: "__node_os",
376
- path: "__node_path",
377
- process: "__node_process",
378
- stream: "__node_stream",
379
- timers: "__node_timers",
380
- tty: "__node_tty",
381
- url: "__node_url",
382
- util: "__node_util",
383
- vm: "__node_vm",
384
- };
385
-
386
- build.onResolve(
387
- {
388
- filter: /^(node:)?(assert|buffer|crypto|diagnostics_channel|events|fs|net|os|path|process|stream|timers|tty|url|util|vm)$/,
389
- },
390
- (args) => ({
391
- path: args.path,
392
- namespace: "node-globals",
393
- })
394
- );
395
-
396
- build.onLoad({ filter: /.*/, namespace: "node-globals" }, (args) => {
397
- const globalName = nodeModuleMap[args.path];
398
- if (!globalName) {
399
- return { loader: "js", contents: "export default {};" };
400
- }
401
- return {
402
- loader: "js",
403
- contents: `
404
- const m = globalThis.${globalName};
405
- export default m;
406
- export const { Buffer } = m.Buffer ? m : { Buffer: m.default?.Buffer };
407
- export * from 'NODE_MODULE_REEXPORT_${globalName}';
408
- `,
409
- };
410
- });
411
-
412
- // Handle re-exports from node modules
413
- build.onResolve({ filter: /^NODE_MODULE_REEXPORT_/ }, (args) => ({
414
- path: args.path,
415
- namespace: "node-reexport",
416
- }));
417
-
418
- build.onLoad({ filter: /.*/, namespace: "node-reexport" }, (args) => {
419
- const globalName = args.path.replace("NODE_MODULE_REEXPORT_", "");
420
- return {
421
- loader: "js",
422
- contents: `
423
- const m = globalThis.${globalName};
424
- const mod = m.default || m;
425
- for (const key in mod) {
426
- if (key !== 'default') {
427
- Object.defineProperty(exports, key, {
428
- enumerable: true,
429
- get: () => mod[key]
430
- });
431
- }
432
- }
433
- `,
434
- };
435
- });
436
- },
437
- };
438
-
439
- /** @internal */
440
- const compileTestFiles = Effect.fnUntraced(function* (
441
- agentTemplatePath: URL,
442
- poolOptions: VitestNode.PoolOptions,
443
- customOptions?: Schema.Schema.Type<typeof ConfigSchema> | undefined
444
- ) {
445
- const path = yield* Path.Path;
446
- const fs = yield* FileSystem.FileSystem;
447
- const url = yield* path.fromFileUrl(agentTemplatePath);
448
-
449
- const testFilesList: Array<string> = (poolOptions.project as any).testFilesList ?? [];
450
- const setupFiles: Array<string> = poolOptions.project.config.setupFiles ?? [];
451
- const allFiles = [...new Set([...setupFiles, ...testFilesList])];
452
- const testFilesMap: Record<string, string> = {};
453
-
454
- const esbuildPlatform = Match.value(customOptions?.platform).pipe(
455
- Match.when("browser", () => "browser" as const),
456
- Match.when("neutral", () => "neutral" as const),
457
- Match.whenOr("gum", undefined, () => "node" as const),
458
- Match.orElseAbsurd
459
- );
460
-
461
- for (const testFile of allFiles) {
462
- const result = yield* Effect.promise(() =>
463
- Esbuild.build({
464
- bundle: true,
465
- write: false,
466
- format: "esm",
467
- platform: esbuildPlatform,
468
- target: "es2020",
469
- entryPoints: [testFile],
470
- plugins: [vitestGlobalsPlugin],
471
- })
472
- );
473
-
474
- if (!result.outputFiles || result.outputFiles.length === 0) {
475
- return yield* Effect.die(new Error(`esbuild produced no output for ${testFile}`));
476
- } else {
477
- testFilesMap[testFile] = Buffer.from(result.outputFiles[0].text).toString("base64");
478
- }
479
- }
480
-
481
- const agentTemplateContents = yield* fs.readFileString(url);
482
- const modifiedAgentContents = agentTemplateContents.replace(
483
- /^const testFiles: Record<string, string> = \{\};$/m,
484
- `const testFiles: Record<string, string> = ${JSON.stringify(testFilesMap, null, 4)};`
485
- );
486
-
487
- const tempAgentPath = yield* fs.makeTempFileScoped({
488
- suffix: ".ts",
489
- prefix: ".agent-",
490
- directory: path.dirname(url),
491
- });
492
-
493
- yield* fs.writeFileString(tempAgentPath, modifiedAgentContents);
494
- return yield* path.toFileUrl(tempAgentPath);
495
- });