@jsenv/snapshot 2.5.3 → 2.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/snapshot",
3
- "version": "2.5.3",
3
+ "version": "2.6.0",
4
4
  "description": "Snapshot testing",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -35,10 +35,10 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@jsenv/assert": "4.1.6",
38
- "@jsenv/ast": "6.2.6",
38
+ "@jsenv/ast": "6.2.7",
39
39
  "@jsenv/exception": "1.0.0",
40
- "@jsenv/filesystem": "4.9.3",
41
- "@jsenv/urls": "2.3.2",
40
+ "@jsenv/filesystem": "4.9.4",
41
+ "@jsenv/urls": "2.4.0",
42
42
  "@jsenv/utils": "2.1.2",
43
43
  "pixelmatch": "6.0.0",
44
44
  "prettier": "3.3.3",
@@ -11,8 +11,14 @@ import {
11
11
  stringifyHtmlAst,
12
12
  visitHtmlNodes,
13
13
  } from "@jsenv/ast";
14
- import { urlToExtension } from "@jsenv/urls";
14
+ import {
15
+ ensurePathnameTrailingSlash,
16
+ removePathnameTrailingSlash,
17
+ urlToExtension,
18
+ } from "@jsenv/urls";
15
19
  import { escapeRegexpSpecialChars } from "@jsenv/utils/src/string/escape_regexp_special_chars.js";
20
+ import { readFileSync } from "node:fs";
21
+ import { homedir } from "node:os";
16
22
  import { fileURLToPath, pathToFileURL } from "node:url";
17
23
  import stripAnsi from "strip-ansi";
18
24
 
@@ -22,35 +28,133 @@ export const replaceFluctuatingValues = (
22
28
  stringType,
23
29
  fileUrl,
24
30
  removeAnsi = true,
25
- rootDirectoryUrl = pathToFileURL(process.cwd()),
31
+ rootDirectoryUrl,
26
32
  // for unit tests
27
- rootDirectoryPath,
33
+ ancestorPackagesDisabled,
34
+ ancestorPackagesRootDirectoryUrl = "file:///",
35
+ homedirDisabled,
36
+ cwdPath = process.cwd(),
37
+ cwdUrl,
28
38
  isWindows = process.platform === "win32",
29
39
  } = {},
30
40
  ) => {
31
- rootDirectoryUrl = String(rootDirectoryUrl);
32
- if (rootDirectoryUrl[rootDirectoryUrl.length - 1] === "/") {
33
- rootDirectoryUrl = rootDirectoryUrl.slice(0, -1);
41
+ const wellKownUrlArray = [];
42
+ const wellKnownPathArray = [];
43
+ const addWellKnownFileUrl = (url, replacement) => {
44
+ const urlWithoutTrailingSlash = removePathnameTrailingSlash(url);
45
+ wellKownUrlArray.push({
46
+ url: urlWithoutTrailingSlash,
47
+ replacement,
48
+ replace: (string) =>
49
+ string.replaceAll(urlWithoutTrailingSlash, replacement),
50
+ });
51
+ const path =
52
+ url === cwdUrl ? cwdPath : fileURLToPath(urlWithoutTrailingSlash);
53
+ const windowPathRegex = new RegExp(
54
+ `${escapeRegexpSpecialChars(path)}(((?:\\\\(?:[\\w !#()-]+|[.]{1,2})+)*)(?:\\\\)?)`,
55
+ "gm",
56
+ );
57
+ const pathReplacement = replacement.startsWith("file:///")
58
+ ? replacement.slice("file:///".length)
59
+ : replacement;
60
+ wellKnownPathArray.push({
61
+ path,
62
+ replacement: pathReplacement,
63
+ replace: isWindows
64
+ ? (string) => {
65
+ return string.replaceAll(windowPathRegex, (match, after) => {
66
+ return `${pathReplacement}${after.replaceAll("\\", "/")}`;
67
+ });
68
+ }
69
+ : (string) => string.replaceAll(path, pathReplacement),
70
+ });
71
+ };
72
+ if (rootDirectoryUrl) {
73
+ addWellKnownFileUrl(rootDirectoryUrl, "file:///<root>");
74
+ }
75
+ /*
76
+ * When running code inside a node project ancestor packages
77
+ * should make things super predictible because
78
+ * it will use a package.json name field
79
+ * to replace files urls
80
+ * And uses the highest ancestor package so that even if the file
81
+ * is executed once within a package then outside that package
82
+ * the replace value remains predictible as the highest package is used
83
+ * The highest package is used because it's pushed first by
84
+ * addWellKnownFileUrl
85
+ */
86
+ ancestor_packages: {
87
+ if (ancestorPackagesDisabled) {
88
+ break ancestor_packages;
89
+ }
90
+ const ancestorPackages = [];
91
+ const cwd = cwdPath || process.cwd();
92
+ const cwdUrl = ensurePathnameTrailingSlash(pathToFileURL(cwd));
93
+ let currentUrl = cwdUrl;
94
+ while (currentUrl.href !== ancestorPackagesRootDirectoryUrl) {
95
+ const packageFileUrl = new URL("package.json", currentUrl);
96
+ const packageDirectoryUrl = currentUrl;
97
+ currentUrl = new URL(getParentUrl(currentUrl));
98
+ let packageFileContent;
99
+ try {
100
+ packageFileContent = readFileSync(packageFileUrl);
101
+ } catch (e) {
102
+ if (e.code === "ENOENT") {
103
+ continue;
104
+ }
105
+ throw e;
106
+ }
107
+ let packageObject;
108
+ try {
109
+ packageObject = JSON.parse(packageFileContent);
110
+ } catch (e) {
111
+ continue;
112
+ }
113
+ const packageName = packageObject.name;
114
+ ancestorPackages.unshift({
115
+ packageDirectoryUrl,
116
+ packageName,
117
+ });
118
+ }
119
+ for (const ancestorPackage of ancestorPackages) {
120
+ addWellKnownFileUrl(
121
+ ancestorPackage.packageDirectoryUrl,
122
+ ancestorPackage.packageName,
123
+ );
124
+ }
125
+ }
126
+ home_dir: {
127
+ if (!homedirDisabled) {
128
+ const homedirPath = homedir();
129
+ const homedirUrl = pathToFileURL(homedirPath);
130
+ addWellKnownFileUrl(homedirUrl, "file:///~");
131
+ }
34
132
  }
35
- if (rootDirectoryPath === undefined) {
36
- rootDirectoryPath = fileURLToPath(rootDirectoryUrl);
133
+ process_cwd: {
134
+ // we fallback on process.cwd()
135
+ // but it's brittle because a file might be execute from anywhere
136
+ // so it should be the last resort
137
+ cwdUrl = cwdUrl || pathToFileURL(cwdPath);
138
+ addWellKnownFileUrl(cwdUrl, "file:///cwd()");
37
139
  }
38
140
  const replaceFileUrls = (value) => {
39
- return value.replaceAll(rootDirectoryUrl, "file:///cwd()");
141
+ for (const wellKownUrl of wellKownUrlArray) {
142
+ const replaceResult = wellKownUrl.replace(value);
143
+ if (replaceResult !== value) {
144
+ return replaceResult;
145
+ }
146
+ }
147
+ return value;
40
148
  };
41
- const replaceFilePaths = isWindows
42
- ? (value) => {
43
- const windowPathRegex = new RegExp(
44
- `${escapeRegexpSpecialChars(rootDirectoryPath)}(((?:\\\\(?:[\\w !#()-]+|[.]{1,2})+)*)(?:\\\\)?)`,
45
- "gm",
46
- );
47
- return value.replaceAll(windowPathRegex, (match, afterCwd) => {
48
- return `cwd()${afterCwd.replaceAll("\\", "/")}`;
49
- });
149
+ const replaceFilePaths = (value) => {
150
+ for (const wellKownPath of wellKnownPathArray) {
151
+ const replaceResult = wellKownPath.replace(value);
152
+ if (replaceResult !== value) {
153
+ return replaceResult;
50
154
  }
51
- : (value) => {
52
- return value.replaceAll(rootDirectoryPath, "cwd()");
53
- };
155
+ }
156
+ return value;
157
+ };
54
158
  const replaceThings = (value) => {
55
159
  if (removeAnsi) {
56
160
  value = stripAnsi(value);
@@ -100,6 +204,27 @@ export const replaceFluctuatingValues = (
100
204
  return replaceThings(string);
101
205
  };
102
206
 
207
+ const getParentUrl = (url) => {
208
+ url = String(url);
209
+ // With node.js new URL('../', 'file:///C:/').href
210
+ // returns "file:///C:/" instead of "file:///"
211
+ const resource = url.slice("file://".length);
212
+ const slashLastIndex = resource.lastIndexOf("/");
213
+ if (slashLastIndex === -1) {
214
+ return url;
215
+ }
216
+ const lastCharIndex = resource.length - 1;
217
+ if (slashLastIndex === lastCharIndex) {
218
+ const slashBeforeLastIndex = resource.lastIndexOf("/", slashLastIndex - 1);
219
+ if (slashBeforeLastIndex === -1) {
220
+ return url;
221
+ }
222
+ return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
223
+ }
224
+
225
+ return `file://${resource.slice(0, slashLastIndex + 1)}`;
226
+ };
227
+
103
228
  const replaceHttpUrls = (source) => {
104
229
  return source.replace(/(?:https?|ftp):\/\/\S+[\w/]/g, (match) => {
105
230
  const lastChar = match[match.length - 1];