@photostructure/fs-metadata 0.4.0 → 0.5.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/C++_REVIEW_TODO.md +291 -0
- package/CHANGELOG.md +29 -1
- package/CLAUDE.md +169 -0
- package/CONTRIBUTING.md +25 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/array.ts.html +217 -0
- package/coverage/lcov-report/src/async.ts.html +547 -0
- package/coverage/lcov-report/src/debuglog.ts.html +187 -0
- package/coverage/lcov-report/src/defer.ts.html +175 -0
- package/coverage/lcov-report/src/dirname.ts.html +124 -0
- package/coverage/lcov-report/src/error.ts.html +322 -0
- package/coverage/lcov-report/src/fs.ts.html +316 -0
- package/coverage/lcov-report/src/glob.ts.html +472 -0
- package/coverage/lcov-report/src/hidden.ts.html +724 -0
- package/coverage/lcov-report/src/index.html +521 -0
- package/coverage/lcov-report/src/index.ts.html +676 -0
- package/coverage/lcov-report/src/linux/dev_disk.ts.html +316 -0
- package/coverage/lcov-report/src/linux/index.html +146 -0
- package/coverage/lcov-report/src/linux/mount_points.ts.html +364 -0
- package/coverage/lcov-report/src/linux/mtab.ts.html +493 -0
- package/coverage/lcov-report/src/mount_point.ts.html +106 -0
- package/coverage/lcov-report/src/number.ts.html +148 -0
- package/coverage/lcov-report/src/object.ts.html +265 -0
- package/coverage/lcov-report/src/options.ts.html +475 -0
- package/coverage/lcov-report/src/path.ts.html +268 -0
- package/coverage/lcov-report/src/platform.ts.html +112 -0
- package/coverage/lcov-report/src/random.ts.html +205 -0
- package/coverage/lcov-report/src/remote_info.ts.html +553 -0
- package/coverage/lcov-report/src/stack_path.ts.html +298 -0
- package/coverage/lcov-report/src/string.ts.html +382 -0
- package/coverage/lcov-report/src/string_enum.ts.html +208 -0
- package/coverage/lcov-report/src/system_volume.ts.html +301 -0
- package/coverage/lcov-report/src/unc.ts.html +274 -0
- package/coverage/lcov-report/src/units.ts.html +274 -0
- package/coverage/lcov-report/src/uuid.ts.html +157 -0
- package/coverage/lcov-report/src/volume_health_status.ts.html +259 -0
- package/coverage/lcov-report/src/volume_metadata.ts.html +787 -0
- package/coverage/lcov-report/src/volume_mount_points.ts.html +388 -0
- package/coverage/lcov.info +3581 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/src/array.ts.html +217 -0
- package/coverage/src/async.ts.html +547 -0
- package/coverage/src/debuglog.ts.html +187 -0
- package/coverage/src/defer.ts.html +175 -0
- package/coverage/src/dirname.ts.html +124 -0
- package/coverage/src/error.ts.html +322 -0
- package/coverage/src/fs.ts.html +316 -0
- package/coverage/src/glob.ts.html +472 -0
- package/coverage/src/hidden.ts.html +724 -0
- package/coverage/src/index.html +521 -0
- package/coverage/src/index.ts.html +676 -0
- package/coverage/src/linux/dev_disk.ts.html +316 -0
- package/coverage/src/linux/index.html +146 -0
- package/coverage/src/linux/mount_points.ts.html +364 -0
- package/coverage/src/linux/mtab.ts.html +493 -0
- package/coverage/src/mount_point.ts.html +106 -0
- package/coverage/src/number.ts.html +148 -0
- package/coverage/src/object.ts.html +265 -0
- package/coverage/src/options.ts.html +475 -0
- package/coverage/src/path.ts.html +268 -0
- package/coverage/src/platform.ts.html +112 -0
- package/coverage/src/random.ts.html +205 -0
- package/coverage/src/remote_info.ts.html +553 -0
- package/coverage/src/stack_path.ts.html +298 -0
- package/coverage/src/string.ts.html +382 -0
- package/coverage/src/string_enum.ts.html +208 -0
- package/coverage/src/system_volume.ts.html +301 -0
- package/coverage/src/unc.ts.html +274 -0
- package/coverage/src/units.ts.html +274 -0
- package/coverage/src/uuid.ts.html +157 -0
- package/coverage/src/volume_health_status.ts.html +259 -0
- package/coverage/src/volume_metadata.ts.html +787 -0
- package/coverage/src/volume_mount_points.ts.html +388 -0
- package/jest.config.cjs +67 -6
- package/package.json +51 -41
- package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/scripts/check-memory.mjs +243 -0
- package/scripts/clang-tidy.mjs +73 -0
- package/scripts/is-platform.mjs +12 -0
- package/scripts/post-build.mjs +21 -0
- package/scripts/run-asan.sh +92 -0
- package/scripts/valgrind-test.mjs +83 -0
- package/scripts/valgrind.sh +70 -0
- package/src/async.ts +3 -3
- package/src/binding.cpp +3 -3
- package/src/error.ts +3 -3
- package/src/fs.ts +1 -1
- package/src/glob.ts +2 -2
- package/src/hidden.ts +6 -6
- package/src/index.ts +19 -23
- package/src/linux/blkid_cache.cpp +15 -12
- package/src/linux/dev_disk.ts +2 -2
- package/src/linux/gio_mount_points.cpp +7 -7
- package/src/linux/gio_utils.cpp +19 -8
- package/src/linux/gio_volume_metadata.cpp +15 -15
- package/src/linux/mount_points.ts +9 -9
- package/src/linux/mtab.ts +7 -7
- package/src/linux/volume_metadata.cpp +6 -1
- package/src/object.ts +1 -1
- package/src/options.ts +3 -3
- package/src/path.ts +2 -2
- package/src/remote_info.ts +5 -5
- package/src/system_volume.ts +8 -8
- package/src/test-utils/assert.ts +2 -2
- package/src/test-utils/debuglog-child.ts +1 -3
- package/src/test-utils/debuglog-enabled-child.ts +10 -0
- package/src/test-utils/hidden-tests.ts +1 -1
- package/src/test-utils/platform.ts +3 -3
- package/src/types/native_bindings.ts +3 -3
- package/src/types/volume_metadata.ts +2 -2
- package/src/unc.ts +2 -2
- package/src/uuid.ts +1 -1
- package/src/volume_health_status.ts +6 -6
- package/src/volume_metadata.ts +20 -23
- package/src/volume_mount_points.ts +12 -17
- package/src/windows/drive_status.h +30 -13
- package/src/windows/hidden.cpp +12 -0
- package/src/windows/volume_metadata.cpp +17 -7
- package/tsup.config.ts +8 -2
- package/dist/index.cjs +0 -1439
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs +0 -1396
- package/dist/index.mjs.map +0 -1
- package/dist/types/array.d.ts +0 -25
- package/dist/types/async.d.ts +0 -42
- package/dist/types/debuglog.d.ts +0 -3
- package/dist/types/defer.d.ts +0 -10
- package/dist/types/dirname.d.ts +0 -1
- package/dist/types/error.d.ts +0 -17
- package/dist/types/fs.d.ts +0 -22
- package/dist/types/glob.d.ts +0 -17
- package/dist/types/hidden.d.ts +0 -29
- package/dist/types/index.d.ts +0 -91
- package/dist/types/linux/dev_disk.d.ts +0 -13
- package/dist/types/linux/mount_points.d.ts +0 -6
- package/dist/types/linux/mtab.d.ts +0 -47
- package/dist/types/mount_point.d.ts +0 -2
- package/dist/types/number.d.ts +0 -3
- package/dist/types/object.d.ts +0 -18
- package/dist/types/options.d.ts +0 -33
- package/dist/types/path.d.ts +0 -17
- package/dist/types/platform.d.ts +0 -4
- package/dist/types/random.d.ts +0 -12
- package/dist/types/remote_info.d.ts +0 -6
- package/dist/types/stack_path.d.ts +0 -2
- package/dist/types/string.d.ts +0 -37
- package/dist/types/string_enum.d.ts +0 -19
- package/dist/types/system_volume.d.ts +0 -14
- package/dist/types/types/hidden_metadata.d.ts +0 -32
- package/dist/types/types/mount_point.d.ts +0 -46
- package/dist/types/types/native_bindings.d.ts +0 -51
- package/dist/types/types/options.d.ts +0 -47
- package/dist/types/types/remote_info.d.ts +0 -33
- package/dist/types/types/volume_metadata.d.ts +0 -46
- package/dist/types/unc.d.ts +0 -11
- package/dist/types/units.d.ts +0 -38
- package/dist/types/uuid.d.ts +0 -16
- package/dist/types/volume_health_status.d.ts +0 -24
- package/dist/types/volume_metadata.d.ts +0 -8
- package/dist/types/volume_mount_points.d.ts +0 -6
- package/jest.config.base.cjs +0 -63
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-platform memory checking script
|
|
5
|
+
* Runs JavaScript memory tests on all platforms
|
|
6
|
+
* Runs valgrind and ASAN tests only on Linux
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { writeFileSync } from "fs";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Colors for output
|
|
19
|
+
const colors = {
|
|
20
|
+
RED: "\x1b[31m",
|
|
21
|
+
GREEN: "\x1b[32m",
|
|
22
|
+
YELLOW: "\x1b[33m",
|
|
23
|
+
BLUE: "\x1b[34m",
|
|
24
|
+
RESET: "\x1b[0m",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Use colors only if not on Windows
|
|
28
|
+
const isWindows = os.platform() === "win32";
|
|
29
|
+
const color = (colorCode, text) =>
|
|
30
|
+
isWindows ? text : `${colorCode}${text}${colors.RESET}`;
|
|
31
|
+
|
|
32
|
+
console.log(color(colors.BLUE, "=== Memory Leak Detection Suite ==="));
|
|
33
|
+
|
|
34
|
+
let exitCode = 0;
|
|
35
|
+
|
|
36
|
+
// 1. Run JavaScript memory tests (all platforms)
|
|
37
|
+
console.log(color(colors.YELLOW, "\nRunning JavaScript memory tests..."));
|
|
38
|
+
try {
|
|
39
|
+
execSync("npm run test:memory", { stdio: "inherit" });
|
|
40
|
+
console.log(color(colors.GREEN, "✓ JavaScript memory tests passed"));
|
|
41
|
+
} catch {
|
|
42
|
+
console.log(color(colors.RED, "✗ JavaScript memory tests failed"));
|
|
43
|
+
exitCode = 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Run valgrind if available and on Linux
|
|
47
|
+
if (os.platform() === "linux") {
|
|
48
|
+
try {
|
|
49
|
+
execSync("which valgrind", { stdio: "ignore" });
|
|
50
|
+
console.log(color(colors.YELLOW, "\nRunning valgrind memory analysis..."));
|
|
51
|
+
try {
|
|
52
|
+
const valgrindScript = path.join(__dirname, "valgrind.sh");
|
|
53
|
+
execSync(valgrindScript, { stdio: "inherit" });
|
|
54
|
+
console.log(color(colors.GREEN, "✓ Valgrind tests passed"));
|
|
55
|
+
} catch {
|
|
56
|
+
console.log(color(colors.RED, "✗ Valgrind tests failed"));
|
|
57
|
+
exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
console.log(color(colors.YELLOW, "\nValgrind not available. Skipping."));
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
console.log(
|
|
64
|
+
color(colors.YELLOW, "\nValgrind tests only run on Linux. Skipping."),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. Run Address Sanitizer if requested (Linux only for now)
|
|
69
|
+
if (process.env.ENABLE_ASAN) {
|
|
70
|
+
if (os.platform() === "linux") {
|
|
71
|
+
console.log(color(colors.YELLOW, "\nBuilding with AddressSanitizer..."));
|
|
72
|
+
try {
|
|
73
|
+
// Check if clang is available
|
|
74
|
+
execSync("which clang", { stdio: "ignore" });
|
|
75
|
+
|
|
76
|
+
const env = {
|
|
77
|
+
...process.env,
|
|
78
|
+
CC: "clang",
|
|
79
|
+
CXX: "clang++",
|
|
80
|
+
CFLAGS: "-fsanitize=address -fno-omit-frame-pointer -g -O1",
|
|
81
|
+
CXXFLAGS: "-fsanitize=address -fno-omit-frame-pointer -g -O1",
|
|
82
|
+
LDFLAGS: "-fsanitize=address",
|
|
83
|
+
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=0:print_stats=1",
|
|
84
|
+
LSAN_OPTIONS: `suppressions=${path.join(process.cwd(), ".lsan-suppressions.txt")}:print_suppressions=0`,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Find ASan runtime library using clang
|
|
88
|
+
try {
|
|
89
|
+
const asanLib = execSync(
|
|
90
|
+
"clang -print-file-name=libclang_rt.asan-x86_64.so",
|
|
91
|
+
{ encoding: "utf8" },
|
|
92
|
+
).trim();
|
|
93
|
+
if (asanLib && !asanLib.includes("not found")) {
|
|
94
|
+
env.LD_PRELOAD = asanLib;
|
|
95
|
+
console.log(color(colors.BLUE, `Using ASan library: ${asanLib}`));
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// Try common paths as fallback
|
|
99
|
+
const asanLibPaths = [
|
|
100
|
+
"/usr/lib/x86_64-linux-gnu/libasan.so.8",
|
|
101
|
+
"/usr/lib/x86_64-linux-gnu/libasan.so.6",
|
|
102
|
+
"/usr/lib64/libasan.so.8",
|
|
103
|
+
"/usr/lib64/libasan.so.6",
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const libPath of asanLibPaths) {
|
|
107
|
+
try {
|
|
108
|
+
execSync(`test -f ${libPath}`, { stdio: "ignore" });
|
|
109
|
+
env.LD_PRELOAD = libPath;
|
|
110
|
+
console.log(color(colors.BLUE, `Using ASan library: ${libPath}`));
|
|
111
|
+
break;
|
|
112
|
+
} catch {
|
|
113
|
+
// Try next path
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
execSync("npm run node-gyp-rebuild", { stdio: "inherit", env });
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
color(colors.YELLOW, "Running tests with AddressSanitizer..."),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Capture ASAN output for analysis
|
|
125
|
+
let asanOutput = "";
|
|
126
|
+
try {
|
|
127
|
+
asanOutput = execSync("npm test -- --no-coverage 2>&1", {
|
|
128
|
+
env,
|
|
129
|
+
}).toString();
|
|
130
|
+
console.log(asanOutput);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
asanOutput = error.stdout ? error.stdout.toString() : "";
|
|
133
|
+
asanOutput += error.stderr ? error.stderr.toString() : "";
|
|
134
|
+
console.log(asanOutput);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Save full output to file
|
|
138
|
+
const outputFile = path.join(process.cwd(), "asan-output.log");
|
|
139
|
+
writeFileSync(outputFile, asanOutput);
|
|
140
|
+
console.log(
|
|
141
|
+
color(colors.BLUE, `\nFull ASAN output saved to: ${outputFile}`),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Check for ASAN errors in our code (not V8/Node internals)
|
|
145
|
+
const lines = asanOutput.split("\n");
|
|
146
|
+
const hasOurErrors = lines.some(
|
|
147
|
+
(line) =>
|
|
148
|
+
(line.includes("ERROR: AddressSanitizer") ||
|
|
149
|
+
line.includes("ERROR: LeakSanitizer")) &&
|
|
150
|
+
(line.includes("fs_metadata.node") || line.includes("/src/")),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const hasOurLeaks = lines.some(
|
|
154
|
+
(line) =>
|
|
155
|
+
(line.includes("Direct leak") || line.includes("Indirect leak")) &&
|
|
156
|
+
lines
|
|
157
|
+
.slice(
|
|
158
|
+
Math.max(0, lines.indexOf(line) - 5),
|
|
159
|
+
lines.indexOf(line) + 10,
|
|
160
|
+
)
|
|
161
|
+
.some(
|
|
162
|
+
(context) =>
|
|
163
|
+
context.includes("fs_metadata.node") ||
|
|
164
|
+
context.includes("/src/"),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Count V8/Node internal leaks for information
|
|
169
|
+
const internalLeaks = (asanOutput.match(/leak.*\/usr\/bin\/node/g) || [])
|
|
170
|
+
.length;
|
|
171
|
+
|
|
172
|
+
if (hasOurErrors || hasOurLeaks) {
|
|
173
|
+
console.log(
|
|
174
|
+
color(
|
|
175
|
+
colors.RED,
|
|
176
|
+
"\n✗ AddressSanitizer found issues in fs-metadata code:",
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Extract relevant error lines
|
|
181
|
+
const relevantErrors = lines.filter((line, idx) => {
|
|
182
|
+
if (line.includes("ERROR:") || line.includes("leak")) {
|
|
183
|
+
// Check context around this line for our code
|
|
184
|
+
const context = lines
|
|
185
|
+
.slice(Math.max(0, idx - 5), idx + 10)
|
|
186
|
+
.join("\n");
|
|
187
|
+
return (
|
|
188
|
+
context.includes("fs_metadata.node") || context.includes("/src/")
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
relevantErrors.forEach((line) => console.log(color(colors.RED, line)));
|
|
195
|
+
exitCode = 1;
|
|
196
|
+
} else {
|
|
197
|
+
console.log(
|
|
198
|
+
color(
|
|
199
|
+
colors.GREEN,
|
|
200
|
+
"✓ AddressSanitizer tests passed (no issues in fs-metadata code)",
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
if (internalLeaks > 0) {
|
|
204
|
+
console.log(
|
|
205
|
+
color(
|
|
206
|
+
colors.YELLOW,
|
|
207
|
+
` Note: ${internalLeaks} V8/Node.js internal leaks detected (suppressed)`,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error && error.code === 1) {
|
|
214
|
+
console.log(
|
|
215
|
+
color(
|
|
216
|
+
colors.YELLOW,
|
|
217
|
+
"clang not found. Skipping AddressSanitizer tests.",
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
console.log(color(colors.RED, "✗ AddressSanitizer tests failed"));
|
|
222
|
+
exitCode = 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
console.log(
|
|
227
|
+
color(
|
|
228
|
+
colors.YELLOW,
|
|
229
|
+
"\nAddressSanitizer tests are currently only supported on Linux. Skipping.",
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (exitCode === 0) {
|
|
236
|
+
console.log(
|
|
237
|
+
color(colors.GREEN, "\n=== All memory tests completed successfully! ==="),
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
console.log(color(colors.RED, "\n=== Some memory tests failed ==="));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
process.exit(exitCode);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync, spawn } from "child_process";
|
|
3
|
+
import { platform } from "os";
|
|
4
|
+
|
|
5
|
+
// Skip clang-tidy on Windows
|
|
6
|
+
if (platform() === "win32") {
|
|
7
|
+
console.log("Skipping clang-tidy on Windows platform");
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Check for required tools
|
|
12
|
+
function checkCommand(command, installHint) {
|
|
13
|
+
try {
|
|
14
|
+
execSync(`which ${command}`, { stdio: "ignore" });
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
console.error(`Error: '${command}' not found in PATH.`);
|
|
18
|
+
console.error(`To install: ${installHint}`);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isMacOS = platform() === "darwin";
|
|
24
|
+
const isLinux = platform() === "linux";
|
|
25
|
+
|
|
26
|
+
let hasAllTools = true;
|
|
27
|
+
|
|
28
|
+
if (
|
|
29
|
+
!checkCommand(
|
|
30
|
+
"bear",
|
|
31
|
+
isLinux
|
|
32
|
+
? "sudo apt-get install bear"
|
|
33
|
+
: isMacOS
|
|
34
|
+
? "brew install bear"
|
|
35
|
+
: "see https://github.com/rizsotto/Bear",
|
|
36
|
+
)
|
|
37
|
+
) {
|
|
38
|
+
hasAllTools = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
!checkCommand(
|
|
43
|
+
"clang-tidy",
|
|
44
|
+
isLinux
|
|
45
|
+
? "sudo apt-get install clang-tidy"
|
|
46
|
+
: isMacOS
|
|
47
|
+
? "brew install llvm && alias clang-tidy=$(brew --prefix llvm)/bin/clang-tidy"
|
|
48
|
+
: "see https://clang.llvm.org/extra/clang-tidy/",
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
hasAllTools = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!hasAllTools) {
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Run the clang-tidy command on Unix platforms
|
|
59
|
+
const command = `npm run configure && bear -- npm run node-gyp-rebuild && find src -name '*.cpp' -o -name '*.h' | grep -E '\\.(cpp|h)$' | grep -v -E '(windows|darwin)/' | xargs clang-tidy`;
|
|
60
|
+
|
|
61
|
+
const shell = spawn("sh", ["-c", command], {
|
|
62
|
+
stdio: "inherit",
|
|
63
|
+
shell: false,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
shell.on("exit", (code) => {
|
|
67
|
+
process.exit(code || 0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
shell.on("error", (err) => {
|
|
71
|
+
console.error("Failed to run clang-tidy:", err);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { platform } from "os";
|
|
3
|
+
|
|
4
|
+
const targetPlatform = process.argv[2];
|
|
5
|
+
if (!targetPlatform) {
|
|
6
|
+
console.error("Usage: is-platform.mjs <platform>");
|
|
7
|
+
console.error("Example: is-platform.mjs win32");
|
|
8
|
+
process.exit(2);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Exit with 0 if current platform matches target, 1 otherwise
|
|
12
|
+
process.exit(platform() === targetPlatform ? 0 : 1);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { copyFile } from "fs/promises";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const distDir = join(__dirname, "..", "dist");
|
|
9
|
+
|
|
10
|
+
// Copy .d.ts to .d.cts for CommonJS type safety
|
|
11
|
+
async function createCjsTypes() {
|
|
12
|
+
try {
|
|
13
|
+
await copyFile(join(distDir, "index.d.ts"), join(distDir, "index.d.cts"));
|
|
14
|
+
console.log("Created index.d.cts for CommonJS type safety");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error("Error creating .d.cts file:", error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
createCjsTypes();
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# AddressSanitizer test runner for @photostructure/fs-metadata
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# Check if we're on Linux
|
|
7
|
+
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
8
|
+
echo "AddressSanitizer tests are only supported on Linux"
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Check for clang
|
|
13
|
+
if ! command -v clang &> /dev/null; then
|
|
14
|
+
echo "Error: clang is required for AddressSanitizer tests"
|
|
15
|
+
echo "Install with: sudo apt-get install clang"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Colors for output
|
|
20
|
+
RED='\033[0;31m'
|
|
21
|
+
GREEN='\033[0;32m'
|
|
22
|
+
YELLOW='\033[1;33m'
|
|
23
|
+
NC='\033[0m' # No Color
|
|
24
|
+
|
|
25
|
+
echo -e "${YELLOW}Running AddressSanitizer tests...${NC}"
|
|
26
|
+
|
|
27
|
+
# Clean previous builds
|
|
28
|
+
echo "Cleaning previous builds..."
|
|
29
|
+
rm -rf build/
|
|
30
|
+
rm -f asan-output.log
|
|
31
|
+
|
|
32
|
+
# Set up ASan environment
|
|
33
|
+
export CC=clang
|
|
34
|
+
export CXX=clang++
|
|
35
|
+
export CFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1"
|
|
36
|
+
export CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer -g -O1"
|
|
37
|
+
export LDFLAGS="-fsanitize=address"
|
|
38
|
+
|
|
39
|
+
# Find ASan runtime library
|
|
40
|
+
ASAN_LIB=$(clang -print-file-name=libclang_rt.asan-x86_64.so 2>/dev/null || true)
|
|
41
|
+
if [[ -f "$ASAN_LIB" ]]; then
|
|
42
|
+
export LD_PRELOAD="$ASAN_LIB"
|
|
43
|
+
echo "Using ASan library: $ASAN_LIB"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Set ASan options
|
|
47
|
+
export ASAN_OPTIONS="detect_leaks=1:check_initialization_order=1:strict_init_order=1:print_stats=1:print_module_map=1"
|
|
48
|
+
export LSAN_OPTIONS="suppressions=$(pwd)/.lsan-suppressions.txt:print_suppressions=0"
|
|
49
|
+
|
|
50
|
+
# Set Node.js options to increase memory for ASan overhead
|
|
51
|
+
export NODE_OPTIONS="--max-old-space-size=8192"
|
|
52
|
+
|
|
53
|
+
# Build the native module
|
|
54
|
+
echo "Building with AddressSanitizer..."
|
|
55
|
+
npm run node-gyp-rebuild
|
|
56
|
+
|
|
57
|
+
# Run tests and capture output
|
|
58
|
+
echo "Running tests..."
|
|
59
|
+
npm run test -- --no-coverage 2>&1 | tee asan-output.log
|
|
60
|
+
|
|
61
|
+
# Analyze results
|
|
62
|
+
echo -e "\n${YELLOW}Analyzing AddressSanitizer output...${NC}"
|
|
63
|
+
|
|
64
|
+
# Check for errors in our code (excluding V8/Node internals)
|
|
65
|
+
if grep -E "ERROR: AddressSanitizer" asan-output.log | grep -v "/usr/bin/node" > /dev/null 2>&1; then
|
|
66
|
+
echo -e "${RED}AddressSanitizer detected errors in fs-metadata code!${NC}"
|
|
67
|
+
grep -A5 -B5 "ERROR: AddressSanitizer" asan-output.log | grep -v "/usr/bin/node" || true
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Check for leaks in our code
|
|
72
|
+
if grep -E "Direct leak.*photostructure|Indirect leak.*photostructure" asan-output.log > /dev/null 2>&1; then
|
|
73
|
+
echo -e "${RED}Memory leaks detected in fs-metadata code!${NC}"
|
|
74
|
+
grep -A5 -B5 "leak.*photostructure" asan-output.log || true
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Check for any leaks from our native module
|
|
79
|
+
if grep -E "leak.*build/Release/fs_metadata.node" asan-output.log > /dev/null 2>&1; then
|
|
80
|
+
echo -e "${RED}Memory leaks detected in native module!${NC}"
|
|
81
|
+
grep -A5 -B5 "leak.*fs_metadata.node" asan-output.log || true
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Report on V8/Node.js internal leaks (informational only)
|
|
86
|
+
INTERNAL_LEAKS=$(grep -c "leak.*node" asan-output.log 2>/dev/null || echo "0")
|
|
87
|
+
if [[ "$INTERNAL_LEAKS" -gt 0 ]]; then
|
|
88
|
+
echo -e "${YELLOW}Note: Found $INTERNAL_LEAKS V8/Node.js internal leaks (suppressed)${NC}"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
echo -e "${GREEN}✓ AddressSanitizer tests passed! No memory errors in fs-metadata code.${NC}"
|
|
92
|
+
echo "Full output saved to: asan-output.log"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Valgrind test runner for @photostructure/fs-metadata
|
|
5
|
+
*
|
|
6
|
+
* This script exercises the native module's core functionality to detect
|
|
7
|
+
* memory leaks. It's designed to be run under valgrind via npm run test:valgrind
|
|
8
|
+
*
|
|
9
|
+
* The test performs multiple iterations of each operation to help detect
|
|
10
|
+
* memory leaks that might only appear after repeated use.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
getAllVolumeMetadata,
|
|
15
|
+
getVolumeMetadata,
|
|
16
|
+
getVolumeMountPoints,
|
|
17
|
+
isHidden,
|
|
18
|
+
} from "../dist/index.js";
|
|
19
|
+
|
|
20
|
+
async function runTests() {
|
|
21
|
+
console.log("Starting valgrind memory leak tests...");
|
|
22
|
+
|
|
23
|
+
// Test 1: Exercise getVolumeMountPoints multiple times
|
|
24
|
+
console.log("Test 1: getVolumeMountPoints");
|
|
25
|
+
for (let i = 0; i < 10; i++) {
|
|
26
|
+
await getVolumeMountPoints();
|
|
27
|
+
if (i % 5 === 0) console.log(` Iteration ${i + 1}/10`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Test 2: Exercise getVolumeMetadata with valid and invalid paths
|
|
31
|
+
console.log("Test 2: getVolumeMetadata");
|
|
32
|
+
for (let i = 0; i < 10; i++) {
|
|
33
|
+
try {
|
|
34
|
+
await getVolumeMetadata("/");
|
|
35
|
+
} catch {
|
|
36
|
+
// Expected for some platforms
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await getVolumeMetadata("/nonexistent-path-" + i);
|
|
41
|
+
} catch {
|
|
42
|
+
// Expected - testing error paths
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (i % 5 === 0) console.log(` Iteration ${i + 1}/10`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Test 3: Exercise getAllVolumeMetadata
|
|
49
|
+
console.log("Test 3: getAllVolumeMetadata");
|
|
50
|
+
try {
|
|
51
|
+
await getAllVolumeMetadata();
|
|
52
|
+
console.log(" Completed successfully");
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.log(" Completed with expected error:", e.message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Test 4: Exercise hidden file operations
|
|
58
|
+
console.log("Test 4: isHidden");
|
|
59
|
+
const testPaths = ["/tmp", "/var", "/nonexistent"];
|
|
60
|
+
for (let i = 0; i < 10; i++) {
|
|
61
|
+
for (const path of testPaths) {
|
|
62
|
+
try {
|
|
63
|
+
await isHidden(path);
|
|
64
|
+
} catch {
|
|
65
|
+
// Expected for some paths
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (i % 5 === 0) console.log(` Iteration ${i + 1}/10`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log("Valgrind tests completed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Run tests and exit
|
|
75
|
+
runTests()
|
|
76
|
+
.then(() => {
|
|
77
|
+
console.log("All tests completed successfully");
|
|
78
|
+
process.exit(0);
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
console.error("Test error:", err);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Valgrind memory leak detection script for CI/CD
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
7
|
+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
8
|
+
|
|
9
|
+
# Colors for output
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Check if valgrind is available
|
|
16
|
+
if ! command -v valgrind &> /dev/null; then
|
|
17
|
+
echo -e "${YELLOW}Warning: valgrind not found. Skipping memory leak tests.${NC}"
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Only run on Linux
|
|
22
|
+
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
23
|
+
echo -e "${YELLOW}Valgrind tests only run on Linux. Skipping.${NC}"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo -e "${GREEN}Running valgrind memory leak detection...${NC}"
|
|
28
|
+
|
|
29
|
+
# Path to the dedicated valgrind test script
|
|
30
|
+
VALGRIND_TEST="$SCRIPT_DIR/valgrind-test.mjs"
|
|
31
|
+
|
|
32
|
+
# Ensure the test script exists
|
|
33
|
+
if [ ! -f "$VALGRIND_TEST" ]; then
|
|
34
|
+
echo -e "${RED}Error: Valgrind test script not found at $VALGRIND_TEST${NC}"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Use the committed suppressions file
|
|
39
|
+
SUPP_FILE="$ROOT_DIR/.valgrind.supp"
|
|
40
|
+
|
|
41
|
+
# Ensure suppressions file exists
|
|
42
|
+
if [ ! -f "$SUPP_FILE" ]; then
|
|
43
|
+
echo -e "${RED}Error: Valgrind suppressions file not found at $SUPP_FILE${NC}"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Run valgrind with appropriate options
|
|
48
|
+
VALGRIND_OPTS="--leak-check=full --show-leak-kinds=definite,indirect,possible --track-origins=yes --suppressions=$SUPP_FILE"
|
|
49
|
+
|
|
50
|
+
echo "Running valgrind tests..."
|
|
51
|
+
if valgrind $VALGRIND_OPTS node "$VALGRIND_TEST" 2>&1 | tee "$ROOT_DIR/valgrind.log"; then
|
|
52
|
+
# Check the log for actual leaks
|
|
53
|
+
if grep -q "definitely lost: 0 bytes in 0 blocks" "$ROOT_DIR/valgrind.log" && \
|
|
54
|
+
grep -q "indirectly lost: 0 bytes in 0 blocks" "$ROOT_DIR/valgrind.log"; then
|
|
55
|
+
echo -e "${GREEN}✓ No memory leaks detected${NC}"
|
|
56
|
+
RESULT=0
|
|
57
|
+
else
|
|
58
|
+
echo -e "${RED}✗ Memory leaks detected${NC}"
|
|
59
|
+
grep -A 5 "LEAK SUMMARY" "$ROOT_DIR/valgrind.log"
|
|
60
|
+
RESULT=1
|
|
61
|
+
fi
|
|
62
|
+
else
|
|
63
|
+
echo -e "${RED}✗ Valgrind error${NC}"
|
|
64
|
+
RESULT=1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Cleanup log file only (keep the committed suppression file)
|
|
68
|
+
rm -f "$ROOT_DIR/valgrind.log"
|
|
69
|
+
|
|
70
|
+
exit $RESULT
|
package/src/async.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { availableParallelism } from "node:os";
|
|
2
2
|
import { env } from "node:process";
|
|
3
|
-
import { gt0, isNumber } from "./number
|
|
4
|
-
import { isBlank } from "./string
|
|
5
|
-
import { DayMs } from "./units
|
|
3
|
+
import { gt0, isNumber } from "./number";
|
|
4
|
+
import { isBlank } from "./string";
|
|
5
|
+
import { DayMs } from "./units";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* An error that is thrown when a promise does not resolve within the specified
|
package/src/binding.cpp
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
namespace {
|
|
21
21
|
|
|
22
22
|
Napi::Value SetDebugLogging(const Napi::CallbackInfo &info) {
|
|
23
|
-
Napi::Env env = info.Env();
|
|
23
|
+
const Napi::Env env = info.Env();
|
|
24
24
|
|
|
25
25
|
if (info.Length() < 1 || !info[0].IsBoolean()) {
|
|
26
26
|
throw Napi::TypeError::New(env, "Boolean argument expected");
|
|
@@ -31,7 +31,7 @@ Napi::Value SetDebugLogging(const Napi::CallbackInfo &info) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
Napi::Value SetDebugPrefix(const Napi::CallbackInfo &info) {
|
|
34
|
-
Napi::Env env = info.Env();
|
|
34
|
+
const Napi::Env env = info.Env();
|
|
35
35
|
|
|
36
36
|
if (info.Length() < 1 || !info[0].IsString()) {
|
|
37
37
|
throw Napi::TypeError::New(env, "String argument expected");
|
|
@@ -44,7 +44,7 @@ Napi::Value SetDebugPrefix(const Napi::CallbackInfo &info) {
|
|
|
44
44
|
|
|
45
45
|
#ifdef ENABLE_GIO
|
|
46
46
|
Napi::Value GetGioMountPoints(const Napi::CallbackInfo &info) {
|
|
47
|
-
Napi::Env env = info.Env();
|
|
47
|
+
const Napi::Env env = info.Env();
|
|
48
48
|
return FSMeta::gio::GetMountPoints(env);
|
|
49
49
|
}
|
|
50
50
|
#endif
|
package/src/error.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/error.ts
|
|
2
2
|
|
|
3
|
-
import { isNumber } from "./number
|
|
4
|
-
import { compactValues, map, omit } from "./object
|
|
5
|
-
import { isBlank, isNotBlank } from "./string
|
|
3
|
+
import { isNumber } from "./number";
|
|
4
|
+
import { compactValues, map, omit } from "./object";
|
|
5
|
+
import { isBlank, isNotBlank } from "./string";
|
|
6
6
|
|
|
7
7
|
function toMessage(context: string, cause: unknown): string {
|
|
8
8
|
const causeStr =
|
package/src/fs.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { type PathLike, type StatOptions, Stats, statSync } from "node:fs";
|
|
4
4
|
import { opendir, stat } from "node:fs/promises";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
|
-
import { withTimeout } from "./async
|
|
6
|
+
import { withTimeout } from "./async";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Wrapping node:fs/promises.stat() so we can mock it in tests.
|
package/src/glob.ts
CHANGED
package/src/hidden.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { rename } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
|
-
import { WrappedError } from "./error
|
|
6
|
-
import { canStatAsync, statAsync } from "./fs
|
|
7
|
-
import { isRootDirectory, normalizePath } from "./path
|
|
8
|
-
import { isWindows } from "./platform
|
|
9
|
-
import type { HiddenMetadata } from "./types/hidden_metadata
|
|
10
|
-
import type { NativeBindingsFn } from "./types/native_bindings
|
|
5
|
+
import { WrappedError } from "./error";
|
|
6
|
+
import { canStatAsync, statAsync } from "./fs";
|
|
7
|
+
import { isRootDirectory, normalizePath } from "./path";
|
|
8
|
+
import { isWindows } from "./platform";
|
|
9
|
+
import type { HiddenMetadata } from "./types/hidden_metadata";
|
|
10
|
+
import type { NativeBindingsFn } from "./types/native_bindings";
|
|
11
11
|
|
|
12
12
|
const HiddenSupportByPlatform: Partial<
|
|
13
13
|
Record<NodeJS.Platform, Pick<HiddenMetadata, "supported">>
|