@jsenv/snapshot 2.6.6 → 2.6.8

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 (26) hide show
  1. package/package.json +7 -5
  2. package/src/filesystem_well_known_values.js +18 -26
  3. package/src/get_caller_location.js +22 -0
  4. package/src/main.js +4 -1
  5. package/src/replace_fluctuating_values.js +1 -1
  6. package/src/side_effects/capture_side_effects.js +6 -0
  7. package/src/side_effects/create_capture_side_effects.js +305 -0
  8. package/src/side_effects/filesystem/filesystem_side_effects.js +323 -0
  9. package/src/side_effects/filesystem/group_file_side_effects_per_directory.js +30 -0
  10. package/src/{function_side_effects → side_effects/filesystem}/spy_filesystem_calls.js +48 -25
  11. package/src/side_effects/log/group_log_side_effects.js +29 -0
  12. package/src/side_effects/log/log_side_effects.js +156 -0
  13. package/src/side_effects/render_logs_gif.js +18 -0
  14. package/src/side_effects/render_side_effects.js +435 -0
  15. package/src/side_effects/snapshot_side_effects.js +43 -0
  16. package/src/side_effects/snapshot_tests.js +115 -0
  17. package/src/side_effects/utils/group_side_effects.js +89 -0
  18. package/src/function_side_effects/function_side_effects_collector.js +0 -160
  19. package/src/function_side_effects/function_side_effects_renderer.js +0 -29
  20. package/src/function_side_effects/function_side_effects_snapshot.js +0 -302
  21. package/src/function_side_effects/group_file_side_effects_per_directory.js +0 -114
  22. package/src/function_side_effects/spy_console_calls.js +0 -89
  23. package/src/snapshot_scenarios.js +0 -32
  24. /package/src/{function_side_effects → side_effects/filesystem}/common_ancestor_path.js +0 -0
  25. /package/src/{function_side_effects → side_effects/filesystem}/common_ancestor_path.test.mjs +0 -0
  26. /package/src/{function_side_effects → side_effects}/hook_into_method.js +0 -0
@@ -0,0 +1,323 @@
1
+ import { setUrlBasename, urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
2
+ import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
3
+ import { pathToFileURL } from "node:url";
4
+ import { createWellKnown } from "../../filesystem_well_known_values.js";
5
+ import { renderFileContent } from "../render_side_effects.js";
6
+ import { groupFileSideEffectsPerDirectory } from "./group_file_side_effects_per_directory.js";
7
+ import { spyFilesystemCalls } from "./spy_filesystem_calls.js";
8
+
9
+ const filesystemSideEffectsOptionsDefault = {
10
+ include: null,
11
+ preserve: false,
12
+ baseDirectory: "",
13
+ textualFilesIntoDirectory: false,
14
+ };
15
+ const INLINE_MAX_LINES = 20;
16
+ const INLINE_MAX_LENGTH = 2000;
17
+
18
+ export const filesystemSideEffects = (
19
+ filesystemSideEffectsOptions,
20
+ { replaceFilesystemWellKnownValues },
21
+ ) => {
22
+ filesystemSideEffectsOptions = {
23
+ ...filesystemSideEffectsOptionsDefault,
24
+ ...filesystemSideEffectsOptions,
25
+ };
26
+ let baseDirectory;
27
+ let removeBaseDirectoryWellKnown = () => {};
28
+ const setBaseDirectory = (value) => {
29
+ removeBaseDirectoryWellKnown();
30
+ baseDirectory = value;
31
+ if (baseDirectory) {
32
+ removeBaseDirectoryWellKnown =
33
+ replaceFilesystemWellKnownValues.addWellKnownFileUrl(
34
+ baseDirectory,
35
+ createWellKnown("base"),
36
+ { position: "start" },
37
+ );
38
+ }
39
+ };
40
+
41
+ return {
42
+ name: "filesystem",
43
+ setBaseDirectory,
44
+ install: (addSideEffect, { addSkippableHandler, addFinallyCallback }) => {
45
+ let { include, preserve, textualFilesIntoDirectory } =
46
+ filesystemSideEffectsOptions;
47
+ if (filesystemSideEffectsOptions.baseDirectory) {
48
+ setBaseDirectory(filesystemSideEffectsOptions.baseDirectory);
49
+ }
50
+ const getUrlRelativeToBase = (url) => {
51
+ if (baseDirectory) {
52
+ return urlToRelativeUrl(url, baseDirectory, {
53
+ preferRelativeNotation: true,
54
+ });
55
+ }
56
+ return url;
57
+ };
58
+ const getUrlInsideOutDirectory = (url, outDirectoryUrl) => {
59
+ if (baseDirectory) {
60
+ if (
61
+ url.href === baseDirectory.href ||
62
+ urlIsInsideOf(url, baseDirectory)
63
+ ) {
64
+ const outRelativeUrl = urlToRelativeUrl(url, baseDirectory);
65
+ return new URL(outRelativeUrl, outDirectoryUrl);
66
+ }
67
+ }
68
+ // otherwise we replace the url with well known
69
+ const toRelativeUrl = replaceFilesystemWellKnownValues(url);
70
+ return new URL(toRelativeUrl, outDirectoryUrl);
71
+ };
72
+
73
+ addSkippableHandler((sideEffect) => {
74
+ // if directory ends up with something inside we'll not report
75
+ // this side effect because:
76
+ // - it was likely created to write the file
77
+ // - the file creation will be reported and implies directory creation
78
+ if (sideEffect.code === "write_directory") {
79
+ return (nextSideEffect, { skip, stop }) => {
80
+ if (
81
+ (nextSideEffect.code === "write_file" ||
82
+ nextSideEffect.code === "write_directory") &&
83
+ urlIsInsideOf(nextSideEffect.value.url, sideEffect.value.url)
84
+ ) {
85
+ skip();
86
+ stop();
87
+ return;
88
+ }
89
+ if (
90
+ nextSideEffect.code === "remove_directory" &&
91
+ nextSideEffect.value.url === sideEffect.value.url
92
+ ) {
93
+ stop();
94
+ return;
95
+ }
96
+ };
97
+ }
98
+ return null;
99
+ });
100
+ addSkippableHandler((sideEffect) => {
101
+ // on llinux writing to a file with the same content
102
+ // is ignored somehow
103
+ // let's make this consistent on other platforms
104
+ if (sideEffect.code === "write_file") {
105
+ return (nextSideEffect, { skip, stop }) => {
106
+ if (
107
+ nextSideEffect.code === "write_file" &&
108
+ nextSideEffect.value.url === sideEffect.value.url &&
109
+ !Buffer.compare(
110
+ nextSideEffect.value.buffer,
111
+ sideEffect.value.buffer,
112
+ )
113
+ ) {
114
+ skip();
115
+ }
116
+ stop();
117
+ };
118
+ }
119
+ return null;
120
+ });
121
+
122
+ addFinallyCallback((sideEffects) => {
123
+ // gather all file side effect next to each other
124
+ // collapse them if they have a shared ancestor
125
+ groupFileSideEffectsPerDirectory(sideEffects, {
126
+ createWriteFileGroupSideEffect: (fileSideEffectArray, commonPath) => {
127
+ let commonUrl = pathToFileURL(commonPath);
128
+ let commonDirectoryUrl;
129
+ if (commonUrl.href.endsWith("/")) {
130
+ commonDirectoryUrl = commonUrl;
131
+ } else {
132
+ commonDirectoryUrl = new URL("./", commonUrl);
133
+ }
134
+
135
+ return {
136
+ code: "write_file_group",
137
+ type: `write_file_group ${commonDirectoryUrl}`,
138
+ value: {},
139
+ render: {
140
+ md: (options) => {
141
+ let allFilesInsideOutDirectory = true;
142
+ let groupMd = "";
143
+ let numberOfFiles = 0;
144
+ for (const fileSideEffect of fileSideEffectArray) {
145
+ numberOfFiles++;
146
+ if (groupMd) {
147
+ groupMd += "\n\n";
148
+ }
149
+ const { url, outDirectoryReason } = fileSideEffect.value;
150
+ const { text } = fileSideEffect.render.md(options);
151
+ const urlRelativeToCommonDirectory = urlToRelativeUrl(
152
+ url,
153
+ commonDirectoryUrl,
154
+ );
155
+ if (outDirectoryReason) {
156
+ const outUrlRelativeToCommonDirectory = urlToRelativeUrl(
157
+ text.urlInsideOutDirectory,
158
+ options.sideEffectFileUrl,
159
+ );
160
+ groupMd += `${"#".repeat(2)} ${urlRelativeToCommonDirectory}
161
+ ${renderFileContent(
162
+ {
163
+ ...text,
164
+ relativeUrl: urlRelativeToCommonDirectory,
165
+ outRelativeUrl: outUrlRelativeToCommonDirectory,
166
+ },
167
+ {
168
+ ...options,
169
+ sideEffect: fileSideEffect,
170
+ },
171
+ )}`;
172
+ continue;
173
+ }
174
+ allFilesInsideOutDirectory = false;
175
+ groupMd += `${"#".repeat(2)} ${urlRelativeToCommonDirectory}
176
+ ${renderFileContent(
177
+ {
178
+ ...text,
179
+ relativeUrl: urlRelativeToCommonDirectory,
180
+ },
181
+ {
182
+ ...options,
183
+ sideEffect: fileSideEffect,
184
+ },
185
+ )}`;
186
+ }
187
+ const commonDirectoryRelativeUrl =
188
+ getUrlRelativeToBase(commonDirectoryUrl);
189
+ if (allFilesInsideOutDirectory) {
190
+ const commonDirectoryOutUrl = getUrlInsideOutDirectory(
191
+ commonDirectoryUrl,
192
+ options.outDirectoryUrl,
193
+ );
194
+ const commonDirectoryOutRelativeUrl = urlToRelativeUrl(
195
+ commonDirectoryOutUrl,
196
+ options.sideEffectFileUrl,
197
+ { preferRelativeNotation: true },
198
+ );
199
+ return {
200
+ label: `write ${numberOfFiles} files into "${commonDirectoryRelativeUrl}"`,
201
+ text: `see [${commonDirectoryOutRelativeUrl}](${commonDirectoryOutRelativeUrl})`,
202
+ };
203
+ }
204
+ return {
205
+ label: `write ${numberOfFiles} files into "${commonDirectoryRelativeUrl}"`,
206
+ text: groupMd,
207
+ };
208
+ },
209
+ },
210
+ };
211
+ },
212
+ });
213
+ });
214
+
215
+ const filesystemSpy = spyFilesystemCalls(
216
+ {
217
+ onWriteFile: (url, buffer) => {
218
+ const contentType = CONTENT_TYPE.fromUrlExtension(url);
219
+ const isTextual = CONTENT_TYPE.isTextual(contentType);
220
+ let outDirectoryReason;
221
+ if (isTextual) {
222
+ if (textualFilesIntoDirectory) {
223
+ outDirectoryReason = "textual_in_directory_option";
224
+ } else if (String(buffer).split("\n").length > INLINE_MAX_LINES) {
225
+ outDirectoryReason = "lot_of_lines";
226
+ } else if (buffer.size > INLINE_MAX_LENGTH) {
227
+ outDirectoryReason = "lot_of_chars";
228
+ }
229
+ } else {
230
+ outDirectoryReason = "binary";
231
+ }
232
+ const writeFileSideEffect = {
233
+ code: "write_file",
234
+ type: `write_file:${url}`,
235
+ value: {
236
+ url,
237
+ buffer,
238
+ contentType,
239
+ isTextual,
240
+ outDirectoryReason,
241
+ },
242
+ render: {
243
+ md: ({ sideEffectFileUrl, outDirectoryUrl }) => {
244
+ const urlRelativeToBase = getUrlRelativeToBase(url);
245
+ if (outDirectoryReason) {
246
+ const urlInsideOutDirectory = getUrlInsideOutDirectory(
247
+ url,
248
+ outDirectoryUrl,
249
+ );
250
+ if (writeFileSideEffect.counter) {
251
+ setUrlBasename(
252
+ urlInsideOutDirectory,
253
+ (basename) =>
254
+ `${basename}_${writeFileSideEffect.counter}`,
255
+ );
256
+ }
257
+ let textValue;
258
+ if (outDirectoryReason === "lot_of_chars") {
259
+ textValue = String(buffer.slice(0, INLINE_MAX_LENGTH));
260
+ } else if (outDirectoryReason === "lot_of_lines") {
261
+ textValue = String(buffer)
262
+ .split("\n")
263
+ .slice(0, INLINE_MAX_LINES)
264
+ .join("\n");
265
+ } else {
266
+ textValue = buffer;
267
+ }
268
+ const outRelativeUrl = urlToRelativeUrl(
269
+ urlInsideOutDirectory,
270
+ sideEffectFileUrl,
271
+ {
272
+ preferRelativeNotation: true,
273
+ },
274
+ );
275
+ return {
276
+ label: `write file "${urlRelativeToBase}"`,
277
+ text: {
278
+ type: "file_content",
279
+ value: textValue,
280
+ relativeUrl: urlRelativeToBase,
281
+ outRelativeUrl,
282
+ urlInsideOutDirectory,
283
+ },
284
+ };
285
+ }
286
+ return {
287
+ label: `write file "${urlRelativeToBase}"`,
288
+ text: {
289
+ type: "file_content",
290
+ value: String(buffer),
291
+ },
292
+ };
293
+ },
294
+ },
295
+ };
296
+ addSideEffect(writeFileSideEffect);
297
+ },
298
+ onWriteDirectory: (url) => {
299
+ addSideEffect({
300
+ code: "write_directory",
301
+ type: `write_directory:${url}`,
302
+ value: { url },
303
+ render: {
304
+ md: () => {
305
+ return {
306
+ label: `write directory "${getUrlRelativeToBase(url)}"`,
307
+ };
308
+ },
309
+ },
310
+ });
311
+ },
312
+ },
313
+ {
314
+ include,
315
+ undoFilesystemSideEffects: !preserve,
316
+ },
317
+ );
318
+ return () => {
319
+ filesystemSpy.restore();
320
+ };
321
+ },
322
+ };
323
+ };
@@ -0,0 +1,30 @@
1
+ import { groupSideEffectsPer, IGNORE } from "../utils/group_side_effects.js";
2
+ import { findCommonAncestorPath } from "./common_ancestor_path.js";
3
+
4
+ export const groupFileSideEffectsPerDirectory = (
5
+ allSideEffects,
6
+ { createWriteFileGroupSideEffect },
7
+ ) => {
8
+ return groupSideEffectsPer(
9
+ allSideEffects,
10
+ (sideEffect) => {
11
+ if (sideEffect.code === "write_directory") {
12
+ return IGNORE;
13
+ }
14
+ return sideEffect.code === "write_file";
15
+ },
16
+ {
17
+ createGroupSideEffect: (sideEffects) => {
18
+ const commonAncestorPath = findCommonAncestorPath(
19
+ sideEffects,
20
+ convertToPathname,
21
+ );
22
+ return createWriteFileGroupSideEffect(sideEffects, commonAncestorPath);
23
+ },
24
+ },
25
+ );
26
+ };
27
+
28
+ const convertToPathname = (writeFileSideEffect) => {
29
+ return new URL(writeFileSideEffect.value.url).pathname;
30
+ };
@@ -9,20 +9,21 @@ import {
9
9
  writeFileSync,
10
10
  } from "@jsenv/filesystem";
11
11
  import { URL_META } from "@jsenv/url-meta";
12
+ import { yieldAncestorUrls } from "@jsenv/urls";
12
13
  import { readFileSync, statSync } from "node:fs";
13
14
  import { pathToFileURL } from "node:url";
14
15
  import {
15
16
  disableHooksWhileCalling,
16
17
  hookIntoMethod,
17
18
  METHOD_EXECUTION_NODE_CALLBACK,
18
- } from "./hook_into_method.js";
19
+ } from "../hook_into_method.js";
19
20
 
20
21
  export const spyFilesystemCalls = (
21
22
  {
22
- readFile = () => {}, // TODO
23
- writeFile = () => {},
24
- writeDirectory = () => {},
25
- removeFile = () => {}, // TODO
23
+ onReadFile = () => {}, // TODO
24
+ onWriteFile = () => {},
25
+ onWriteDirectory = () => {},
26
+ onRemoveFile = () => {}, // TODO
26
27
  // removeDirectory = () => {},
27
28
  },
28
29
  { include, undoFilesystemSideEffects } = {},
@@ -40,15 +41,19 @@ export const spyFilesystemCalls = (
40
41
  // - writing file for the 1st time
41
42
  // - updating file content
42
43
  // the important part is the file content in the end of the function execution
43
- if (
44
- (!stateBefore.found && stateAfter.found) ||
45
- stateBefore.content !== stateAfter.content ||
46
- stateBefore.mtimeMs !== stateAfter.mtimeMs
47
- ) {
44
+ const reason =
45
+ !stateBefore.found && stateAfter.found
46
+ ? "created"
47
+ : Buffer.compare(stateBefore.buffer, stateAfter.buffer)
48
+ ? "content_modified"
49
+ : stateBefore.mtimeMs === stateAfter.mtimeMs
50
+ ? ""
51
+ : "mtime_modified";
52
+ if (reason) {
48
53
  if (undoFilesystemSideEffects && !fileRestoreMap.has(fileUrl)) {
49
54
  if (stateBefore.found) {
50
55
  fileRestoreMap.set(fileUrl, () => {
51
- writeFileSync(fileUrl, stateBefore.content);
56
+ writeFileSync(fileUrl, stateBefore.buffer);
52
57
  });
53
58
  } else {
54
59
  fileRestoreMap.set(fileUrl, () => {
@@ -57,17 +62,14 @@ export const spyFilesystemCalls = (
57
62
  }
58
63
  }
59
64
  if (shouldReport(fileUrl)) {
60
- writeFile(fileUrl, stateAfter.content);
65
+ onWriteFile(fileUrl, stateAfter.buffer, reason);
61
66
  return;
62
67
  }
63
68
  }
64
69
  // file is exactly the same
65
70
  // function did not have any effect on the file
66
71
  };
67
- const onWriteDirectoryDone = (directoryUrl, stateBefore) => {
68
- if (stateBefore.found) {
69
- return;
70
- }
72
+ const onWriteDirectoryDone = (directoryUrl) => {
71
73
  if (undoFilesystemSideEffects && !fileRestoreMap.has(directoryUrl)) {
72
74
  fileRestoreMap.set(directoryUrl, () => {
73
75
  removeDirectorySync(directoryUrl, {
@@ -77,7 +79,7 @@ export const spyFilesystemCalls = (
77
79
  });
78
80
  }
79
81
  if (shouldReport(directoryUrl)) {
80
- writeDirectory(directoryUrl);
82
+ onWriteDirectory(directoryUrl);
81
83
  }
82
84
  };
83
85
  const beforeUndoCallbackSet = new Set();
@@ -93,13 +95,34 @@ export const spyFilesystemCalls = (
93
95
  const mkdirHook = hookIntoMethod(
94
96
  _internalFs,
95
97
  "mkdir",
96
- (directoryPath) => {
98
+ (directoryPath, mode, recursive) => {
97
99
  const directoryUrl = pathToFileURL(directoryPath);
98
100
  const stateBefore = getDirectoryState(directoryPath);
101
+ if (!stateBefore.found && recursive) {
102
+ const ancestorNotFoundArray = [];
103
+ for (const ancestorUrl of yieldAncestorUrls(directoryUrl)) {
104
+ const ancestorState = getDirectoryState(new URL(ancestorUrl));
105
+ if (ancestorState.found) {
106
+ break;
107
+ }
108
+ ancestorNotFoundArray.unshift(ancestorUrl);
109
+ }
110
+ return {
111
+ return: (fd) => {
112
+ fileDescriptorPathMap.set(fd, directoryPath);
113
+ for (const ancestorNotFoundUrl of ancestorNotFoundArray) {
114
+ onWriteDirectoryDone(ancestorNotFoundUrl);
115
+ }
116
+ onWriteDirectoryDone(String(directoryUrl));
117
+ },
118
+ };
119
+ }
99
120
  return {
100
121
  return: (fd) => {
101
122
  fileDescriptorPathMap.set(fd, directoryPath);
102
- onWriteDirectoryDone(directoryUrl, stateBefore, { found: true });
123
+ if (!stateBefore.found) {
124
+ onWriteDirectoryDone(String(directoryUrl));
125
+ }
103
126
  },
104
127
  };
105
128
  },
@@ -140,12 +163,12 @@ export const spyFilesystemCalls = (
140
163
  }
141
164
  const fileUrl = pathToFileURL(filePath);
142
165
  if (buffer) {
143
- readFile(fileUrl);
166
+ onReadFile(String(fileUrl));
144
167
  }
145
168
  fileDescriptorPathMap.delete(fileDescriptor);
146
169
  filesystemStateInfoMap.delete(filePath);
147
170
  const stateAfter = getFileStateWithinHook(fileUrl);
148
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
171
+ onWriteFileDone(String(fileUrl), stateBefore, stateAfter);
149
172
  },
150
173
  };
151
174
  },
@@ -160,7 +183,7 @@ export const spyFilesystemCalls = (
160
183
  return {
161
184
  return: () => {
162
185
  const stateAfter = getFileStateWithinHook(fileUrl);
163
- onWriteFileDone(fileUrl, stateBefore, stateAfter);
186
+ onWriteFileDone(String(fileUrl), stateBefore, stateAfter);
164
187
  },
165
188
  };
166
189
  },
@@ -168,7 +191,7 @@ export const spyFilesystemCalls = (
168
191
  const unlinkHook = hookIntoMethod(_internalFs, "unlink", (filePath) => {
169
192
  return {
170
193
  return: () => {
171
- removeFile(filePath); // TODO eventually split in removeFile/removeDirectory
194
+ onRemoveFile(filePath); // TODO eventually split in removeFile/removeDirectory
172
195
  },
173
196
  };
174
197
  });
@@ -202,12 +225,12 @@ export const spyFilesystemCalls = (
202
225
 
203
226
  const getFileState = (file) => {
204
227
  try {
205
- const fileContent = readFileSync(file);
228
+ const fileBuffer = readFileSync(file);
206
229
  const { mtimeMs } = statSync(file);
207
230
  return {
208
231
  found: true,
209
232
  mtimeMs,
210
- content: String(fileContent),
233
+ buffer: fileBuffer,
211
234
  };
212
235
  } catch (e) {
213
236
  if (e.code === "ENOENT") {
@@ -0,0 +1,29 @@
1
+ import { groupSideEffectsPer } from "../utils/group_side_effects.js";
2
+
3
+ export const groupLogSideEffects = (
4
+ allSideEffects,
5
+ { createLogGroupSideEffect },
6
+ ) => {
7
+ return groupSideEffectsPer(
8
+ allSideEffects,
9
+ (sideEffect) => {
10
+ if (
11
+ sideEffect.code === "console.trace" ||
12
+ sideEffect.code === "console.log" ||
13
+ sideEffect.code === "console.info" ||
14
+ sideEffect.code === "console.warn" ||
15
+ sideEffect.code === "console.error" ||
16
+ sideEffect.code === "process.stdout" ||
17
+ sideEffect.code === "process.stderr"
18
+ ) {
19
+ return true;
20
+ }
21
+ return false;
22
+ },
23
+ {
24
+ createGroupSideEffect: (sideEffects) => {
25
+ return createLogGroupSideEffect(sideEffects);
26
+ },
27
+ },
28
+ );
29
+ };
@@ -0,0 +1,156 @@
1
+ import { hookIntoMethod } from "../hook_into_method.js";
2
+ import { renderConsole } from "../render_side_effects.js";
3
+ import { groupLogSideEffects } from "./group_log_side_effects.js";
4
+
5
+ const logSideEffectsOptionsDefault = {
6
+ prevent: true,
7
+ group: false,
8
+ };
9
+
10
+ export const logSideEffects = (logSideEffectsOptions) => {
11
+ logSideEffectsOptions = {
12
+ ...logSideEffectsOptionsDefault,
13
+ ...logSideEffectsOptions,
14
+ };
15
+ return {
16
+ name: "console",
17
+ install: (addSideEffect, { addFinallyCallback }) => {
18
+ const { prevent, group } = logSideEffectsOptions;
19
+ if (group) {
20
+ addFinallyCallback((sideEffects) => {
21
+ groupLogSideEffects(sideEffects, {
22
+ createLogGroupSideEffect: (logSideEffectArray) => {
23
+ const logGroupSideEffect = {
24
+ code: "log_group",
25
+ type: `log_group`,
26
+ value: {},
27
+ render: {
28
+ md: (options) => {
29
+ const renderLogGroup = () => {
30
+ let logs = "";
31
+ let i = 0;
32
+ while (i < logSideEffectArray.length) {
33
+ const logSideEffect = logSideEffectArray[i];
34
+ i++;
35
+ const { text } = logSideEffect.render.md(options);
36
+ logs += text.value;
37
+ if (
38
+ i !== logSideEffectArray.length &&
39
+ logSideEffect.type.startsWith("console")
40
+ ) {
41
+ logs += "\n";
42
+ }
43
+ }
44
+ const logGroupMd = renderConsole(logs, {
45
+ sideEffect: logGroupSideEffect,
46
+ ...options,
47
+ });
48
+ return logGroupMd;
49
+ };
50
+ return {
51
+ label: `logs`,
52
+ text: renderLogGroup(),
53
+ };
54
+ },
55
+ },
56
+ };
57
+ return logGroupSideEffect;
58
+ },
59
+ });
60
+ });
61
+ }
62
+ const addLogSideEffect = (type, message) => {
63
+ addSideEffect({
64
+ code: type,
65
+ type,
66
+ value: message,
67
+ render: {
68
+ md: () => {
69
+ return {
70
+ label: type,
71
+ text: {
72
+ type: "console",
73
+ value: message,
74
+ },
75
+ };
76
+ },
77
+ },
78
+ });
79
+ };
80
+
81
+ const errorHook = hookIntoMethod(console, "error", (message) => {
82
+ return {
83
+ preventOriginalCall: prevent,
84
+ return: () => {
85
+ addLogSideEffect("console.error", message);
86
+ },
87
+ };
88
+ });
89
+ const warnHook = hookIntoMethod(console, "warn", (message) => {
90
+ return {
91
+ preventOriginalCall: prevent,
92
+ return: () => {
93
+ addLogSideEffect("console.warn", message);
94
+ },
95
+ };
96
+ });
97
+ const infoHook = hookIntoMethod(console, "info", (message) => {
98
+ return {
99
+ preventOriginalCall: prevent,
100
+ return: () => {
101
+ addLogSideEffect("console.info", message);
102
+ },
103
+ };
104
+ });
105
+ const logHook = hookIntoMethod(console, "log", (message) => {
106
+ return {
107
+ preventOriginalCall: prevent,
108
+ return: () => {
109
+ addLogSideEffect("console.log", message);
110
+ },
111
+ };
112
+ });
113
+ const traceHook = hookIntoMethod(console, "trace", (message) => {
114
+ return {
115
+ preventOriginalCall: prevent,
116
+ return: () => {
117
+ addLogSideEffect("console.trace", message);
118
+ },
119
+ };
120
+ });
121
+ const processStdouthook = hookIntoMethod(
122
+ process.stdout,
123
+ "write",
124
+ (message) => {
125
+ return {
126
+ preventOriginalCall: prevent,
127
+ return: () => {
128
+ addLogSideEffect("process.stdout", message);
129
+ },
130
+ };
131
+ },
132
+ );
133
+ const processStderrHhook = hookIntoMethod(
134
+ process.stderr,
135
+ "write",
136
+ (message) => {
137
+ return {
138
+ preventOriginalCall: prevent,
139
+ return: () => {
140
+ addLogSideEffect("process.stderr", message);
141
+ },
142
+ };
143
+ },
144
+ );
145
+ return () => {
146
+ errorHook.remove();
147
+ warnHook.remove();
148
+ infoHook.remove();
149
+ logHook.remove();
150
+ traceHook.remove();
151
+ processStdouthook.remove();
152
+ processStderrHhook.remove();
153
+ };
154
+ },
155
+ };
156
+ };