@jsenv/snapshot 2.2.1 → 2.2.2
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/package.json +1 -1
- package/src/function_snapshot.js +113 -74
- package/src/replace_fluctuating_values.js +34 -28
package/package.json
CHANGED
package/src/function_snapshot.js
CHANGED
|
@@ -1,111 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
moveEntrySync,
|
|
3
|
-
writeDirectorySync,
|
|
4
|
-
writeFileSync,
|
|
5
|
-
} from "@jsenv/filesystem";
|
|
1
|
+
import { readEntryStatSync, readFileSync } from "@jsenv/filesystem";
|
|
6
2
|
import { urlToRelativeUrl } from "@jsenv/urls/src/url_to_relative_url.js";
|
|
7
|
-
import {
|
|
3
|
+
import { takeFileSnapshot } from "./filesystem_snapshot.js";
|
|
8
4
|
import { replaceFluctuatingValues } from "./replace_fluctuating_values.js";
|
|
9
5
|
|
|
10
6
|
export const snapshotFunctionSideEffects = (
|
|
11
7
|
fn,
|
|
12
8
|
fnFileUrl,
|
|
9
|
+
sideEffectFileRelativeUrl,
|
|
13
10
|
{
|
|
14
|
-
sideEffectDirectoryName = "output",
|
|
15
11
|
rootDirectoryUrl = new URL("./", fnFileUrl),
|
|
16
12
|
captureConsole = true,
|
|
17
13
|
filesystemEffects,
|
|
18
14
|
} = {},
|
|
19
15
|
) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
sideEffectDirectoryUrl,
|
|
24
|
-
);
|
|
16
|
+
const sideEffectFileUrl = new URL(sideEffectFileRelativeUrl, fnFileUrl);
|
|
17
|
+
const sideEffectFileSnapshot = takeFileSnapshot(sideEffectFileUrl);
|
|
18
|
+
const sideEffects = [];
|
|
25
19
|
const finallyCallbackSet = new Set();
|
|
26
|
-
const errorFileUrl = new URL("./error.txt", sideEffectDirectoryUrl);
|
|
27
|
-
const resultFileUrl = new URL("./result.json", sideEffectDirectoryUrl);
|
|
28
20
|
const onError = (e) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
21
|
+
sideEffects.push({
|
|
22
|
+
type: "throw",
|
|
23
|
+
value: e,
|
|
24
|
+
});
|
|
35
25
|
};
|
|
36
26
|
const onResult = (result) => {
|
|
37
|
-
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
resultFileUrl,
|
|
42
|
-
replaceFluctuatingValues(JSON.stringify(result, null, " "), {
|
|
43
|
-
fileUrl: resultFileUrl,
|
|
44
|
-
rootDirectoryUrl,
|
|
45
|
-
}),
|
|
46
|
-
);
|
|
27
|
+
sideEffects.push({
|
|
28
|
+
type: "return",
|
|
29
|
+
value: result,
|
|
30
|
+
});
|
|
47
31
|
};
|
|
48
32
|
const onFinally = () => {
|
|
49
33
|
for (const finallyCallback of finallyCallbackSet) {
|
|
50
34
|
finallyCallback();
|
|
51
35
|
}
|
|
52
|
-
|
|
36
|
+
sideEffectFileSnapshot.update(
|
|
37
|
+
stringifySideEffects(sideEffects, { rootDirectoryUrl }),
|
|
38
|
+
);
|
|
39
|
+
sideEffectFileSnapshot.compare();
|
|
53
40
|
};
|
|
54
41
|
if (captureConsole) {
|
|
55
|
-
const installConsoleSpy = (methodName
|
|
42
|
+
const installConsoleSpy = (methodName) => {
|
|
56
43
|
const methodSpied = console[methodName];
|
|
57
|
-
let output = "";
|
|
58
44
|
console[methodName] = (message) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
sideEffects.push({
|
|
46
|
+
type: `console.${methodName}`,
|
|
47
|
+
value: message,
|
|
48
|
+
});
|
|
63
49
|
};
|
|
64
50
|
finallyCallbackSet.add(() => {
|
|
65
51
|
console[methodName] = methodSpied;
|
|
66
|
-
if (output) {
|
|
67
|
-
writeFileSync(
|
|
68
|
-
consoleOutputFileUrl,
|
|
69
|
-
replaceFluctuatingValues(output, {
|
|
70
|
-
fileUrl: consoleOutputFileUrl,
|
|
71
|
-
rootDirectoryUrl,
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
52
|
});
|
|
76
53
|
};
|
|
77
|
-
installConsoleSpy(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
installConsoleSpy(
|
|
82
|
-
"warn",
|
|
83
|
-
new URL("./console_warnings.txt", sideEffectDirectoryUrl),
|
|
84
|
-
);
|
|
85
|
-
installConsoleSpy(
|
|
86
|
-
"info",
|
|
87
|
-
new URL("./console_infos.txt", sideEffectDirectoryUrl),
|
|
88
|
-
);
|
|
89
|
-
installConsoleSpy(
|
|
90
|
-
"log",
|
|
91
|
-
new URL("./console_logs.txt", sideEffectDirectoryUrl),
|
|
92
|
-
);
|
|
54
|
+
installConsoleSpy("error");
|
|
55
|
+
installConsoleSpy("warn");
|
|
56
|
+
installConsoleSpy("info");
|
|
57
|
+
installConsoleSpy("log");
|
|
93
58
|
}
|
|
94
59
|
if (filesystemEffects) {
|
|
95
|
-
const filesystemEffectDirectoryUrl = new URL(
|
|
96
|
-
"./fs/",
|
|
97
|
-
sideEffectDirectoryUrl,
|
|
98
|
-
);
|
|
99
60
|
for (const filesystemEffect of filesystemEffects) {
|
|
61
|
+
const from = new URL(filesystemEffect, fnFileUrl);
|
|
62
|
+
const relativeUrl = urlToRelativeUrl(from, fnFileUrl);
|
|
63
|
+
const atStartState = getFileState(from);
|
|
64
|
+
const onFileSystemSideEffect = (fsSideEffect) => {
|
|
65
|
+
const last = sideEffects.pop();
|
|
66
|
+
sideEffects.push(fsSideEffect);
|
|
67
|
+
sideEffects.push(last);
|
|
68
|
+
};
|
|
100
69
|
finallyCallbackSet.add(() => {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
70
|
+
const nowState = getFileState(from);
|
|
71
|
+
if (atStartState.found && !nowState.found) {
|
|
72
|
+
onFileSystemSideEffect({
|
|
73
|
+
type: `remove file "${relativeUrl}"`,
|
|
74
|
+
value: atStartState.content,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// we use same type because we don't want to differentiate between
|
|
79
|
+
// - writing file for the 1st time
|
|
80
|
+
// - updating file content
|
|
81
|
+
// the important part is the file content in the end of the function execution
|
|
82
|
+
if (
|
|
83
|
+
(!atStartState.found && nowState.found) ||
|
|
84
|
+
atStartState.content !== nowState.content ||
|
|
85
|
+
atStartState.mtimeMs !== nowState.mtimeMs
|
|
86
|
+
) {
|
|
87
|
+
onFileSystemSideEffect({
|
|
88
|
+
type: `write file "${relativeUrl}"`,
|
|
89
|
+
value: nowState.content,
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// file is exactly the same
|
|
94
|
+
// function did not have any effect on the file
|
|
109
95
|
});
|
|
110
96
|
}
|
|
111
97
|
}
|
|
@@ -136,3 +122,56 @@ export const snapshotFunctionSideEffects = (
|
|
|
136
122
|
onFinally();
|
|
137
123
|
}
|
|
138
124
|
};
|
|
125
|
+
|
|
126
|
+
const stringifySideEffects = (sideEffects, { rootDirectoryUrl }) => {
|
|
127
|
+
let string = "";
|
|
128
|
+
let index = 0;
|
|
129
|
+
for (const sideEffect of sideEffects) {
|
|
130
|
+
if (string) {
|
|
131
|
+
string += "\n\n";
|
|
132
|
+
}
|
|
133
|
+
string += `${index + 1}. ${sideEffect.type}`;
|
|
134
|
+
string += "\n";
|
|
135
|
+
let value = sideEffect.value;
|
|
136
|
+
if (sideEffect.type === "throw") {
|
|
137
|
+
value = replaceFluctuatingValues(value.stack, {
|
|
138
|
+
stringType: "error",
|
|
139
|
+
});
|
|
140
|
+
} else if (sideEffect.type === "return") {
|
|
141
|
+
value =
|
|
142
|
+
value === undefined
|
|
143
|
+
? undefined
|
|
144
|
+
: replaceFluctuatingValues(JSON.stringify(value, null, " "), {
|
|
145
|
+
stringType: "json",
|
|
146
|
+
rootDirectoryUrl,
|
|
147
|
+
});
|
|
148
|
+
} else if (sideEffect.type.startsWith("console.")) {
|
|
149
|
+
value = replaceFluctuatingValues(value, {
|
|
150
|
+
stringType: "console",
|
|
151
|
+
rootDirectoryUrl,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
string += value;
|
|
155
|
+
index++;
|
|
156
|
+
}
|
|
157
|
+
return string;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const getFileState = (fileUrl) => {
|
|
161
|
+
try {
|
|
162
|
+
const fileContent = readFileSync(fileUrl);
|
|
163
|
+
const { mtimeMs } = readEntryStatSync(fileUrl);
|
|
164
|
+
return {
|
|
165
|
+
found: true,
|
|
166
|
+
mtimeMs,
|
|
167
|
+
content: String(fileContent),
|
|
168
|
+
};
|
|
169
|
+
} catch (e) {
|
|
170
|
+
if (e.code === "ENOENT") {
|
|
171
|
+
return {
|
|
172
|
+
found: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
throw e;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
@@ -19,6 +19,7 @@ import stripAnsi from "strip-ansi";
|
|
|
19
19
|
export const replaceFluctuatingValues = (
|
|
20
20
|
string,
|
|
21
21
|
{
|
|
22
|
+
stringType,
|
|
22
23
|
fileUrl,
|
|
23
24
|
removeAnsi = true,
|
|
24
25
|
rootDirectoryUrl = pathToFileURL(process.cwd()),
|
|
@@ -60,37 +61,42 @@ export const replaceFluctuatingValues = (
|
|
|
60
61
|
return value;
|
|
61
62
|
};
|
|
62
63
|
|
|
63
|
-
if (fileUrl) {
|
|
64
|
+
if (fileUrl && stringType === undefined) {
|
|
64
65
|
const extension = urlToExtension(fileUrl);
|
|
65
|
-
if (extension === ".
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
? parseSvgString(string)
|
|
70
|
-
: parseHtml({
|
|
71
|
-
html: string,
|
|
72
|
-
storeOriginalPositions: false,
|
|
73
|
-
});
|
|
74
|
-
// for each attribute value
|
|
75
|
-
// and each text node content
|
|
76
|
-
visitHtmlNodes(htmlAst, {
|
|
77
|
-
"*": (node) => {
|
|
78
|
-
const htmlNodeText = getHtmlNodeText(node);
|
|
79
|
-
if (htmlNodeText) {
|
|
80
|
-
setHtmlNodeText(node, replaceThings(htmlNodeText));
|
|
81
|
-
}
|
|
82
|
-
const attributes = getHtmlNodeAttributes(node);
|
|
83
|
-
if (attributes) {
|
|
84
|
-
for (const name of Object.keys(attributes)) {
|
|
85
|
-
attributes[name] = replaceThings(attributes[name]);
|
|
86
|
-
}
|
|
87
|
-
setHtmlNodeAttributes(node, attributes);
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
return stringifyHtmlAst(htmlAst);
|
|
66
|
+
if (extension === ".html") {
|
|
67
|
+
stringType = "html";
|
|
68
|
+
} else if (extension === ".svg") {
|
|
69
|
+
stringType = "svg";
|
|
92
70
|
}
|
|
93
71
|
}
|
|
72
|
+
if (stringType === "html") {
|
|
73
|
+
// do parse html
|
|
74
|
+
const htmlAst =
|
|
75
|
+
stringType === "svg"
|
|
76
|
+
? parseSvgString(string)
|
|
77
|
+
: parseHtml({
|
|
78
|
+
html: string,
|
|
79
|
+
storeOriginalPositions: false,
|
|
80
|
+
});
|
|
81
|
+
// for each attribute value
|
|
82
|
+
// and each text node content
|
|
83
|
+
visitHtmlNodes(htmlAst, {
|
|
84
|
+
"*": (node) => {
|
|
85
|
+
const htmlNodeText = getHtmlNodeText(node);
|
|
86
|
+
if (htmlNodeText) {
|
|
87
|
+
setHtmlNodeText(node, replaceThings(htmlNodeText));
|
|
88
|
+
}
|
|
89
|
+
const attributes = getHtmlNodeAttributes(node);
|
|
90
|
+
if (attributes) {
|
|
91
|
+
for (const name of Object.keys(attributes)) {
|
|
92
|
+
attributes[name] = replaceThings(attributes[name]);
|
|
93
|
+
}
|
|
94
|
+
setHtmlNodeAttributes(node, attributes);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
return stringifyHtmlAst(htmlAst);
|
|
99
|
+
}
|
|
94
100
|
return replaceThings(string);
|
|
95
101
|
};
|
|
96
102
|
|