@cotestdev/mcp_playwright 0.0.51 → 0.0.52

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.
Files changed (34) hide show
  1. package/lib/mcp/browser/browserContextFactory.js +11 -4
  2. package/lib/mcp/browser/browserServerBackend.js +2 -4
  3. package/lib/mcp/browser/config.js +71 -47
  4. package/lib/mcp/browser/context.js +65 -4
  5. package/lib/mcp/browser/logFile.js +96 -0
  6. package/lib/mcp/browser/response.js +107 -104
  7. package/lib/mcp/browser/sessionLog.js +1 -1
  8. package/lib/mcp/browser/tab.js +73 -18
  9. package/lib/mcp/browser/tools/config.js +41 -0
  10. package/lib/mcp/browser/tools/console.js +6 -2
  11. package/lib/mcp/browser/tools/cookies.js +152 -0
  12. package/lib/mcp/browser/tools/install.js +1 -0
  13. package/lib/mcp/browser/tools/network.js +25 -11
  14. package/lib/mcp/browser/tools/pdf.js +3 -4
  15. package/lib/mcp/browser/tools/route.js +140 -0
  16. package/lib/mcp/browser/tools/runCode.js +0 -2
  17. package/lib/mcp/browser/tools/screenshot.js +6 -7
  18. package/lib/mcp/browser/tools/storage.js +3 -4
  19. package/lib/mcp/browser/tools/tracing.js +10 -9
  20. package/lib/mcp/browser/tools/utils.js +0 -6
  21. package/lib/mcp/browser/tools/video.js +31 -13
  22. package/lib/mcp/browser/tools/webstorage.js +223 -0
  23. package/lib/mcp/browser/tools.js +11 -3
  24. package/lib/mcp/extension/cdpRelay.js +7 -7
  25. package/lib/mcp/extension/extensionContextFactory.js +4 -2
  26. package/lib/mcp/program.js +19 -12
  27. package/lib/mcp/terminal/cli.js +23 -2
  28. package/lib/mcp/terminal/command.js +34 -30
  29. package/lib/mcp/terminal/commands.js +310 -38
  30. package/lib/mcp/terminal/daemon.js +23 -38
  31. package/lib/mcp/terminal/helpGenerator.js +8 -6
  32. package/lib/mcp/terminal/program.js +482 -199
  33. package/lib/mcp/terminal/socketConnection.js +17 -2
  34. package/package.json +2 -2
@@ -128,7 +128,7 @@ class IsolatedContextFactory extends BaseContextFactory {
128
128
  handleSIGTERM: false
129
129
  }).catch((error) => {
130
130
  if (error.message.includes("Executable doesn't exist"))
131
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
131
+ throwBrowserIsNotInstalledError(this.config);
132
132
  throw error;
133
133
  });
134
134
  }
@@ -201,7 +201,7 @@ class PersistentContextFactory {
201
201
  return { browserContext, close };
202
202
  } catch (error) {
203
203
  if (error.message.includes("Executable doesn't exist"))
204
- throw new Error(`Browser specified in your config is not installed. Either install it (likely) or change the config.`);
204
+ throwBrowserIsNotInstalledError(this.config);
205
205
  if (error.message.includes("cannot open shared object file: No such file or directory")) {
206
206
  const browserName = launchOptions.channel ?? this.config.browser.browserName;
207
207
  throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`);
@@ -306,12 +306,12 @@ class SharedContextFactory {
306
306
  async function computeTracesDir(config, clientInfo) {
307
307
  if (!config.saveTrace && !config.capabilities?.includes("devtools"))
308
308
  return;
309
- return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
309
+ return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code" });
310
310
  }
311
311
  async function browserContextOptionsFromConfig(config, clientInfo) {
312
312
  const result = { ...config.browser.contextOptions };
313
313
  if (config.saveVideo) {
314
- const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code", title: "Saving video" });
314
+ const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code" });
315
315
  result.recordVideo = {
316
316
  dir,
317
317
  size: config.saveVideo
@@ -319,6 +319,13 @@ async function browserContextOptionsFromConfig(config, clientInfo) {
319
319
  }
320
320
  return result;
321
321
  }
322
+ function throwBrowserIsNotInstalledError(config) {
323
+ const channel = config.browser.launchOptions?.channel ?? config.browser.browserName;
324
+ if (config.skillMode)
325
+ throw new Error(`Browser "${channel}" is not installed. Run \`playwright-cli install-browser ${channel}\` to install`);
326
+ else
327
+ throw new Error(`Browser "${channel}" is not installed. Either install it (likely) or change the config.`);
328
+ }
322
329
  // Annotate the CommonJS export names for ESM import in node:
323
330
  0 && (module.exports = {
324
331
  SharedContextFactory,
@@ -41,7 +41,6 @@ class BrowserServerBackend {
41
41
  sessionLog: this._sessionLog,
42
42
  clientInfo
43
43
  });
44
- this._context.onBrowserContextClosed = () => this.onBrowserContextClosed?.();
45
44
  }
46
45
  async listTools() {
47
46
  return this._tools.map((tool) => (0, import_tool.toMcpTool)(tool.schema));
@@ -58,13 +57,12 @@ Tool "${name}" not found` }],
58
57
  const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
59
58
  const cwd = rawArguments?._meta && typeof rawArguments?._meta === "object" && rawArguments._meta?.cwd;
60
59
  const context = this._context;
61
- const response = import_response.Response.create(context, name, parsedArguments);
60
+ const response = new import_response.Response(context, name, parsedArguments, cwd);
62
61
  context.setRunningTool(name);
63
62
  let responseObject;
64
63
  try {
65
64
  await tool.handle(context, parsedArguments, response);
66
- const sections = await response.build();
67
- responseObject = await (0, import_response.serializeResponse)(context, sections, cwd ?? context.firstRootPath());
65
+ responseObject = await response.serialize();
68
66
  this._sessionLog?.logResponse(name, parsedArguments, responseObject);
69
67
  } catch (error) {
70
68
  return {
@@ -40,7 +40,9 @@ __export(config_exports, {
40
40
  resolutionParser: () => resolutionParser,
41
41
  resolveCLIConfig: () => resolveCLIConfig,
42
42
  resolveConfig: () => resolveConfig,
43
- semicolonSeparatedList: () => semicolonSeparatedList
43
+ semicolonSeparatedList: () => semicolonSeparatedList,
44
+ workspaceDir: () => workspaceDir,
45
+ workspaceFile: () => workspaceFile
44
46
  });
45
47
  module.exports = __toCommonJS(config_exports);
46
48
  var import_fs = __toESM(require("fs"));
@@ -59,7 +61,8 @@ const defaultConfig = {
59
61
  },
60
62
  contextOptions: {
61
63
  viewport: null
62
- }
64
+ },
65
+ isolated: false
63
66
  },
64
67
  console: {
65
68
  level: "info"
@@ -79,38 +82,35 @@ const defaultConfig = {
79
82
  navigation: 6e4
80
83
  }
81
84
  };
82
- const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
85
+ const defaultDaemonConfig = mergeConfig(defaultConfig, {
83
86
  browser: {
84
- userDataDir: cliOptions.extension ? void 0 : "<daemon-data-dir>",
85
- // Use default user profile with extension.
86
87
  launchOptions: {
87
- headless: !cliOptions.daemonHeaded
88
+ headless: true
88
89
  },
89
- contextOptions: {
90
- viewport: cliOptions.daemonHeaded ? null : { width: 1280, height: 720 }
91
- }
92
- },
93
- outputMode: "file",
94
- snapshot: {
95
- mode: "full"
90
+ isolated: true
96
91
  }
97
92
  });
98
93
  async function resolveConfig(config) {
99
94
  return mergeConfig(defaultConfig, config);
100
95
  }
101
96
  async function resolveCLIConfig(cliOptions) {
102
- const configInFile = await loadConfig(cliOptions.config);
103
97
  const envOverrides = configFromEnv();
98
+ const daemonOverrides = await configForDaemonSession(cliOptions);
104
99
  const cliOverrides = configFromCLIOptions(cliOptions);
105
- let result = cliOptions.daemon ? defaultDaemonConfig(cliOptions) : defaultConfig;
100
+ const configFile = cliOverrides.configFile ?? envOverrides.configFile ?? daemonOverrides.configFile;
101
+ const configInFile = await loadConfig(configFile);
102
+ let result = cliOptions.daemonSession ? defaultDaemonConfig : defaultConfig;
106
103
  result = mergeConfig(result, configInFile);
104
+ result = mergeConfig(result, daemonOverrides);
107
105
  result = mergeConfig(result, envOverrides);
108
106
  result = mergeConfig(result, cliOverrides);
109
- if (cliOptions.daemon)
107
+ if (daemonOverrides.sessionConfig) {
110
108
  result.skillMode = true;
111
- if (result.browser.userDataDir === "<daemon-data-dir>") {
112
- const browserToken = result.browser.launchOptions?.channel ?? result.browser?.browserName;
113
- result.browser.userDataDir = `${cliOptions.daemonDataDir}-${browserToken}`;
109
+ if (!result.extension && !result.browser.userDataDir && daemonOverrides.sessionConfig.userDataDirPrefix) {
110
+ const browserToken = result.browser.launchOptions?.channel ?? result.browser?.browserName;
111
+ const userDataDir = `${daemonOverrides.sessionConfig.userDataDirPrefix}-${browserToken}`;
112
+ result.browser.userDataDir = userDataDir;
113
+ }
114
114
  }
115
115
  if (result.browser.browserName === "chromium" && result.browser.launchOptions.chromiumSandbox === void 0) {
116
116
  if (process.platform === "linux")
@@ -118,6 +118,10 @@ async function resolveCLIConfig(cliOptions) {
118
118
  else
119
119
  result.browser.launchOptions.chromiumSandbox = true;
120
120
  }
121
+ result.configFile = configFile;
122
+ result.sessionConfig = daemonOverrides.sessionConfig;
123
+ if (result.sessionConfig && result.browser.launchOptions.headless !== false)
124
+ result.browser.contextOptions.viewport ??= { width: 1280, height: 720 };
121
125
  await validateConfig(result);
122
126
  return result;
123
127
  }
@@ -189,7 +193,7 @@ function configFromCLIOptions(cliOptions) {
189
193
  contextOptions.serviceWorkers = "block";
190
194
  if (cliOptions.grantPermissions)
191
195
  contextOptions.permissions = cliOptions.grantPermissions;
192
- const result = {
196
+ const config = {
193
197
  browser: {
194
198
  browserName,
195
199
  isolated: cliOptions.isolated,
@@ -198,6 +202,7 @@ function configFromCLIOptions(cliOptions) {
198
202
  contextOptions,
199
203
  cdpEndpoint: cliOptions.cdpEndpoint,
200
204
  cdpHeaders: cliOptions.cdpHeader,
205
+ cdpTimeout: cliOptions.cdpTimeout,
201
206
  initPage: cliOptions.initPage,
202
207
  initScript: cliOptions.initScript
203
208
  },
@@ -232,7 +237,7 @@ function configFromCLIOptions(cliOptions) {
232
237
  navigation: cliOptions.timeoutNavigation
233
238
  }
234
239
  };
235
- return result;
240
+ return { ...config, configFile: cliOptions.config };
236
241
  }
237
242
  function configFromEnv() {
238
243
  const options = {};
@@ -245,6 +250,7 @@ function configFromEnv() {
245
250
  options.caps = commaSeparatedList(process.env.PLAYWRIGHT_MCP_CAPS);
246
251
  options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
247
252
  options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
253
+ options.cdpTimeout = numberParser(process.env.PLAYWRIGHT_MCP_CDP_TIMEOUT);
248
254
  options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
249
255
  if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
250
256
  options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
@@ -281,6 +287,22 @@ function configFromEnv() {
281
287
  options.viewportSize = resolutionParser("--viewport-size", process.env.PLAYWRIGHT_MCP_VIEWPORT_SIZE);
282
288
  return configFromCLIOptions(options);
283
289
  }
290
+ async function configForDaemonSession(cliOptions) {
291
+ if (!cliOptions.daemonSession)
292
+ return {};
293
+ const sessionConfig = await import_fs.default.promises.readFile(cliOptions.daemonSession, "utf-8").then((data) => JSON.parse(data));
294
+ const config = configFromCLIOptions({
295
+ config: sessionConfig.cli.config,
296
+ browser: sessionConfig.cli.browser,
297
+ isolated: sessionConfig.cli.persistent === true ? false : void 0,
298
+ headless: sessionConfig.cli.headed ? false : void 0,
299
+ extension: sessionConfig.cli.extension,
300
+ userDataDir: sessionConfig.cli.profile,
301
+ outputMode: "file",
302
+ snapshotMode: "full"
303
+ });
304
+ return { ...config, sessionConfig };
305
+ }
284
306
  async function loadConfig(configFile) {
285
307
  if (!configFile)
286
308
  return {};
@@ -290,31 +312,38 @@ async function loadConfig(configFile) {
290
312
  throw new Error(`Failed to load config file: ${configFile}, ${error}`);
291
313
  }
292
314
  }
293
- function tmpDir() {
294
- return import_path.default.join(process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir(), "playwright-mcp-output");
315
+ function workspaceDir(clientInfo) {
316
+ return import_path.default.resolve((0, import_server.firstRootPath)(clientInfo) ?? process.cwd());
317
+ }
318
+ async function workspaceFile(config, clientInfo, fileName, perCallWorkspaceDir) {
319
+ const workspace = perCallWorkspaceDir ?? workspaceDir(clientInfo);
320
+ const resolvedName = import_path.default.resolve(workspace, fileName);
321
+ await checkFile(config, clientInfo, resolvedName, { origin: "code" });
322
+ return resolvedName;
295
323
  }
296
324
  function outputDir(config, clientInfo) {
325
+ if (config.outputDir)
326
+ return import_path.default.resolve(config.outputDir);
297
327
  const rootPath = (0, import_server.firstRootPath)(clientInfo);
298
- return config.outputDir ?? (rootPath ? import_path.default.join(rootPath, ".playwright-mcp") : void 0) ?? import_path.default.join(tmpDir(), String(clientInfo.timestamp));
328
+ if (rootPath)
329
+ return import_path.default.resolve(rootPath, config.skillMode ? ".playwright-cli" : ".playwright-mcp");
330
+ const tmpDir = process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir();
331
+ return import_path.default.resolve(tmpDir, "playwright-mcp-output", String(clientInfo.timestamp));
299
332
  }
300
333
  async function outputFile(config, clientInfo, fileName, options) {
301
- const file = await resolveFile(config, clientInfo, fileName, options);
302
- await import_fs.default.promises.mkdir(import_path.default.dirname(file), { recursive: true });
303
- (0, import_utilsBundle.debug)("pw:mcp:file")(options.title, file);
304
- return file;
334
+ const resolvedFile = import_path.default.resolve(outputDir(config, clientInfo), fileName);
335
+ await checkFile(config, clientInfo, resolvedFile, options);
336
+ await import_fs.default.promises.mkdir(import_path.default.dirname(resolvedFile), { recursive: true });
337
+ (0, import_utilsBundle.debug)("pw:mcp:file")(resolvedFile);
338
+ return resolvedFile;
305
339
  }
306
- async function resolveFile(config, clientInfo, fileName, options) {
307
- const dir = outputDir(config, clientInfo);
340
+ async function checkFile(config, clientInfo, resolvedFilename, options) {
308
341
  if (options.origin === "code")
309
- return import_path.default.resolve(dir, fileName);
310
- if (options.origin === "llm") {
311
- fileName = fileName.split("\\").join("/");
312
- const resolvedFile = import_path.default.resolve(dir, fileName);
313
- if (!resolvedFile.startsWith(import_path.default.resolve(dir) + import_path.default.sep))
314
- throw new Error(`Resolved file path ${resolvedFile} is outside of the output directory ${dir}. Use relative file names to stay within the output directory.`);
315
- return resolvedFile;
316
- }
317
- return import_path.default.join(dir, sanitizeForFilePath(fileName));
342
+ return;
343
+ const output = outputDir(config, clientInfo);
344
+ const workspace = workspaceDir(clientInfo);
345
+ if (!resolvedFilename.startsWith(output) && !resolvedFilename.startsWith(workspace))
346
+ throw new Error(`Resolved file path ${resolvedFilename} is outside of the output directory ${output} and workspace directory ${workspace}. Use relative file names to stay within the output directory.`);
318
347
  }
319
348
  function pickDefined(obj) {
320
349
  return Object.fromEntries(
@@ -425,13 +454,6 @@ function envToBoolean(value) {
425
454
  function envToString(value) {
426
455
  return value ? value.trim() : void 0;
427
456
  }
428
- function sanitizeForFilePath(s) {
429
- const sanitize = (s2) => s2.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
430
- const separator = s.lastIndexOf(".");
431
- if (separator === -1)
432
- return sanitize(s);
433
- return sanitize(s.substring(0, separator)) + "." + sanitize(s.substring(separator + 1));
434
- }
435
457
  // Annotate the CommonJS export names for ESM import in node:
436
458
  0 && (module.exports = {
437
459
  commaSeparatedList,
@@ -446,5 +468,7 @@ function sanitizeForFilePath(s) {
446
468
  resolutionParser,
447
469
  resolveCLIConfig,
448
470
  resolveConfig,
449
- semicolonSeparatedList
471
+ semicolonSeparatedList,
472
+ workspaceDir,
473
+ workspaceFile
450
474
  });
@@ -31,11 +31,11 @@ __export(context_exports, {
31
31
  Context: () => Context
32
32
  });
33
33
  module.exports = __toCommonJS(context_exports);
34
+ var import_os = __toESM(require("os"));
34
35
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
35
36
  var import_utils = require("playwright-core/lib/utils");
36
37
  var import_playwright_core = require("playwright-core");
37
38
  var import_url = require("url");
38
- var import_os = __toESM(require("os"));
39
39
  var import_log = require("../log");
40
40
  var import_tab = require("./tab");
41
41
  var import_config = require("./config");
@@ -43,6 +43,7 @@ const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
43
43
  class Context {
44
44
  constructor(options) {
45
45
  this._tabs = [];
46
+ this._routes = [];
46
47
  this._abortController = new AbortController();
47
48
  this.config = options.config;
48
49
  this.sessionLog = options.sessionLog;
@@ -97,8 +98,39 @@ class Context {
97
98
  await tab.page.close();
98
99
  return url;
99
100
  }
100
- async outputFile(fileName, options) {
101
- return (0, import_config.outputFile)(this.config, this._clientInfo, fileName, options);
101
+ async workspaceFile(fileName, perCallWorkspaceDir) {
102
+ return await (0, import_config.workspaceFile)(this.config, this._clientInfo, fileName, perCallWorkspaceDir);
103
+ }
104
+ async outputFile(template, options) {
105
+ const baseName = template.suggestedFilename || `${template.prefix}-${(template.date ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}${template.ext ? "." + template.ext : ""}`;
106
+ return await (0, import_config.outputFile)(this.config, this._clientInfo, baseName, options);
107
+ }
108
+ async startVideoRecording(params) {
109
+ if (this._video)
110
+ throw new Error("Video recording has already been started.");
111
+ const listener = (page) => {
112
+ this._video?.allVideos.add(page.video());
113
+ page.video().start(params).catch(() => {
114
+ });
115
+ };
116
+ this._video = { allVideos: /* @__PURE__ */ new Set(), listener };
117
+ const browserContext = await this.ensureBrowserContext();
118
+ browserContext.pages().forEach(listener);
119
+ browserContext.on("page", listener);
120
+ }
121
+ async stopVideoRecording() {
122
+ if (!this._video)
123
+ throw new Error("Video recording has not been started.");
124
+ const video = this._video;
125
+ if (this._browserContextPromise) {
126
+ const { browserContext } = await this._browserContextPromise;
127
+ browserContext.off("page", video.listener);
128
+ for (const page of browserContext.pages())
129
+ await page.video().stop().catch(() => {
130
+ });
131
+ }
132
+ this._video = void 0;
133
+ return video.allVideos;
102
134
  }
103
135
  _onPageCreated(page) {
104
136
  const tab = new import_tab.Tab(this, page, (tab2) => this._onPageClosed(tab2));
@@ -122,6 +154,33 @@ class Context {
122
154
  await this._closeBrowserContextPromise;
123
155
  this._closeBrowserContextPromise = void 0;
124
156
  }
157
+ routes() {
158
+ return this._routes;
159
+ }
160
+ async addRoute(entry) {
161
+ const { browserContext } = await this._ensureBrowserContext();
162
+ await browserContext.route(entry.pattern, entry.handler);
163
+ this._routes.push(entry);
164
+ }
165
+ async removeRoute(pattern) {
166
+ if (!this._browserContextPromise)
167
+ return 0;
168
+ const { browserContext } = await this._browserContextPromise;
169
+ let removed = 0;
170
+ if (pattern) {
171
+ const toRemove = this._routes.filter((r) => r.pattern === pattern);
172
+ for (const route of toRemove)
173
+ await browserContext.unroute(route.pattern, route.handler);
174
+ this._routes = this._routes.filter((r) => r.pattern !== pattern);
175
+ removed = toRemove.length;
176
+ } else {
177
+ for (const route of this._routes)
178
+ await browserContext.unroute(route.pattern, route.handler);
179
+ removed = this._routes.length;
180
+ this._routes = [];
181
+ }
182
+ return removed;
183
+ }
125
184
  isRunningTool() {
126
185
  return this._runningToolName !== void 0;
127
186
  }
@@ -186,7 +245,6 @@ class Context {
186
245
  for (const page of browserContext.pages())
187
246
  this._onPageCreated(page);
188
247
  browserContext.on("page", (page) => this._onPageCreated(page));
189
- browserContext.on("close", () => this.onBrowserContextClosed?.());
190
248
  if (this.config.saveTrace) {
191
249
  await browserContext.tracing.start({
192
250
  name: "trace-" + Date.now(),
@@ -229,6 +287,9 @@ function allRootPaths(clientInfo) {
229
287
  return paths;
230
288
  }
231
289
  function originOrHostGlob(originOrHost) {
290
+ const wildcardPortMatch = originOrHost.match(/^(https?:\/\/[^/:]+):\*$/);
291
+ if (wildcardPortMatch)
292
+ return `${wildcardPortMatch[1]}:*/**`;
232
293
  try {
233
294
  const url = new URL(originOrHost);
234
295
  if (url.origin !== "null")
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var logFile_exports = {};
30
+ __export(logFile_exports, {
31
+ LogFile: () => LogFile
32
+ });
33
+ module.exports = __toCommonJS(logFile_exports);
34
+ var import_fs = __toESM(require("fs"));
35
+ var import_path = __toESM(require("path"));
36
+ var import_log = require("../log");
37
+ class LogFile {
38
+ constructor(context, startTime, filePrefix, title) {
39
+ this._stopped = false;
40
+ this._line = 0;
41
+ this._entries = 0;
42
+ this._lastLine = 0;
43
+ this._lastEntries = 0;
44
+ this._writeChain = Promise.resolve();
45
+ this._context = context;
46
+ this._startTime = startTime;
47
+ this._filePrefix = filePrefix;
48
+ this._title = title;
49
+ }
50
+ appendLine(wallTime, text) {
51
+ this._writeChain = this._writeChain.then(() => this._write(wallTime, text)).catch(import_log.logUnhandledError);
52
+ }
53
+ stop() {
54
+ this._stopped = true;
55
+ }
56
+ async take(relativeTo) {
57
+ const logChunk = await this._take();
58
+ if (!logChunk)
59
+ return void 0;
60
+ const logFilePath = relativeTo ? import_path.default.relative(relativeTo, logChunk.file) : logChunk.file;
61
+ const lineRange = logChunk.fromLine === logChunk.toLine ? `#L${logChunk.fromLine}` : `#L${logChunk.fromLine}-L${logChunk.toLine}`;
62
+ return `${logFilePath}${lineRange}`;
63
+ }
64
+ async _take() {
65
+ await this._writeChain;
66
+ if (!this._file || this._entries === this._lastEntries)
67
+ return void 0;
68
+ const chunk = {
69
+ type: this._title.toLowerCase(),
70
+ file: this._file,
71
+ fromLine: this._lastLine + 1,
72
+ toLine: this._line,
73
+ entryCount: this._entries - this._lastEntries
74
+ };
75
+ this._lastLine = this._line;
76
+ this._lastEntries = this._entries;
77
+ return chunk;
78
+ }
79
+ async _write(wallTime, text) {
80
+ if (this._stopped)
81
+ return;
82
+ this._file ??= await this._context.outputFile({ prefix: this._filePrefix, ext: "log", date: new Date(this._startTime) }, { origin: "code" });
83
+ const relativeTime = Math.round(wallTime - this._startTime);
84
+ const renderedText = await text();
85
+ const logLine = `[${String(relativeTime).padStart(8, " ")}ms] ${renderedText}
86
+ `;
87
+ await import_fs.default.promises.appendFile(this._file, logLine);
88
+ const lineCount = logLine.split("\n").length - 1;
89
+ this._line += lineCount;
90
+ this._entries++;
91
+ }
92
+ }
93
+ // Annotate the CommonJS export names for ESM import in node:
94
+ 0 && (module.exports = {
95
+ LogFile
96
+ });